mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Switch config system to Configurate (#5010)
* Start implementing Configurate config system * More development * Start migrating to Gson only * Progress * Update usage of WebUtils * Most things now use Gson for JSON * Allow tests to succeed by using new Gson version * Use slightly cleaner version for Version deserializer * Work around older Gson versions without record support * GeyserCustomSkullConfiguration uses Configurate * Fix regression in properties get * New config used in core * The configuration is gone. Long live the config. * More changes and migrations away from Jackson * Improve node ordering when updating configs * typo * Better check for ignoring non-configurate configs for considering comment moving * Ensure metrics UUID is valid * Initial advanced config * Remove Jackson; finish config value placements * Remove duplicate relocate declarations * Let annotations work * Renaming to PluginSpecific * Use global bStats config where possible * Fix test * Re-introduce asterisk behavior in configs * Remove GeyserPluginBootstrap as it's no longer necessary * Remove old config.yml file * Update Xbox achievement comment * Apply suggestions from code review Co-authored-by: chris <github@onechris.mozmail.com> * No need to remove values anymore * Fix: disable bstats relocation on platforms where it is not needed * ensure it builds * Update custom unavailable slot comment Co-authored-by: chris <github@onechris.mozmail.com> * Update cooldown image * Logger message for direct-compression still being enabled * oops * More explicit RuntimeException message Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> * Constant for 'system' locale * Better config JSON encoding (something is broken with Cloudflare; we'll figure it out * Fix broadcast port default * Add this file too * Update configurate branch * fix build * Fix: Allow using custom config file on Standalone, add relocation comment * Move config loading to GeyserBootstrap interface * Add and rename some config options, add section notes (#5390) * Add and rename some config options, add section notes * adjust message * Update core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz> * Update core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz> * Update ConfigLoader.java * Update AdvancedConfig.java * clarify that we're talking about the HAProxy protocol * rename config option to use-haproxy-protocol * remove ominous warning sign on xbox auth warning * adjust wording --------- Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz> * Back to one config file * Some minor touchups * Configurate: Sectionification (#5904) * Init: config sections * Start on adding tests, move migrations over to ConfigMigrations class * Get rid of auth section again, rename that one config option, fix mtu migration * Move custom skulls config options to the bottom of the gameplay settings * Add more tests * Rename and migrate proxy-protocol-whitelisted-ips to haproxy-protocol-whitelisted-ips * Add automatic downloading of the GeyserOptionalPack * Revert "Add automatic downloading of the GeyserOptionalPack" This reverts commit65b96208fb. * Add more invalid config tests * Warn about emote-offhand-workaround removal * Add automatic loading of the GeyserOptionalPack (feature/configurate) (#5964) * Add automatic downloading of the GeyserOptionalPack * Warn about including the OptionalPack from extensions when Geyser is already including it instead of throwing. * Copy optional pack instead of downloading --------- Co-authored-by: onebeastchris <github@onechris.mozmail.com> * Remove unused variable * Start warning users not running Java 21 * Update tests, temporarily remove NumericRanges test * Remove duplicate advanced section from Geyser dump * Address some "reviews" * yeet md5 hash from geyser dump * Add info about number of resource packs / amount of mappings into Geyser dump * Re-enable invalid config loading test * Fix: allow-custom-skulls migration * Fix test * Add "enable-emotes" configuration option * Rename "emotes-enabled" to "show-emotes" * Only enable integrated pack when optional pack isn't present * Update integrated pack * Exclude jackson annotations, remove leftover debug print * Remove one-time config migration warnings as we don't have access to the logger at that stage * Throw more detailed descriptive error when loading resource packs from the "packs" folder, add another legacy config test * Fix NeoForge's fun module conflict * Re-add warning about moved functionality, fix Geyser-ViaProxy This reverts commitfbadfa574a. * oops * Move GeyserLegacyPingPassthrough to separate thread to avoid Standalone command locking issues --------- Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> Co-authored-by: chris <github@onechris.mozmail.com> Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz> Co-authored-by: Aurora <auroranova8756@gmail.com>
This commit is contained in:
@@ -15,11 +15,12 @@ dependencies {
|
||||
}
|
||||
|
||||
platformRelocate("net.md_5.bungee.jni")
|
||||
platformRelocate("com.fasterxml.jackson")
|
||||
platformRelocate("net.kyori")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated
|
||||
platformRelocate("org.yaml") // Broken as of 1.20
|
||||
platformRelocate("org.spongepowered")
|
||||
platformRelocate("org.bstats")
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.bungeecord.proxy)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.platform.bungeecord;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class BungeeMetrics implements MetricsPlatform {
|
||||
private final Configuration configuration;
|
||||
|
||||
public BungeeMetrics(Plugin plugin) throws IOException {
|
||||
// https://github.com/Bastian/bstats-metrics/blob/master/bungeecord/src/main/java/org/bstats/bungeecord/Metrics.java
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
bStatsFolder.mkdirs();
|
||||
File configFile = new File(bStatsFolder, "config.yml");
|
||||
if (!configFile.exists()) {
|
||||
writeFile(configFile,
|
||||
"# bStats (https://bStats.org) collects some basic information for plugin authors, like how",
|
||||
"# many people use their plugin and their total player count. It's recommended to keep bStats",
|
||||
"# enabled, but if you're not comfortable with this, you can turn this setting off. There is no",
|
||||
"# performance penalty associated with having metrics enabled, and data sent to bStats is fully",
|
||||
"# anonymous.",
|
||||
"enabled: true",
|
||||
"serverUuid: \"" + UUID.randomUUID() + "\"",
|
||||
"logFailedRequests: false",
|
||||
"logSentData: false",
|
||||
"logResponseStatusText: false");
|
||||
}
|
||||
|
||||
this.configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return configuration.getBoolean("enabled", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serverUuid() {
|
||||
return configuration.getString("serverUuid");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logFailedRequests() {
|
||||
return configuration.getBoolean("logFailedRequests", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logSentData() {
|
||||
return configuration.getBoolean("logSentData", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logResponseStatusText() {
|
||||
return configuration.getBoolean("logResponseStatusText", false);
|
||||
}
|
||||
|
||||
private void writeFile(File file, String... lines) throws IOException {
|
||||
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) {
|
||||
for (String line : lines) {
|
||||
bufferedWriter.write(line);
|
||||
bufferedWriter.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
listenerInfo.isPingPassthrough(),
|
||||
listenerInfo.getQueryPort(),
|
||||
listenerInfo.isQueryEnabled(),
|
||||
bootstrap.getGeyserConfig().getRemote().isUseProxyProtocol() // If Geyser is expecting HAProxy, so should the Bungee end
|
||||
bootstrap.config().advanced().java().useHaproxyProtocol() // If Geyser is expecting HAProxy, so should the Bungee end
|
||||
);
|
||||
|
||||
// The field that stores all listeners in BungeeCord
|
||||
@@ -191,7 +191,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
}
|
||||
initChannel.invoke(channelInitializer, ch);
|
||||
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression()) {
|
||||
if (bootstrap.config().advanced().java().disableCompression()) {
|
||||
ch.pipeline().addAfter(PipelineUtils.PACKET_ENCODER, "geyser-compression-disabler",
|
||||
new GeyserBungeeCompressionDisabler());
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
|
||||
try {
|
||||
event = future.get(100, TimeUnit.MILLISECONDS);
|
||||
} catch (Throwable cause) {
|
||||
String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
|
||||
String address = GeyserImpl.getInstance().config().logPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -33,25 +33,25 @@ import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.CommandSourceConverter;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bungee.BungeeCommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -61,13 +61,12 @@ import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserBungeeConfiguration geyserConfig;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserBungeeInjector geyserInjector;
|
||||
private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
|
||||
private IGeyserPingPassthrough geyserBungeePingPassthrough;
|
||||
@@ -102,12 +101,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
System.setProperty("Mcpl.io_uring", "true");
|
||||
}
|
||||
|
||||
if (!this.loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
|
||||
this.geyser = GeyserImpl.load(this);
|
||||
this.geyserInjector = new GeyserBungeeInjector(this);
|
||||
|
||||
// Registration of listeners occurs only once
|
||||
@@ -168,16 +166,15 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
|
||||
public void onGeyserEnable() {
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
}
|
||||
|
||||
// Force-disable query if enabled, or else Geyser won't enable
|
||||
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
|
||||
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) {
|
||||
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.bedrock().port()) {
|
||||
try {
|
||||
Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled");
|
||||
queryField.setAccessible(true);
|
||||
@@ -195,7 +192,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
if (!geyserConfig.motd().integratedPingPassthrough()) {
|
||||
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
|
||||
@@ -232,8 +229,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserBungeeConfiguration getGeyserConfig() {
|
||||
return geyserConfig;
|
||||
public @NonNull PlatformType platformType() {
|
||||
return PlatformType.BUNGEECORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserPluginConfig config() {
|
||||
return this.geyserConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -290,11 +292,29 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
|
||||
@Override
|
||||
public boolean testFloodgatePluginPresent() {
|
||||
if (getProxy().getPluginManager().getPlugin("floodgate") != null) {
|
||||
geyserConfig.loadFloodgate(this);
|
||||
return true;
|
||||
return getProxy().getPluginManager().getPlugin("floodgate") != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
Plugin floodgate = getProxy().getPluginManager().getPlugin("floodgate");
|
||||
Path geyserDataFolder = getDataFolder().toPath();
|
||||
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
||||
|
||||
return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsPlatform createMetricsPlatform() {
|
||||
try {
|
||||
return new BungeeMetrics(this);
|
||||
} catch (IOException e) {
|
||||
this.geyserLogger.debug("Integrated bStats support failed to load.");
|
||||
if (this.config().debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Optional<InetSocketAddress> findCompatibleListener() {
|
||||
@@ -303,20 +323,4 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
.map(info -> (InetSocketAddress) info.getSocketAddress())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
if (!getDataFolder().exists()) //noinspection ResultOfMethodCallIgnored
|
||||
getDataFolder().mkdir();
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class GeyserBungeeUpdateListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(final PostLoginEvent event) {
|
||||
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
|
||||
if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
|
||||
final ProxiedPlayer player = event.getPlayer();
|
||||
if (player.hasPermission(Permissions.CHECK_UPDATE)) {
|
||||
VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player));
|
||||
|
||||
@@ -17,7 +17,7 @@ dependencies {
|
||||
shadowBundle(projects.core)
|
||||
includeTransitive(projects.core)
|
||||
|
||||
// These are NOT transitively included, and instead shadowed + relocated.
|
||||
// These are NOT transitively included, and instead shadowed (+ relocated, if not under the org.geyser namespace).
|
||||
// Avoids fabric complaining about non-SemVer versioning
|
||||
shadowBundle(libs.protocol.connection)
|
||||
shadowBundle(libs.protocol.common)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.platform.fabric;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.fabricmc.api.EnvType;
|
||||
@@ -48,7 +49,7 @@ public class GeyserFabricDumpInfo extends BootstrapDumpInfo {
|
||||
private final String minecraftVersion;
|
||||
private final EnvType environmentType;
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonAdapter(value = AsteriskSerializer.class)
|
||||
private final String serverIP;
|
||||
private final int serverPort;
|
||||
private final boolean onlineMode;
|
||||
|
||||
@@ -73,7 +73,7 @@ public class GeyserFabricPlatform implements GeyserModPlatform {
|
||||
Optional<ModContainer> floodgate = FabricLoader.getInstance().getModContainer("floodgate");
|
||||
if (floodgate.isPresent()) {
|
||||
Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate");
|
||||
bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder);
|
||||
bootstrap.loadFloodgate(floodgateDataFolder);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ architectury {
|
||||
provided("org.cloudburstmc.math", "api")
|
||||
provided("com.google.errorprone", "error_prone_annotations")
|
||||
|
||||
// Jackson shipped by Minecraft is too old, so we shade & relocate our newer version
|
||||
relocate("com.fasterxml.jackson")
|
||||
|
||||
dependencies {
|
||||
// See https://github.com/google/guava/issues/6618
|
||||
modules {
|
||||
@@ -30,15 +27,12 @@ dependencies {
|
||||
shadowBundle(project(path = ":mod", configuration = "transformProductionNeoForge"))
|
||||
shadowBundle(projects.core)
|
||||
|
||||
// Minecraft (1.21.2+) includes jackson. But an old version!
|
||||
shadowBundle(libs.jackson.core)
|
||||
shadowBundle(libs.jackson.databind)
|
||||
shadowBundle(libs.jackson.dataformat.yaml)
|
||||
shadowBundle(libs.jackson.annotations)
|
||||
|
||||
// Let's shade in our own api
|
||||
shadowBundle(projects.api)
|
||||
|
||||
// this one is particularly dumb
|
||||
shadowBundle(libs.configurate.`interface`)
|
||||
|
||||
// cannot be shaded, since neoforge will complain if floodgate-neoforge tries to provide this
|
||||
include(projects.common)
|
||||
|
||||
@@ -61,11 +55,6 @@ tasks {
|
||||
remapModrinthJar {
|
||||
archiveBaseName.set("geyser-neoforge")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// Without this, jackson's service files are not relocated
|
||||
mergeServiceFiles()
|
||||
}
|
||||
}
|
||||
|
||||
modrinth {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.platform.neoforge;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
@@ -47,7 +48,7 @@ public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo {
|
||||
private final String minecraftVersion;
|
||||
private final Dist dist;
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonAdapter(value = AsteriskSerializer.class)
|
||||
private final String serverIP;
|
||||
private final int serverPort;
|
||||
private final boolean onlineMode;
|
||||
|
||||
@@ -72,7 +72,7 @@ public class GeyserNeoForgePlatform implements GeyserModPlatform {
|
||||
public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
|
||||
if (ModList.get().isLoaded("floodgate")) {
|
||||
Path floodgateDataFolder = FMLPaths.CONFIGDIR.get().resolve("floodgate");
|
||||
bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder);
|
||||
bootstrap.loadFloodgate(floodgateDataFolder);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -31,11 +31,13 @@ import lombok.Setter;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
@@ -43,14 +45,10 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
|
||||
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
@@ -69,7 +67,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
|
||||
@Setter
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserModConfiguration geyserConfig;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserModInjector geyserInjector;
|
||||
private final GeyserModLogger geyserLogger = new GeyserModLogger();
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
@@ -80,12 +78,11 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
instance = this;
|
||||
dataFolder = this.platform.dataFolder(this.platform.configPath());
|
||||
GeyserLocale.init(this);
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
|
||||
this.geyser = GeyserImpl.load(this);
|
||||
}
|
||||
|
||||
public void onGeyserEnable() {
|
||||
@@ -95,16 +92,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
}
|
||||
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
if (!geyserConfig.motd().integratedPingPassthrough()) {
|
||||
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
|
||||
@@ -145,7 +141,12 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserModConfiguration getGeyserConfig() {
|
||||
public @NonNull PlatformType platformType() {
|
||||
return this.platform.platformType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserPluginConfig config() {
|
||||
return geyserConfig;
|
||||
}
|
||||
|
||||
@@ -199,12 +200,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
|
||||
@Override
|
||||
public int getServerPort() {
|
||||
if (isServer()) {
|
||||
return ((GeyserServerPortGetter) server).geyser$getServerPort();
|
||||
} else {
|
||||
// Set in the IntegratedServerMixin
|
||||
return geyserConfig.getRemote().port();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean isServer();
|
||||
@@ -214,31 +210,23 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
return this.platform.testFloodgatePluginPresent(this);
|
||||
}
|
||||
|
||||
private Path floodgateKeyPath;
|
||||
|
||||
public void loadFloodgate(Path floodgateDataFolder) {
|
||||
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, dataFolder, geyserLogger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
return floodgateKeyPath;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public InputStream getResourceOrNull(String resource) {
|
||||
return this.platform.resolveResource(resource);
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
if (!dataFolder.toFile().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dataFolder.toFile().mkdir();
|
||||
}
|
||||
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserModConfiguration.class);
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getServerPlatform() {
|
||||
return server.getServerModName();
|
||||
|
||||
@@ -96,7 +96,7 @@ public class GeyserModInjector extends GeyserInjector {
|
||||
int index = ch.pipeline().names().indexOf("encoder");
|
||||
String baseName = index != -1 ? "encoder" : "outbound_config";
|
||||
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression()) {
|
||||
if (bootstrap.config().advanced().java().disableCompression()) {
|
||||
ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserModCompressionDisabler());
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public class GeyserModInjector extends GeyserInjector {
|
||||
childHandler = (ChannelInitializer<Channel>) childHandlerField.get(handler);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
if (bootstrap.getGeyserConfig().isDebugMode()) {
|
||||
if (bootstrap.config().debugMode()) {
|
||||
bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.geysermc.geyser.util.VersionCheckUtils;
|
||||
public final class GeyserModUpdateListener {
|
||||
public static void onPlayReady(ServerPlayer player) {
|
||||
// We could just not register the listener, but, this allows config reloading
|
||||
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
|
||||
if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
|
||||
// Should be creating this in the supplier, but we need it for the permission check.
|
||||
// Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
|
||||
ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
|
||||
|
||||
@@ -56,14 +56,13 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
|
||||
// If the LAN is opened, starts Geyser.
|
||||
GeyserModBootstrap instance = GeyserModBootstrap.getInstance();
|
||||
instance.setServer((MinecraftServer) (Object) this);
|
||||
instance.getGeyserConfig().getRemote().setPort(port);
|
||||
instance.onGeyserEnable();
|
||||
// Ensure player locale has been loaded, in case it's different from Java system language
|
||||
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
|
||||
// Give indication that Geyser is loaded
|
||||
Objects.requireNonNull(this.minecraft.player);
|
||||
this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start",
|
||||
this.minecraft.options.languageCode, "localhost", String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false);
|
||||
this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start.ip_suppressed",
|
||||
this.minecraft.options.languageCode, String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,17 +31,25 @@ dependencies {
|
||||
compileOnly(libs.folia.api)
|
||||
|
||||
compileOnlyApi(libs.viaversion)
|
||||
|
||||
// For 1.16.5/1.17.1
|
||||
implementation(libs.gson.record.factory) {
|
||||
isTransitive = false
|
||||
}
|
||||
}
|
||||
|
||||
platformRelocate("it.unimi.dsi.fastutil")
|
||||
platformRelocate("com.fasterxml.jackson")
|
||||
// Relocate net.kyori but exclude the component logger
|
||||
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
|
||||
platformRelocate("org.objectweb.asm")
|
||||
platformRelocate("me.lucko.commodore")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated
|
||||
platformRelocate("org.yaml") // Broken as of 1.20
|
||||
platformRelocate("org.spongepowered")
|
||||
platformRelocate("marcono1234.gson")
|
||||
platformRelocate("org.bstats")
|
||||
|
||||
provided(libs.viaversion)
|
||||
|
||||
tasks.withType<Jar> {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.platform.spigot;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
|
||||
@JsonIgnore
|
||||
private Path floodgateKeyPath;
|
||||
|
||||
public void loadFloodgate(GeyserSpigotPlugin plugin) {
|
||||
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
|
||||
Path geyserDataFolder = plugin.getDataFolder().toPath();
|
||||
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
||||
|
||||
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.platform.spigot;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@@ -42,7 +43,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
|
||||
private final String platformAPIVersion;
|
||||
private final boolean onlineMode;
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonAdapter(value = AsteriskSerializer.class)
|
||||
private final String serverIP;
|
||||
private final int serverPort;
|
||||
private final List<PluginInfo> plugins;
|
||||
|
||||
@@ -25,11 +25,13 @@
|
||||
|
||||
package org.geysermc.geyser.platform.spigot;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
|
||||
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -38,6 +40,8 @@ import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.network.netty.GeyserInjector;
|
||||
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
|
||||
import org.geysermc.geyser.network.netty.LocalSession;
|
||||
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
|
||||
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -123,7 +127,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
|
||||
int index = ch.pipeline().names().indexOf("encoder");
|
||||
String baseName = index != -1 ? "encoder" : "outbound_config";
|
||||
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
|
||||
if (bootstrap.config().advanced().java().disableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
|
||||
ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
|
||||
}
|
||||
}
|
||||
@@ -158,7 +162,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
|
||||
}
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
if (bootstrap.getGeyserConfig().isDebugMode()) {
|
||||
if (bootstrap.config().debugMode()) {
|
||||
bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -178,8 +182,8 @@ public class GeyserSpigotInjector extends GeyserInjector {
|
||||
private void workAroundWeirdBug(GeyserBootstrap bootstrap) {
|
||||
MinecraftProtocol protocol = new MinecraftProtocol();
|
||||
LocalSession session = new LocalSession(this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, Runnable::run);
|
||||
session.setFlag(MinecraftConstants.CLIENT_HOST, bootstrap.getGeyserConfig().getRemote().address());
|
||||
session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.getGeyserConfig().getRemote().port());
|
||||
session.setFlag(MinecraftConstants.CLIENT_HOST, bootstrap.config().java().address());
|
||||
session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.config().java().port());
|
||||
session.connect();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,11 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.adapters.paper.PaperAdapters;
|
||||
@@ -50,7 +52,7 @@ import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.CommandSourceConverter;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
@@ -64,23 +66,20 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativ
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
import org.incendo.cloud.bukkit.BukkitCommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
import org.incendo.cloud.paper.LegacyPaperCommandManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserSpigotConfiguration geyserConfig;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserSpigotInjector geyserInjector;
|
||||
private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
|
||||
new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger());
|
||||
@@ -161,16 +160,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
// We'll disable ourselves later
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this);
|
||||
this.geyser = GeyserImpl.load(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,16 +225,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
// Configs are loaded once early - so we can create the logger, then load extensions and finally register
|
||||
// extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
}
|
||||
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
if (!geyserConfig.motd().integratedPingPassthrough()) {
|
||||
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
if (ReflectedNames.checkPaperPingEvent()) {
|
||||
@@ -289,7 +288,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
}
|
||||
geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName());
|
||||
} catch (Throwable e) {
|
||||
if (geyserConfig.isDebugMode()) {
|
||||
if (geyserConfig.debugMode()) {
|
||||
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -363,8 +362,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserSpigotConfiguration getGeyserConfig() {
|
||||
return geyserConfig;
|
||||
public @NonNull PlatformType platformType() {
|
||||
return PlatformType.SPIGOT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserPluginConfig config() {
|
||||
return this.geyserConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -455,32 +459,21 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
|
||||
@Override
|
||||
public boolean testFloodgatePluginPresent() {
|
||||
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) {
|
||||
geyserConfig.loadFloodgate(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Bukkit.getPluginManager().getPlugin("floodgate") != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
|
||||
try {
|
||||
if (!getDataFolder().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return false;
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
|
||||
Path geyserDataFolder = getDataFolder().toPath();
|
||||
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
||||
|
||||
return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger);
|
||||
}
|
||||
|
||||
return true;
|
||||
@Override
|
||||
public MetricsPlatform createMetricsPlatform() {
|
||||
return new SpigotMetrics(this);
|
||||
}
|
||||
|
||||
private void warnInvalidProxySetups(String platform) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class GeyserSpigotUpdateListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(final PlayerJoinEvent event) {
|
||||
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
|
||||
if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
|
||||
final Player player = event.getPlayer();
|
||||
if (player.hasPermission(Permissions.CHECK_UPDATE)) {
|
||||
VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player));
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.platform.spigot;
|
||||
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class SpigotMetrics implements MetricsPlatform {
|
||||
private final YamlConfiguration config;
|
||||
|
||||
public SpigotMetrics(Plugin plugin) {
|
||||
// https://github.com/Bastian/bstats-metrics/blob/master/bukkit/src/main/java/org/bstats/bukkit/Metrics.java
|
||||
// Get the config file
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
File configFile = new File(bStatsFolder, "config.yml");
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
|
||||
if (!config.isSet("serverUuid")) {
|
||||
config.addDefault("enabled", true);
|
||||
config.addDefault("serverUuid", UUID.randomUUID().toString());
|
||||
config.addDefault("logFailedRequests", false);
|
||||
config.addDefault("logSentData", false);
|
||||
config.addDefault("logResponseStatusText", false);
|
||||
|
||||
// Inform the server owners about bStats
|
||||
config.options().header(
|
||||
"bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" +
|
||||
"many people use their plugin and their total player count. It's recommended to keep bStats\n" +
|
||||
"enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" +
|
||||
"performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" +
|
||||
"anonymous."
|
||||
).copyDefaults(true);
|
||||
try {
|
||||
config.save(configFile);
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return config.getBoolean("enabled", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serverUuid() {
|
||||
return config.getString("serverUuid");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logFailedRequests() {
|
||||
return config.getBoolean("logFailedRequests", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logSentData() {
|
||||
return config.getBoolean("logSentData", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logResponseStatusText() {
|
||||
return config.getBoolean("logResponseStatusText", false);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ dependencies {
|
||||
|
||||
implementation(libs.bundles.jline)
|
||||
implementation(libs.bundles.log4j)
|
||||
|
||||
implementation(libs.gson.runtime)
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -25,11 +25,6 @@
|
||||
|
||||
package org.geysermc.geyser.platform.standalone;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
|
||||
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import lombok.Getter;
|
||||
import org.apache.logging.log4j.Level;
|
||||
@@ -41,35 +36,32 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
import org.geysermc.geyser.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.configuration.GeyserRemoteConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
||||
private StandaloneCloudCommandManager cloud;
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserStandaloneConfiguration geyserConfig;
|
||||
private GeyserConfig geyserConfig;
|
||||
private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
private GeyserStandaloneGUI gui;
|
||||
@@ -80,9 +72,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
||||
private GeyserImpl geyser;
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private static final Map<String, String> argsConfigKeys = new HashMap<>();
|
||||
private static final Map<NodePath, String> argsConfigKeys = new HashMap<>();
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (System.getProperty("io.netty.leakDetection.level") == null) {
|
||||
@@ -99,8 +89,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
||||
GeyserLocale.init(bootstrap);
|
||||
|
||||
List<BeanPropertyDefinition> availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class);
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
// By default, standalone Geyser will check if it should open the GUI based on if the GUI is null
|
||||
// Optionally, you can force the use of a GUI or no GUI by specifying args
|
||||
@@ -132,36 +120,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
// Split the argument by an =
|
||||
String[] argParts = arg.substring(2).split("=");
|
||||
if (argParts.length == 2) {
|
||||
// Split the config key by . to allow for nested options
|
||||
String[] configKeyParts = argParts[0].split("\\.");
|
||||
|
||||
// Loop the possible config options to check the passed key is valid
|
||||
boolean found = false;
|
||||
for (BeanPropertyDefinition property : availableProperties) {
|
||||
if (configKeyParts[0].equals(property.getName())) {
|
||||
if (configKeyParts.length > 1) {
|
||||
// Loop sub-section options to check the passed key is valid
|
||||
for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) {
|
||||
if (configKeyParts[1].equals(subProperty.getName())) {
|
||||
found = true;
|
||||
argsConfigKeys.put(NodePath.of(argParts[0].split("\\.")), argParts[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
found = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the found key to the stored list for later usage
|
||||
if (found) {
|
||||
argsConfigKeys.put(argParts[0], argParts[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg));
|
||||
return;
|
||||
}
|
||||
@@ -189,19 +151,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
||||
@Override
|
||||
public void onGeyserEnable() {
|
||||
try {
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class);
|
||||
|
||||
handleArgsConfigOptions();
|
||||
|
||||
if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) {
|
||||
geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug
|
||||
geyserConfig.getRemote().setAddress("127.0.0.1");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
this.geyserConfig = loadConfig(GeyserRemoteConfig.class);
|
||||
if (this.geyserConfig == null) {
|
||||
if (gui == null) {
|
||||
System.exit(1);
|
||||
} else {
|
||||
@@ -209,13 +160,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
return;
|
||||
}
|
||||
}
|
||||
geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Allow libraries like Protocol to have their debug information passthrough
|
||||
log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
|
||||
log4jLogger.get().setLevel(geyserConfig.debugMode() ? Level.DEBUG : Level.INFO);
|
||||
|
||||
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
|
||||
geyser = GeyserImpl.load(this);
|
||||
|
||||
boolean reloading = geyser.isReloading();
|
||||
if (!reloading) {
|
||||
@@ -245,6 +194,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
geyserLogger.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GeyserConfig> T loadConfig(Class<T> configClass) {
|
||||
return new ConfigLoader(this)
|
||||
.configFile(new File(getConfigFolder().toFile(), configFilename))
|
||||
.transformer(this::handleArgsConfigOptions)
|
||||
.load(configClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check using {@link java.awt.GraphicsEnvironment} that we are a headless client
|
||||
*
|
||||
@@ -273,8 +230,13 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserConfiguration getGeyserConfig() {
|
||||
return geyserConfig;
|
||||
public @NonNull PlatformType platformType() {
|
||||
return PlatformType.STANDALONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserConfig config() {
|
||||
return this.geyserConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -330,100 +292,47 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link BeanPropertyDefinition}s for the given class
|
||||
*
|
||||
* @param clazz The class to get the definitions for
|
||||
* @return A list of {@link BeanPropertyDefinition} for the given class
|
||||
*/
|
||||
public static List<BeanPropertyDefinition> getPOJOForClass(Class<?> clazz) {
|
||||
JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz);
|
||||
|
||||
// Introspect the given type
|
||||
BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType);
|
||||
|
||||
// Find properties
|
||||
List<BeanPropertyDefinition> properties = beanDescription.findProperties();
|
||||
|
||||
// Get the ignored properties
|
||||
Set<String> ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector()
|
||||
.findPropertyIgnoralByName(OBJECT_MAPPER.getSerializationConfig(), beanDescription.getClassInfo()).getIgnored();
|
||||
|
||||
// Filter properties removing the ignored ones
|
||||
return properties.stream()
|
||||
.filter(property -> !ignoredProperties.contains(property.getName()))
|
||||
.collect(Collectors.toList());
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
return Path.of(geyserConfig.advanced().floodgateKeyFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a POJO property value on an object
|
||||
*
|
||||
* @param property The {@link BeanPropertyDefinition} to set
|
||||
* @param parentObject The object to alter
|
||||
* @param value The new value of the property
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"}) // Required for enum usage
|
||||
private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) {
|
||||
private static void setConfigOption(CommentedConfigurationNode node, Object value) throws SerializationException {
|
||||
Object parsedValue = value;
|
||||
|
||||
// Change the values type if needed
|
||||
if (int.class.equals(property.getRawPrimaryType())) {
|
||||
Class<?> clazz = node.raw().getClass();
|
||||
if (Integer.class == clazz) {
|
||||
parsedValue = Integer.valueOf((String) parsedValue);
|
||||
} else if (boolean.class.equals(property.getRawPrimaryType())) {
|
||||
} else if (Boolean.class == clazz) {
|
||||
parsedValue = Boolean.valueOf((String) parsedValue);
|
||||
} else if (Enum.class.isAssignableFrom(property.getRawPrimaryType())) {
|
||||
parsedValue = Enum.valueOf((Class<? extends Enum>) property.getRawPrimaryType(), ((String) parsedValue).toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
// Force the value to be set
|
||||
AnnotatedField field = property.getField();
|
||||
field.fixAccess(true);
|
||||
field.setValue(parentObject, parsedValue);
|
||||
node.set(parsedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loaded {@link GeyserStandaloneConfiguration} with any values passed in the command line arguments
|
||||
* Update the loaded config with any values passed in the command line arguments
|
||||
*/
|
||||
private void handleArgsConfigOptions() {
|
||||
// Get the available properties from the class
|
||||
List<BeanPropertyDefinition> availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class);
|
||||
private void handleArgsConfigOptions(CommentedConfigurationNode node) {
|
||||
for (Map.Entry<NodePath, String> configKey : argsConfigKeys.entrySet()) {
|
||||
NodePath path = configKey.getKey();
|
||||
CommentedConfigurationNode subNode = node.node(path);
|
||||
if (subNode.virtual()) {
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", path));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> configKey : argsConfigKeys.entrySet()) {
|
||||
String[] configKeyParts = configKey.getKey().split("\\.");
|
||||
|
||||
// Loop over the properties looking for any matches against the stored one from the argument
|
||||
for (BeanPropertyDefinition property : availableProperties) {
|
||||
if (configKeyParts[0].equals(property.getName())) {
|
||||
if (configKeyParts.length > 1) {
|
||||
// Loop through the sub property if the first part matches
|
||||
for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) {
|
||||
if (configKeyParts[1].equals(subProperty.getName())) {
|
||||
geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue()));
|
||||
|
||||
// Set the sub property value on the config
|
||||
try {
|
||||
Object subConfig = property.getGetter().callOn(geyserConfig);
|
||||
setConfigOption(subProperty, subConfig, configKey.getValue());
|
||||
} catch (Exception e) {
|
||||
geyserLogger.error("Failed to set config option: " + property.getFullName());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setConfigOption(subNode, configKey.getValue());
|
||||
geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue()));
|
||||
|
||||
// Set the property value on the config
|
||||
try {
|
||||
setConfigOption(property, geyserConfig, configKey.getValue());
|
||||
} catch (Exception e) {
|
||||
geyserLogger.error("Failed to set config option: " + property.getFullName());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (SerializationException e) {
|
||||
geyserLogger.error("Failed to set config option: " + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.apache.logging.log4j.io.IoBuilder;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
@@ -112,7 +113,12 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
log.debug(ChatColor.GRAY + message);
|
||||
log.debug(ChatColor.GRAY + "{}", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(@Nullable Object object) {
|
||||
log.debug("{}", object);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,12 +16,13 @@ dependencies {
|
||||
api(libs.cloud.velocity)
|
||||
}
|
||||
|
||||
platformRelocate("com.fasterxml.jackson")
|
||||
platformRelocate("it.unimi.dsi.fastutil")
|
||||
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
|
||||
platformRelocate("org.yaml")
|
||||
platformRelocate("org.spongepowered")
|
||||
platformRelocate("org.bstats")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.velocity.api)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.platform.velocity;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import lombok.Getter;
|
||||
@@ -42,7 +43,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo {
|
||||
private final String platformVendor;
|
||||
private final boolean onlineMode;
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonAdapter(value = AsteriskSerializer.class)
|
||||
private final String serverIP;
|
||||
private final int serverPort;
|
||||
private final List<PluginInfo> plugins;
|
||||
|
||||
@@ -128,7 +128,7 @@ public class GeyserVelocityInjector extends GeyserInjector {
|
||||
protected void initChannel(@NonNull Channel ch) throws Exception {
|
||||
initChannel.invoke(channelInitializer, ch);
|
||||
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserVelocityCompressionDisabler.ENABLED) {
|
||||
if (bootstrap.config().advanced().java().disableCompression() && GeyserVelocityCompressionDisabler.ENABLED) {
|
||||
ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler",
|
||||
new GeyserVelocityCompressionDisabler());
|
||||
}
|
||||
|
||||
@@ -39,31 +39,31 @@ import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.CommandSourceConverter;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
import org.incendo.cloud.velocity.VelocityCommandManager;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
import java.util.Optional;
|
||||
|
||||
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
|
||||
public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
@@ -71,7 +71,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
private final ProxyServer proxyServer;
|
||||
private final PluginContainer container;
|
||||
private final GeyserVelocityLogger geyserLogger;
|
||||
private GeyserVelocityConfiguration geyserConfig;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserVelocityInjector geyserInjector;
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
private CommandRegistry commandRegistry;
|
||||
@@ -106,13 +106,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
System.setProperty("Mcpl.io_uring", "true");
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this);
|
||||
this.geyser = GeyserImpl.load(this);
|
||||
this.geyserInjector = new GeyserVelocityInjector(proxyServer);
|
||||
|
||||
// We need to register commands here, rather than in onGeyserEnable which is invoked during the appropriate ListenerBoundEvent.
|
||||
@@ -139,16 +138,15 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
return;
|
||||
}
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
}
|
||||
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
if (!geyserConfig.motd().integratedPingPassthrough()) {
|
||||
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
|
||||
@@ -178,7 +176,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserVelocityConfiguration getGeyserConfig() {
|
||||
public @NonNull PlatformType platformType() {
|
||||
return PlatformType.VELOCITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserPluginConfig config() {
|
||||
return geyserConfig;
|
||||
}
|
||||
|
||||
@@ -250,27 +253,26 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
@Override
|
||||
public boolean testFloodgatePluginPresent() {
|
||||
var floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
|
||||
if (floodgate.isPresent()) {
|
||||
geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return floodgate.isPresent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
Optional<PluginContainer> floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
|
||||
Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null;
|
||||
return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataPath, configFolder, geyserLogger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsPlatform createMetricsPlatform() {
|
||||
try {
|
||||
if (!configFolder.toFile().exists())
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
configFolder.toFile().mkdirs();
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
return new VelocityMetrics(this.configFolder);
|
||||
} catch (IOException e) {
|
||||
this.geyserLogger.debug("Integrated bStats support failed to load.");
|
||||
if (this.config().debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class GeyserVelocityUpdateListener {
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerJoin(PostLoginEvent event) {
|
||||
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
|
||||
if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
|
||||
final Player player = event.getPlayer();
|
||||
if (player.hasPermission(Permissions.CHECK_UPDATE)) {
|
||||
VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,28 +25,45 @@
|
||||
|
||||
package org.geysermc.geyser.platform.velocity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
import org.bstats.config.MetricsConfig;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration {
|
||||
@JsonIgnore
|
||||
private Path floodgateKeyPath;
|
||||
public final class VelocityMetrics implements MetricsPlatform {
|
||||
private final MetricsConfig config;
|
||||
|
||||
public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) {
|
||||
Optional<PluginContainer> floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
|
||||
Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null;
|
||||
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataPath, dataFolder.toPath(), plugin.getGeyserLogger());
|
||||
public VelocityMetrics(Path dataDirectory) throws IOException {
|
||||
// https://github.com/Bastian/bstats-metrics/blob/master/velocity/src/main/java/org/bstats/velocity/Metrics.java
|
||||
File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile();
|
||||
this.config = new MetricsConfig(configFile, true);
|
||||
// No logger message is implemented as Velocity should print its own before we do.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return config.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serverUuid() {
|
||||
return config.getServerUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logFailedRequests() {
|
||||
return config.isLogErrorsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logSentData() {
|
||||
return config.isLogSentDataEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logResponseStatusText() {
|
||||
return config.isLogResponseStatusTextEnabled();
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,9 @@ platformRelocate("net.kyori")
|
||||
platformRelocate("org.yaml")
|
||||
platformRelocate("it.unimi.dsi.fastutil")
|
||||
platformRelocate("org.cloudburstmc.netty")
|
||||
platformRelocate("org.bstats")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.viaproxy)
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
package org.geysermc.geyser.platform.viaproxy;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import net.raphimc.vialegacy.api.LegacyProtocolVersion;
|
||||
import net.raphimc.viaproxy.ViaProxy;
|
||||
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
public class GeyserViaProxyConfiguration extends GeyserJacksonConfiguration {
|
||||
|
||||
private RemoteConfiguration remote = new RemoteConfiguration() {
|
||||
@Override
|
||||
public boolean isForwardHost() {
|
||||
return super.isForwardHost() || !ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
return new File(GeyserViaProxyPlugin.ROOT_FOLDER, this.getFloodgateKeyFile()).toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPingPassthroughInterval() {
|
||||
int interval = super.getPingPassthroughInterval();
|
||||
if (interval < 15 && ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) {
|
||||
// <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made
|
||||
interval = 15;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteConfiguration getRemote() {
|
||||
return this.remote;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package org.geysermc.geyser.platform.viaproxy;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.Getter;
|
||||
import net.raphimc.viaproxy.ViaProxy;
|
||||
import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
|
||||
@@ -41,7 +42,7 @@ public class GeyserViaProxyDumpInfo extends BootstrapDumpInfo {
|
||||
private final String platformVersion;
|
||||
private final boolean onlineMode;
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonAdapter(value = AsteriskSerializer.class)
|
||||
private final String serverIP;
|
||||
private final int serverPort;
|
||||
private final List<PluginInfo> plugins;
|
||||
|
||||
@@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.viaproxy;
|
||||
|
||||
import net.raphimc.viaproxy.cli.ConsoleFormatter;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
||||
@@ -75,6 +76,13 @@ public class GeyserViaProxyLogger implements GeyserLogger, GeyserCommandSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(@Nullable Object object) {
|
||||
if (this.debug) {
|
||||
this.logger.debug(ConsoleFormatter.convert(String.valueOf(object)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Object... arguments) {
|
||||
if (this.debug) {
|
||||
|
||||
@@ -36,7 +36,9 @@ import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent;
|
||||
import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
|
||||
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
|
||||
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
|
||||
import net.raphimc.viaproxy.plugins.events.ViaProxyLoadedEvent;
|
||||
import net.raphimc.viaproxy.plugins.events.types.ITyped;
|
||||
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
@@ -47,18 +49,19 @@ import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListener;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.file.Files;
|
||||
@@ -67,10 +70,10 @@ import java.util.UUID;
|
||||
|
||||
public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap, EventRegistrar {
|
||||
|
||||
public static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser");
|
||||
private static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser");
|
||||
|
||||
private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser"));
|
||||
private GeyserViaProxyConfiguration config;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserImpl geyser;
|
||||
private StandaloneCloudCommandManager cloud;
|
||||
private CommandRegistry commandRegistry;
|
||||
@@ -79,10 +82,6 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
@Override
|
||||
public void onEnable() {
|
||||
ROOT_FOLDER.mkdirs();
|
||||
|
||||
GeyserLocale.init(this);
|
||||
this.onGeyserInitialize();
|
||||
|
||||
ViaProxy.EVENT_MANAGER.register(this);
|
||||
}
|
||||
|
||||
@@ -91,6 +90,12 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
this.onGeyserShutdown();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
private void onViaProxyLoaded(ViaProxyLoadedEvent event) {
|
||||
GeyserLocale.init(this);
|
||||
this.onGeyserInitialize();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
private void onConsoleCommand(final ConsoleCommandEvent event) {
|
||||
final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand();
|
||||
@@ -119,6 +124,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
if (event.getType() != ITyped.Type.POST || event.isLegacyPassthrough()) {
|
||||
return;
|
||||
}
|
||||
// TODO remove
|
||||
if (System.getProperty("geyser.viaproxy.disableIpPassthrough") != null) { // Temporary until Configurate branch is merged
|
||||
return;
|
||||
}
|
||||
@@ -147,11 +153,12 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
|
||||
@Override
|
||||
public void onGeyserInitialize() {
|
||||
if (!this.loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.VIAPROXY, this);
|
||||
this.geyser = GeyserImpl.load(this);
|
||||
this.geyser.eventBus().register(this, new GeyserServerTransferListener());
|
||||
LoopbackUtil.checkAndApplyLoopback(this.logger);
|
||||
}
|
||||
@@ -164,7 +171,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
}
|
||||
boolean reloading = geyser.isReloading();
|
||||
if (reloading) {
|
||||
if (!this.loadConfig()) {
|
||||
geyserConfig = loadConfig(GeyserPluginConfig.class);
|
||||
if (geyserConfig == null) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -185,7 +193,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
// Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added
|
||||
this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser);
|
||||
}
|
||||
if (this.config.getRemote().authType() == AuthType.FLOODGATE) {
|
||||
if (this.geyserConfig.java().authType() == AuthType.FLOODGATE) {
|
||||
ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true);
|
||||
}
|
||||
}
|
||||
@@ -201,8 +209,13 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserConfiguration getGeyserConfig() {
|
||||
return this.config;
|
||||
public @NonNull PlatformType platformType() {
|
||||
return PlatformType.VIAPROXY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserPluginConfig config() {
|
||||
return this.geyserConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,19 +272,36 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.config = FileUtils.loadConfig(configFile, GeyserViaProxyConfiguration.class);
|
||||
} catch (IOException e) {
|
||||
this.logger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e);
|
||||
return false;
|
||||
}
|
||||
this.config.getRemote().setAuthType(Files.isRegularFile(this.config.getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE);
|
||||
this.logger.setDebug(this.config.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(this.config, this.logger);
|
||||
return true;
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
return new File(ROOT_FOLDER, geyserConfig.advanced().floodgateKeyFile()).toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GeyserConfig> T loadConfig(Class<T> configClass) {
|
||||
T config = new ConfigLoader(this)
|
||||
.transformer(node -> {
|
||||
try {
|
||||
if (!ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE)) {
|
||||
node.node("java", "forward-host").set(true);
|
||||
}
|
||||
|
||||
var pingPassthroughInterval = node.node("ping-passthrough-interval");
|
||||
int interval = pingPassthroughInterval.getInt();
|
||||
if (interval < 15 && ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) {
|
||||
// <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made
|
||||
pingPassthroughInterval.set(15);
|
||||
}
|
||||
} catch (SerializationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.configFile(new File(ROOT_FOLDER, "config.yml"))
|
||||
.load(configClass);
|
||||
if (config != null) {
|
||||
this.geyserConfig = (GeyserPluginConfig) config;
|
||||
config.java().authType(Files.isRegularFile(getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,29 @@ dependencies {
|
||||
api(projects.common)
|
||||
api(projects.api)
|
||||
|
||||
// Jackson JSON and YAML serialization
|
||||
api(libs.bundles.jackson)
|
||||
api(libs.yaml) // Used for extensions
|
||||
annotationProcessor(libs.configurate.`interface`.ap)
|
||||
api(libs.configurate.`interface`)
|
||||
implementation(libs.configurate.yaml)
|
||||
api(libs.guava)
|
||||
|
||||
compileOnly(libs.gson.record.factory) {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
// Fastutil Maps
|
||||
implementation(libs.bundles.fastutil)
|
||||
|
||||
// Network libraries
|
||||
implementation(libs.websocket)
|
||||
|
||||
api(libs.bundles.protocol)
|
||||
api(libs.bundles.protocol) {
|
||||
exclude("com.fasterxml.jackson.core", "jackson-annotations")
|
||||
}
|
||||
|
||||
api(libs.minecraftauth)
|
||||
api(libs.minecraftauth) {
|
||||
exclude("com.google.code.gson", "gson")
|
||||
}
|
||||
api(libs.mcprotocollib) {
|
||||
exclude("io.netty", "netty-all")
|
||||
exclude("net.raphimc", "MinecraftAuth")
|
||||
@@ -60,6 +70,7 @@ dependencies {
|
||||
|
||||
// Test
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.gson.runtime) // Record support
|
||||
testImplementation(libs.mockito)
|
||||
|
||||
// Annotation Processors
|
||||
@@ -68,6 +79,8 @@ dependencies {
|
||||
annotationProcessor(projects.ap)
|
||||
|
||||
api(libs.events)
|
||||
|
||||
api(libs.bstats)
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
|
||||
@@ -42,6 +42,10 @@ public final class Constants {
|
||||
|
||||
public static final String MINECRAFT_SKIN_SERVER_URL = "https://textures.minecraft.net/texture/";
|
||||
|
||||
public static final int CONFIG_VERSION = 5;
|
||||
|
||||
public static final int BSTATS_ID = 5273;
|
||||
|
||||
static {
|
||||
URI wsUri = null;
|
||||
try {
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
|
||||
package org.geysermc.geyser;
|
||||
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FloodgateKeyLoader {
|
||||
public static Path getKeyPath(GeyserJacksonConfiguration config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) {
|
||||
public static Path getKeyPath(GeyserConfig config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) {
|
||||
// Always prioritize Floodgate's key, if it is installed.
|
||||
// This mostly prevents people from trying to copy the key and corrupting it in the process
|
||||
if (floodgateDataFolder != null) {
|
||||
@@ -45,13 +45,7 @@ public class FloodgateKeyLoader {
|
||||
}
|
||||
}
|
||||
|
||||
Path floodgateKey;
|
||||
if (config.getFloodgateKeyFile().equals("public-key.pem")) {
|
||||
logger.debug("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead");
|
||||
floodgateKey = geyserDataFolder.resolve("key.pem");
|
||||
} else {
|
||||
floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile());
|
||||
}
|
||||
Path floodgateKey = geyserDataFolder.resolve(config.advanced().floodgateKeyFile());
|
||||
|
||||
if (!Files.exists(floodgateKey)) {
|
||||
logger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed"));
|
||||
|
||||
@@ -27,12 +27,16 @@ package org.geysermc.geyser;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.level.GeyserWorldManager;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
import org.geysermc.geyser.util.metrics.ProvidedMetricsPlatform;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketAddress;
|
||||
@@ -68,11 +72,19 @@ public interface GeyserBootstrap {
|
||||
void onGeyserShutdown();
|
||||
|
||||
/**
|
||||
* Returns the current GeyserConfiguration
|
||||
* Returns the platform type this Geyser instance is running on.
|
||||
*
|
||||
* @return The current GeyserConfiguration
|
||||
* @return the PlatformType this Geyser instance is running on.
|
||||
*/
|
||||
GeyserConfiguration getGeyserConfig();
|
||||
@NonNull
|
||||
PlatformType platformType();
|
||||
|
||||
/**
|
||||
* Returns the current GeyserConfig
|
||||
*
|
||||
* @return The current GeyserConfig
|
||||
*/
|
||||
GeyserConfig config();
|
||||
|
||||
/**
|
||||
* Returns the current GeyserLogger
|
||||
@@ -194,4 +206,18 @@ public interface GeyserBootstrap {
|
||||
* Tests if Floodgate is installed, loads the Floodgate key if so, and returns the result of Floodgate installed.
|
||||
*/
|
||||
boolean testFloodgatePluginPresent();
|
||||
|
||||
/**
|
||||
* TEMPORARY - will be removed after The Merge:tm:.
|
||||
*/
|
||||
Path getFloodgateKeyPath();
|
||||
|
||||
@Nullable
|
||||
default MetricsPlatform createMetricsPlatform() {
|
||||
return new ProvidedMetricsPlatform();
|
||||
}
|
||||
|
||||
default <T extends GeyserConfig> T loadConfig(Class<T> configClass) {
|
||||
return new ConfigLoader(this).createFolder().load(configClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.util.NettyRuntime;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
@@ -40,6 +38,11 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.raphimc.minecraftauth.msa.data.MsaConstants;
|
||||
import net.raphimc.minecraftauth.msa.model.MsaApplicationConfig;
|
||||
import org.bstats.MetricsBase;
|
||||
import org.bstats.charts.AdvancedPie;
|
||||
import org.bstats.charts.DrilldownPie;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@@ -68,7 +71,8 @@ import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.erosion.UnixSocketClientListener;
|
||||
import org.geysermc.geyser.event.GeyserEventBus;
|
||||
@@ -79,6 +83,7 @@ import org.geysermc.geyser.level.BedrockDimension;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.network.netty.GeyserServer;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
@@ -97,15 +102,17 @@ import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.AssetUtils;
|
||||
import org.geysermc.geyser.util.CodeOfConductManager;
|
||||
import org.geysermc.geyser.util.CooldownUtils;
|
||||
import org.geysermc.geyser.util.Metrics;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
import org.geysermc.geyser.util.NewsHandler;
|
||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.geysermc.geyser.util.metrics.MetricsPlatform;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -129,12 +136,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
public static final ObjectMapper JSON_MAPPER = new ObjectMapper()
|
||||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
|
||||
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
|
||||
public static final Gson GSON = JsonUtils.createGson();
|
||||
|
||||
public static final String NAME = "Geyser";
|
||||
public static final String GIT_VERSION = BuildData.GIT_VERSION;
|
||||
@@ -167,13 +169,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
private ScheduledExecutorService scheduledThread;
|
||||
|
||||
private GeyserServer geyserServer;
|
||||
private final PlatformType platformType;
|
||||
private final GeyserBootstrap bootstrap;
|
||||
|
||||
private final GeyserEventBus eventBus;
|
||||
private final GeyserExtensionManager extensionManager;
|
||||
|
||||
private Metrics metrics;
|
||||
private MetricsBase metrics;
|
||||
|
||||
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@@ -194,12 +195,11 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
@Setter
|
||||
private boolean isEnabled;
|
||||
|
||||
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
|
||||
private GeyserImpl(GeyserBootstrap bootstrap) {
|
||||
instance = this;
|
||||
|
||||
Geyser.set(this);
|
||||
|
||||
this.platformType = platformType;
|
||||
this.bootstrap = bootstrap;
|
||||
|
||||
/* Initialize event bus */
|
||||
@@ -218,10 +218,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
|
||||
public void initialize() {
|
||||
// Setup encryption early so we don't start if we can't auth
|
||||
if (config().advanced().bedrock().validateBedrockLogin()) {
|
||||
try {
|
||||
EncryptionUtils.getMojangPublicKey();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Cannot setup authentication! Are you offline? ", e);
|
||||
} catch (Throwable t) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to set up encryption! This can be caused by your internet connection or the Minecraft api being unreachable. ", t);
|
||||
}
|
||||
}
|
||||
|
||||
long startupTime = System.currentTimeMillis();
|
||||
@@ -278,19 +280,19 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
|
||||
startInstance();
|
||||
|
||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||
GeyserConfig config = bootstrap.config();
|
||||
|
||||
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;
|
||||
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime));
|
||||
message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
|
||||
logger.info(message);
|
||||
|
||||
if (platformType == PlatformType.STANDALONE) {
|
||||
if (config.getRemote().authType() != AuthType.FLOODGATE) {
|
||||
if (platformType() == PlatformType.STANDALONE) {
|
||||
if (config.java().authType() != AuthType.FLOODGATE) {
|
||||
// If the auth-type is Floodgate, then this Geyser instance is probably owned by the Java server
|
||||
logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn"));
|
||||
}
|
||||
} else if (config.getRemote().authType() == AuthType.FLOODGATE) {
|
||||
} else if (config.java().authType() == AuthType.FLOODGATE) {
|
||||
VersionCheckUtils.checkForOutdatedFloodgate(logger);
|
||||
}
|
||||
|
||||
@@ -308,7 +310,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
|
||||
GeyserLogger logger = bootstrap.getGeyserLogger();
|
||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||
GeyserConfig config = bootstrap.config();
|
||||
|
||||
ScoreboardUpdater.init();
|
||||
|
||||
@@ -316,6 +318,23 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
|
||||
Registries.RESOURCE_PACKS.load();
|
||||
|
||||
// Warnings to users who enable options that they might not need.
|
||||
if (config.advanced().bedrock().useHaproxyProtocol()) {
|
||||
logger.warning("Geyser is configured to expect HAProxy protocol for incoming Bedrock connections.");
|
||||
logger.warning("If you do not know what this is, open the Geyser config, and set \"use-haproxy-protocol\" under the \"advanced/bedrock\" section to \"false\".");
|
||||
}
|
||||
|
||||
if (config.advanced().java().useHaproxyProtocol()) {
|
||||
logger.warning("Geyser is configured to use proxy protocol when connecting to the Java server.");
|
||||
logger.warning("If you do not know what this is, open the Geyser config, and set \"use-haproxy-protocol\" under the \"advanced/java\" section to \"false\".");
|
||||
}
|
||||
|
||||
if (!config.advanced().bedrock().validateBedrockLogin()) {
|
||||
logger.error("XBOX AUTHENTICATION IS DISABLED ON THIS GEYSER INSTANCE!");
|
||||
logger.error("While this allows using Bedrock edition proxies, it also opens up the ability for hackers to connect with any username they choose.");
|
||||
logger.error("To change this, set \"disable-xbox-auth\" to \"false\" in Geyser's config file.");
|
||||
}
|
||||
|
||||
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
|
||||
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
|
||||
if ("-1".equals(pluginUdpPort)) {
|
||||
@@ -324,33 +343,30 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
boolean portPropertyApplied = false;
|
||||
String pluginUdpAddress = System.getProperty("geyserUdpAddress", System.getProperty("pluginUdpAddress", ""));
|
||||
|
||||
if (platformType != PlatformType.STANDALONE) {
|
||||
if (platformType() != PlatformType.STANDALONE) {
|
||||
int javaPort = bootstrap.getServerPort();
|
||||
if (config.getRemote().address().equals("auto")) {
|
||||
config.setAutoconfiguredRemote(true);
|
||||
String serverAddress = bootstrap.getServerBindAddress();
|
||||
if (!serverAddress.isEmpty() && !"0.0.0.0".equals(serverAddress)) {
|
||||
config.getRemote().setAddress(serverAddress);
|
||||
config.java().address(serverAddress);
|
||||
} else {
|
||||
// Set the remote address to localhost since that is where we are always connecting
|
||||
try {
|
||||
config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress());
|
||||
config.java().address(InetAddress.getLocalHost().getHostAddress());
|
||||
} catch (UnknownHostException ex) {
|
||||
logger.debug("Unknown host when trying to find localhost.");
|
||||
if (config.isDebugMode()) {
|
||||
if (config.debugMode()) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
config.getRemote().setAddress(InetAddress.getLoopbackAddress().getHostAddress());
|
||||
config.java().address(InetAddress.getLoopbackAddress().getHostAddress());
|
||||
}
|
||||
}
|
||||
if (javaPort != -1) {
|
||||
config.getRemote().setPort(javaPort);
|
||||
}
|
||||
config.java().port(javaPort);
|
||||
}
|
||||
|
||||
boolean forceMatchServerPort = "server".equals(pluginUdpPort);
|
||||
if ((config.getBedrock().isCloneRemotePort() || forceMatchServerPort) && javaPort != -1) {
|
||||
config.getBedrock().setPort(javaPort);
|
||||
if ((config.bedrock().cloneRemotePort() || forceMatchServerPort) && javaPort != -1) {
|
||||
config.bedrock().port(javaPort);
|
||||
if (forceMatchServerPort) {
|
||||
if (geyserUdpPort.isEmpty()) {
|
||||
logger.info("Port set from system generic property to match Java server.");
|
||||
@@ -364,15 +380,15 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
if ("server".equals(pluginUdpAddress)) {
|
||||
String address = bootstrap.getServerBindAddress();
|
||||
if (!address.isEmpty()) {
|
||||
config.getBedrock().setAddress(address);
|
||||
config.bedrock().address(address);
|
||||
}
|
||||
} else if (!pluginUdpAddress.isEmpty()) {
|
||||
config.getBedrock().setAddress(pluginUdpAddress);
|
||||
config.bedrock().address(pluginUdpAddress);
|
||||
}
|
||||
|
||||
if (!portPropertyApplied && !pluginUdpPort.isEmpty()) {
|
||||
int port = Integer.parseInt(pluginUdpPort);
|
||||
config.getBedrock().setPort(port);
|
||||
config.bedrock().port(port);
|
||||
if (geyserUdpPort.isEmpty()) {
|
||||
logger.info("Port set from generic system property: " + port);
|
||||
} else {
|
||||
@@ -380,16 +396,17 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
}
|
||||
|
||||
if (platformType != PlatformType.VIAPROXY) {
|
||||
|
||||
if (platformType() != PlatformType.VIAPROXY) {
|
||||
boolean floodgatePresent = bootstrap.testFloodgatePluginPresent();
|
||||
if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) {
|
||||
if (config.java().authType() == AuthType.FLOODGATE && !floodgatePresent) {
|
||||
logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " "
|
||||
+ GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
||||
return;
|
||||
} else if (config.isAutoconfiguredRemote() && floodgatePresent) {
|
||||
} else if (floodgatePresent) {
|
||||
// Floodgate installed means that the user wants Floodgate authentication
|
||||
logger.debug("Auto-setting to Floodgate authentication.");
|
||||
config.getRemote().setAuthType(AuthType.FLOODGATE);
|
||||
config.java().authType(AuthType.FLOODGATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +419,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
if (parsedPort < 1 || parsedPort > 65535) {
|
||||
throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!");
|
||||
}
|
||||
config.getBedrock().setBroadcastPort(parsedPort);
|
||||
config.advanced().bedrock().broadcastPort(parsedPort);
|
||||
logger.info("Broadcast port set from system property: " + parsedPort);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error(String.format("Invalid broadcast port from system property: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")"));
|
||||
@@ -410,23 +427,27 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
|
||||
// It's set to 0 only if no system property or manual config value was set
|
||||
if (config.getBedrock().broadcastPort() == 0) {
|
||||
config.getBedrock().setBroadcastPort(config.getBedrock().port());
|
||||
if (config.advanced().bedrock().broadcastPort() == 0) {
|
||||
config.advanced().bedrock().broadcastPort(config.bedrock().port());
|
||||
}
|
||||
|
||||
String remoteAddress = config.getRemote().address();
|
||||
if (!(config instanceof GeyserPluginConfig)) {
|
||||
String remoteAddress = config.java().address();
|
||||
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
|
||||
if (!IP_REGEX.matcher(remoteAddress).matches() && !remoteAddress.equalsIgnoreCase("localhost")) {
|
||||
String[] record = WebUtils.findSrvRecord(this, remoteAddress);
|
||||
if (record != null) {
|
||||
int remotePort = Integer.parseInt(record[2]);
|
||||
config.getRemote().setAddress(remoteAddress = record[3]);
|
||||
config.getRemote().setPort(remotePort);
|
||||
config.java().address(remoteAddress = record[3]);
|
||||
config.java().port(remotePort);
|
||||
logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\"");
|
||||
}
|
||||
}
|
||||
} else if (!config.advanced().java().useDirectConnection()) {
|
||||
logger.warning("The use-direct-connection config option is deprecated. Please reach out to us on Discord if there's a reason it needs to be disabled.");
|
||||
}
|
||||
|
||||
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout());
|
||||
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.pendingAuthenticationTimeout());
|
||||
|
||||
this.newsHandler = new NewsHandler(BRANCH, this.buildNumber());
|
||||
|
||||
@@ -438,20 +459,19 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
logger.debug("Epoll is not available; Erosion's Unix socket handling will not work.");
|
||||
}
|
||||
|
||||
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
||||
BedrockDimension.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
|
||||
BedrockDimension.changeBedrockNetherId(config.gameplay().netherRoofWorkaround()); // Apply End dimension ID workaround to Nether
|
||||
|
||||
Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads");
|
||||
if (bedrockThreadCount == null) {
|
||||
int bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads", -1);
|
||||
if (bedrockThreadCount == -1) {
|
||||
// Copy the code from Netty's default thread count fallback
|
||||
bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
|
||||
}
|
||||
|
||||
this.geyserServer = new GeyserServer(this, bedrockThreadCount);
|
||||
this.geyserServer.bind(new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port()))
|
||||
this.geyserServer.bind(new InetSocketAddress(config.bedrock().address(), config.bedrock().port()))
|
||||
.whenComplete((avoid, throwable) -> {
|
||||
String address = config.getBedrock().address();
|
||||
String port = String.valueOf(config.getBedrock().port()); // otherwise we get commas
|
||||
String address = config.bedrock().address();
|
||||
String port = String.valueOf(config.bedrock().port()); // otherwise we get commas
|
||||
|
||||
if (throwable == null) {
|
||||
if ("0.0.0.0".equals(address)) {
|
||||
@@ -469,9 +489,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
}).join();
|
||||
|
||||
if (config.getRemote().authType() == AuthType.FLOODGATE) {
|
||||
if (config.java().authType() == AuthType.FLOODGATE) {
|
||||
try {
|
||||
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath());
|
||||
Key key = new AesKeyProducer().produceFrom(bootstrap.getFloodgateKeyPath());
|
||||
cipher = new AesCipher(new Base64Topping());
|
||||
cipher.init(key);
|
||||
logger.debug("Loaded Floodgate key!");
|
||||
@@ -483,28 +503,55 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getMetrics().isEnabled()) {
|
||||
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger(""));
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size));
|
||||
MetricsPlatform metricsPlatform = bootstrap.createMetricsPlatform();
|
||||
if (metricsPlatform != null && metricsPlatform.enabled()) {
|
||||
metrics = new MetricsBase(
|
||||
"server-implementation",
|
||||
metricsPlatform.serverUuid(),
|
||||
Constants.BSTATS_ID,
|
||||
true, // Already checked above.
|
||||
builder -> {
|
||||
// OS specific data
|
||||
String osName = System.getProperty("os.name");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
builder.appendField("osName", osName);
|
||||
builder.appendField("osArch", osArch);
|
||||
builder.appendField("osVersion", osVersion);
|
||||
builder.appendField("coreCount", coreCount);
|
||||
},
|
||||
builder -> {},
|
||||
null,
|
||||
() -> true,
|
||||
logger::error,
|
||||
logger::info,
|
||||
metricsPlatform.logFailedRequests(),
|
||||
metricsPlatform.logSentData(),
|
||||
metricsPlatform.logResponseStatusText(),
|
||||
metricsPlatform.disableRelocateCheck()
|
||||
);
|
||||
metrics.addCustomChart(new SingleLineChart("players", sessionManager::size));
|
||||
// Prevent unwanted words best we can
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT)));
|
||||
metrics.addCustomChart(new SimplePie("authMode", () -> config.java().authType().toString().toLowerCase(Locale.ROOT)));
|
||||
|
||||
Map<String, Map<String, Integer>> platformTypeMap = new HashMap<>();
|
||||
Map<String, Integer> serverPlatform = new HashMap<>();
|
||||
serverPlatform.put(bootstrap.getServerPlatform(), 1);
|
||||
platformTypeMap.put(platformType().platformName(), serverPlatform);
|
||||
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("platform", () -> {
|
||||
metrics.addCustomChart(new DrilldownPie("platform", () -> {
|
||||
// By the end, we should return, for example:
|
||||
// Geyser-Spigot => (Paper, 1)
|
||||
return platformTypeMap;
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.getRemote().isUseProxyProtocol())));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.getBedrock().isEnableProxyProtocol())));
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> {
|
||||
metrics.addCustomChart(new SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
|
||||
metrics.addCustomChart(new SimplePie("version", () -> GeyserImpl.VERSION));
|
||||
metrics.addCustomChart(new SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.advanced().java().useHaproxyProtocol())));
|
||||
metrics.addCustomChart(new SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.advanced().bedrock().useHaproxyProtocol())));
|
||||
metrics.addCustomChart(new AdvancedPie("playerPlatform", () -> {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (GeyserSession session : sessionManager.getAllSessions()) {
|
||||
if (session == null) continue;
|
||||
@@ -518,7 +565,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
return valueMap;
|
||||
}));
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> {
|
||||
metrics.addCustomChart(new AdvancedPie("playerVersion", () -> {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (GeyserSession session : sessionManager.getAllSessions()) {
|
||||
if (session == null) continue;
|
||||
@@ -540,7 +587,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
platformMap.put(bootstrap.getServerPlatform(), 1);
|
||||
versionMap.put(minecraftVersion, platformMap);
|
||||
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> {
|
||||
metrics.addCustomChart(new DrilldownPie("minecraftServerVersion", () -> {
|
||||
// By the end, we should return, for example:
|
||||
// 1.16.5 => (Spigot, 1)
|
||||
return versionMap;
|
||||
@@ -549,7 +596,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
|
||||
// The following code can be attributed to the PaperMC project
|
||||
// https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("javaVersion", () -> {
|
||||
metrics.addCustomChart(new DrilldownPie("javaVersion", () -> {
|
||||
Map<String, Map<String, Integer>> map = new HashMap<>();
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
Map<String, Integer> entry = new HashMap<>();
|
||||
@@ -584,22 +631,21 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
metrics = null;
|
||||
}
|
||||
|
||||
if (config.getRemote().authType() == AuthType.ONLINE) {
|
||||
if (config.java().authType() == AuthType.ONLINE) {
|
||||
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
|
||||
savedAuthChains = new ConcurrentHashMap<>();
|
||||
Type type = new TypeToken<Map<String, String>>() { }.getType();
|
||||
|
||||
File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
|
||||
if (authChainsFile.exists()) {
|
||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||
|
||||
Map<String, String> authChainFile = null;
|
||||
try {
|
||||
authChainFile = JSON_MAPPER.readValue(authChainsFile, type);
|
||||
try (FileReader reader = new FileReader(authChainsFile)) {
|
||||
authChainFile = GSON.fromJson(reader, type);
|
||||
} catch (IOException e) {
|
||||
logger.error("Cannot load saved user tokens!", e);
|
||||
}
|
||||
if (authChainFile != null) {
|
||||
List<String> validUsers = config.getSavedUserLogins();
|
||||
List<String> validUsers = config.savedUserLogins();
|
||||
boolean doWrite = false;
|
||||
for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
|
||||
String user = entry.getKey();
|
||||
@@ -627,7 +673,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
|
||||
}
|
||||
|
||||
if (config.isNotifyOnNewBedrockUpdate()) {
|
||||
if (config.notifyOnNewBedrockUpdate()) {
|
||||
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
|
||||
}
|
||||
}
|
||||
@@ -703,6 +749,10 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
runIfNonNull(newsHandler, NewsHandler::shutdown);
|
||||
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
|
||||
|
||||
if (bootstrap.getGeyserPingPassthrough() instanceof GeyserLegacyPingPassthrough legacyPingPassthrough) {
|
||||
legacyPingPassthrough.interrupt();
|
||||
}
|
||||
|
||||
ResourcePackLoader.clear();
|
||||
CodeOfConductManager.getInstance().save();
|
||||
|
||||
@@ -777,13 +827,13 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
|
||||
@NonNull
|
||||
public RemoteServer defaultRemoteServer() {
|
||||
return getConfig().getRemote();
|
||||
return config().java();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public BedrockListener bedrockListener() {
|
||||
return getConfig().getBedrock();
|
||||
return config().bedrock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -801,7 +851,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
@Override
|
||||
@NonNull
|
||||
public PlatformType platformType() {
|
||||
return platformType;
|
||||
return bootstrap.platformType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -828,9 +878,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
return Integer.parseInt(BUILD_NUMBER);
|
||||
}
|
||||
|
||||
public static GeyserImpl load(PlatformType platformType, GeyserBootstrap bootstrap) {
|
||||
public static GeyserImpl load(GeyserBootstrap bootstrap) {
|
||||
if (instance == null) {
|
||||
return new GeyserImpl(platformType, bootstrap);
|
||||
return new GeyserImpl(bootstrap);
|
||||
}
|
||||
|
||||
return instance;
|
||||
@@ -853,8 +903,8 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
return bootstrap.getGeyserLogger();
|
||||
}
|
||||
|
||||
public GeyserConfiguration getConfig() {
|
||||
return bootstrap.getGeyserConfig();
|
||||
public GeyserConfig config() {
|
||||
return bootstrap.config();
|
||||
}
|
||||
|
||||
public WorldManager getWorldManager() {
|
||||
@@ -867,7 +917,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
}
|
||||
|
||||
public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
|
||||
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
|
||||
if (!config().savedUserLogins().contains(bedrockName)) {
|
||||
// Do not save this login
|
||||
return;
|
||||
}
|
||||
@@ -889,11 +939,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
scheduledThread.execute(() -> {
|
||||
// Ensure all writes are handled on the same thread
|
||||
File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
|
||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||
Type type = new TypeToken<Map<String, String>>() { }.getType();
|
||||
try (FileWriter writer = new FileWriter(savedAuthChains)) {
|
||||
JSON_MAPPER.writerFor(type)
|
||||
.withDefaultPrettyPrinter()
|
||||
.writeValue(writer, this.savedAuthChains);
|
||||
GSON.toJson(this.savedAuthChains, type, writer);
|
||||
} catch (IOException e) {
|
||||
getLogger().error("Unable to write saved refresh tokens!", e);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,10 @@ public interface GeyserLogger extends GeyserCommandSource {
|
||||
* @param object the object to log
|
||||
*/
|
||||
default void debug(@Nullable Object object) {
|
||||
debug(String.valueOf(object));
|
||||
if (isDebug()) {
|
||||
// Don't create String object by default
|
||||
info(String.valueOf(object));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -99,8 +99,8 @@ public class CommandRegistry implements EventRegistrar {
|
||||
|
||||
private static final String GEYSER_ROOT_PERMISSION = "geyser.command";
|
||||
|
||||
public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE ||
|
||||
GeyserImpl.getInstance().getPlatformType() == PlatformType.VIAPROXY;
|
||||
public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().platformType() == PlatformType.STANDALONE ||
|
||||
GeyserImpl.getInstance().platformType() == PlatformType.VIAPROXY;
|
||||
|
||||
protected final GeyserImpl geyser;
|
||||
private final CommandManager<GeyserCommandSource> cloud;
|
||||
@@ -171,7 +171,7 @@ public class CommandRegistry implements EventRegistrar {
|
||||
registerBuiltInCommand(new CustomOptionsCommand("options", "geyser.commands.options.desc", "geyser.command.options"));
|
||||
registerBuiltInCommand(new QuickActionsCommand("quickactions", "geyser.commands.quickactions.desc", "geyser.command.quickactions"));
|
||||
|
||||
if (this.geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
if (this.geyser.platformType() == PlatformType.STANDALONE) {
|
||||
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,13 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
@@ -79,7 +80,7 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
|
||||
// Replace "<" and ">" symbols if they are present to avoid the common issue of people including them
|
||||
final String ip = ipArgument.replace("<", "").replace(">", "");
|
||||
final int port = portArgument != null ? portArgument : geyser.getConfig().getBedrock().broadcastPort(); // default bedrock port
|
||||
final int port = portArgument != null ? portArgument : geyser.config().advanced().bedrock().broadcastPort(); // default bedrock port
|
||||
|
||||
// Issue: people commonly checking placeholders
|
||||
if (ip.equals("ip")) {
|
||||
@@ -105,42 +106,42 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserConfiguration config = geyser.getConfig();
|
||||
GeyserConfig config = geyser.config();
|
||||
|
||||
// Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing
|
||||
if (config.getBedrock().broadcastPort() == config.getBedrock().port()) {
|
||||
if (port != config.getBedrock().port()) {
|
||||
if (config.advanced().bedrock().broadcastPort() == config.bedrock().port()) {
|
||||
if (port != config.bedrock().port()) {
|
||||
if (portArgument != null) {
|
||||
source.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
|
||||
+ config.getBedrock().port() + ")");
|
||||
+ config.bedrock().port() + ")");
|
||||
source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
|
||||
if (config.getBedrock().isCloneRemotePort()) {
|
||||
if (config.bedrock().cloneRemotePort()) {
|
||||
source.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
|
||||
}
|
||||
} else {
|
||||
source.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
|
||||
"and the default port 19132 does not match the port in your Geyser configuration ("
|
||||
+ config.getBedrock().port() + ")!");
|
||||
+ config.bedrock().port() + ")!");
|
||||
source.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (config.getBedrock().broadcastPort() != port) {
|
||||
if (config.advanced().bedrock().broadcastPort() != port) {
|
||||
source.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration ("
|
||||
+ config.getBedrock().broadcastPort() + "). ");
|
||||
+ config.advanced().bedrock().broadcastPort() + "). ");
|
||||
source.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on.");
|
||||
source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config.");
|
||||
}
|
||||
}
|
||||
|
||||
// Issue: is the `bedrock` `address` in the config different?
|
||||
if (!config.getBedrock().address().equals("0.0.0.0")) {
|
||||
if (!config.bedrock().address().equals("0.0.0.0")) {
|
||||
source.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
|
||||
}
|
||||
|
||||
// Issue: did someone turn on enable-proxy-protocol, and they didn't mean it?
|
||||
if (config.getBedrock().isEnableProxyProtocol()) {
|
||||
source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
|
||||
if (config.advanced().bedrock().useHaproxyProtocol()) {
|
||||
source.sendMessage("You have the `use-haproxy-protocol` setting enabled. " +
|
||||
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
CONNECTION_TEST_MOTD = connectionTestMotd;
|
||||
|
||||
source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
|
||||
JsonNode output;
|
||||
JsonObject output;
|
||||
try {
|
||||
String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8);
|
||||
output = WebUtils.getJson("https://checker.geysermc.org/ping?hostname=" + hostname + "&port=" + port);
|
||||
@@ -179,18 +180,18 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
CONNECTION_TEST_MOTD = null;
|
||||
}
|
||||
|
||||
if (output.get("success").asBoolean()) {
|
||||
JsonNode cache = output.get("cache");
|
||||
if (output.get("success").getAsBoolean()) {
|
||||
JsonObject cache = output.getAsJsonObject("cache");
|
||||
String when;
|
||||
if (cache.get("fromCache").asBoolean()) {
|
||||
when = cache.get("secondsSince").asInt() + " seconds ago";
|
||||
if (cache.get("fromCache").isJsonPrimitive()) {
|
||||
when = cache.get("secondsSince").getAsBoolean() + " seconds ago";
|
||||
} else {
|
||||
when = "now";
|
||||
}
|
||||
|
||||
JsonNode ping = output.get("ping");
|
||||
JsonNode pong = ping.get("pong");
|
||||
String remoteMotd = pong.get("motd").asText();
|
||||
JsonObject ping = output.getAsJsonObject("ping");
|
||||
JsonObject pong = ping.getAsJsonObject("pong");
|
||||
String remoteMotd = pong.get("motd").getAsString();
|
||||
if (!connectionTestMotd.equals(remoteMotd)) {
|
||||
source.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
|
||||
"Did you supply the correct IP and port of your server?");
|
||||
@@ -198,7 +199,7 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ping.get("tcpFirst").asBoolean()) {
|
||||
if (ping.get("tcpFirst").getAsBoolean()) {
|
||||
source.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
|
||||
sendLinks(source);
|
||||
return;
|
||||
@@ -210,9 +211,9 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
source.sendMessage("Your server is likely unreachable from outside the network!");
|
||||
JsonNode message = output.get("message");
|
||||
if (message != null && !message.asText().isEmpty()) {
|
||||
source.sendMessage("Got the error message: " + message.asText());
|
||||
JsonElement message = output.get("message");
|
||||
if (message != null && !message.getAsString().isEmpty()) {
|
||||
source.sendMessage("Got the error message: " + message.getAsString());
|
||||
}
|
||||
sendLinks(source);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
@@ -38,16 +41,12 @@ import org.geysermc.geyser.dump.DumpInfo;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser;
|
||||
|
||||
public class DumpCommand extends GeyserCommand {
|
||||
@@ -56,7 +55,6 @@ public class DumpCommand extends GeyserCommand {
|
||||
private static final Iterable<String> SUGGESTIONS = List.of("full", "offline", "logs");
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final String DUMP_URL = "https://dump.geysermc.org/";
|
||||
|
||||
public DumpCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
@@ -111,20 +109,14 @@ public class DumpCommand extends GeyserCommand {
|
||||
|
||||
AsteriskSerializer.showSensitive = showSensitive;
|
||||
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale()));
|
||||
String dumpData;
|
||||
try {
|
||||
DumpInfo dump = new DumpInfo(geyser, addLog);
|
||||
|
||||
if (offlineDump) {
|
||||
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
||||
// Make arrays easier to read
|
||||
prettyPrinter.indentArraysWith(new DefaultIndenter(" ", "\n"));
|
||||
dumpData = MAPPER.writer(prettyPrinter).writeValueAsString(dump);
|
||||
} else {
|
||||
dumpData = MAPPER.writeValueAsString(dump);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
dumpData = gson.toJson(dump);
|
||||
} catch (Exception e) {
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale()));
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
|
||||
return;
|
||||
@@ -150,11 +142,11 @@ public class DumpCommand extends GeyserCommand {
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale()));
|
||||
|
||||
String response = null;
|
||||
JsonNode responseNode;
|
||||
JsonObject responseNode;
|
||||
try {
|
||||
response = WebUtils.post(DUMP_URL + "documents", dumpData);
|
||||
responseNode = MAPPER.readTree(response);
|
||||
} catch (IOException e) {
|
||||
responseNode = JsonUtils.parseJson(response);
|
||||
} catch (Throwable e) {
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale()));
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
|
||||
if (e instanceof JsonParseException && response != null) {
|
||||
@@ -164,11 +156,11 @@ public class DumpCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
if (!responseNode.has("key")) {
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").getAsString() : response));
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
|
||||
uploadedDumpUrl = DUMP_URL + responseNode.get("key").getAsString();
|
||||
}
|
||||
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
@@ -77,7 +77,7 @@ public class VersionCommand extends GeyserCommand {
|
||||
GeyserImpl.NAME, GeyserImpl.VERSION, SUPPORTED_JAVA_RANGE, SUPPORTED_BEDROCK_RANGE));
|
||||
|
||||
// Disable update checking in dev mode and for players in Geyser Standalone
|
||||
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
|
||||
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.platformType() == PlatformType.STANDALONE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,8 +89,8 @@ public class VersionCommand extends GeyserCommand {
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale()));
|
||||
try {
|
||||
int buildNumber = this.geyser.buildNumber();
|
||||
JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
|
||||
int latestBuildNumber = response.get("build").asInt();
|
||||
JsonObject response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
|
||||
int latestBuildNumber = response.get("build").getAsInt();
|
||||
|
||||
if (latestBuildNumber == buildNumber) {
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale()));
|
||||
|
||||
@@ -25,21 +25,18 @@
|
||||
|
||||
package org.geysermc.geyser.command.standalone;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
@ConfigSerializable
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class PermissionConfiguration {
|
||||
|
||||
@JsonProperty("default-permissions")
|
||||
private Set<String> defaultPermissions = Collections.emptySet();
|
||||
|
||||
@JsonProperty("default-denied-permissions")
|
||||
private Set<String> defaultDeniedPermissions = Collections.emptySet();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.common.returnsreceiver.qual.This;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Processor;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.yaml.NodeStyle;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ConfigLoader {
|
||||
private static final String HEADER = """
|
||||
--------------------------------
|
||||
Geyser Configuration File
|
||||
|
||||
A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition.
|
||||
|
||||
GitHub: https://github.com/GeyserMC/Geyser
|
||||
Discord: https://discord.gg/geysermc
|
||||
Wiki: https://geysermc.org/wiki
|
||||
|
||||
NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated.
|
||||
In most cases, especially with server hosting providers, further hosting-specific configuration is required.
|
||||
--------------------------------""";
|
||||
|
||||
/**
|
||||
* Only nullable for testing.
|
||||
*/
|
||||
private final @Nullable GeyserBootstrap bootstrap;
|
||||
private PlatformType platformType;
|
||||
private @Nullable Consumer<CommentedConfigurationNode> transformer;
|
||||
private File configFile;
|
||||
|
||||
/**
|
||||
* Only set during testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
CommentedConfigurationNode configurationNode;
|
||||
|
||||
public ConfigLoader(GeyserBootstrap bootstrap) {
|
||||
this.bootstrap = bootstrap;
|
||||
this.platformType = bootstrap.platformType();
|
||||
configFile = new File(bootstrap.getConfigFolder().toFile(), "config.yml");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ConfigLoader(File file) {
|
||||
this.bootstrap = null;
|
||||
configFile = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the directory as indicated by {@link GeyserBootstrap#getConfigFolder()}
|
||||
*/
|
||||
@This
|
||||
public ConfigLoader createFolder() {
|
||||
Path dataFolder = this.bootstrap.getConfigFolder();
|
||||
if (!dataFolder.toFile().exists()) {
|
||||
if (!dataFolder.toFile().mkdir()) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Failed to create config folder: " + dataFolder);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@This
|
||||
public ConfigLoader transformer(Consumer<CommentedConfigurationNode> transformer) {
|
||||
this.transformer = transformer;
|
||||
return this;
|
||||
}
|
||||
|
||||
@This
|
||||
public ConfigLoader configFile(File configFile) {
|
||||
this.configFile = configFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if the config failed to load.
|
||||
*/
|
||||
@Nullable
|
||||
public <T extends GeyserConfig> T load(Class<T> configClass) {
|
||||
try {
|
||||
return load0(configClass);
|
||||
} catch (IOException ex) {
|
||||
bootstrap.getGeyserLogger().error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends GeyserConfig> T load0(Class<T> configClass) throws ConfigurateException {
|
||||
var loader = createLoader(configFile);
|
||||
|
||||
CommentedConfigurationNode node = loader.load();
|
||||
boolean originallyEmpty = !configFile.exists() || node.isNull();
|
||||
|
||||
ConfigurationTransformation.Versioned migrations = ConfigMigrations.TRANSFORMER.apply(configClass, bootstrap);
|
||||
int currentVersion = migrations.version(node);
|
||||
migrations.apply(node);
|
||||
int newVersion = migrations.version(node);
|
||||
|
||||
T config = node.get(configClass);
|
||||
|
||||
// Serialize the instance to ensure strict field ordering. Additionally, if we serialized back
|
||||
// to the old node, existing nodes would only have their value changed, keeping their position
|
||||
// at the top of the ordered map, forcing all new nodes to the bottom (regardless of field order).
|
||||
// For that reason, we must also create a new node.
|
||||
CommentedConfigurationNode newRoot = CommentedConfigurationNode.root(loader.defaultOptions());
|
||||
newRoot.set(config);
|
||||
|
||||
if (originallyEmpty || currentVersion != newVersion) {
|
||||
if (!originallyEmpty && currentVersion > 4) {
|
||||
// Only copy comments over if the file already existed, and we are going to replace it
|
||||
|
||||
// Second case: Version 4 is pre-configurate where there were commented out nodes.
|
||||
// These get treated as comments on lower nodes, which produces very undesirable results.
|
||||
ConfigurationCommentMover.moveComments(node, newRoot);
|
||||
}
|
||||
|
||||
loader.save(newRoot);
|
||||
}
|
||||
|
||||
if (transformer != null) {
|
||||
// We transform AFTER saving so that these specific transformations aren't applied to file.
|
||||
transformer.accept(newRoot);
|
||||
config = newRoot.get(configClass);
|
||||
}
|
||||
|
||||
if (this.bootstrap != null) { // Null for testing only.
|
||||
this.bootstrap.getGeyserLogger().setDebug(config.debugMode());
|
||||
} else {
|
||||
this.configurationNode = newRoot;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
CommentedConfigurationNode loadConfigurationNode(Class<? extends GeyserConfig> configClass, PlatformType platformType) throws ConfigurateException {
|
||||
this.platformType = platformType;
|
||||
load0(configClass);
|
||||
return configurationNode.copy();
|
||||
}
|
||||
|
||||
private YamlConfigurationLoader createLoader(File file) {
|
||||
return YamlConfigurationLoader.builder()
|
||||
.file(file)
|
||||
.indent(2)
|
||||
.nodeStyle(NodeStyle.BLOCK)
|
||||
.defaultOptions(options -> InterfaceDefaultOptions.addTo(options, builder -> {
|
||||
builder.addProcessor(ExcludePlatform.class, excludePlatform(platformType.platformName()))
|
||||
.addProcessor(PluginSpecific.class, integrationSpecific(platformType != PlatformType.STANDALONE));
|
||||
})
|
||||
.shouldCopyDefaults(false) // If we use ConfigurationNode#get(type, default), do not write the default back to the node.
|
||||
.header(ConfigLoader.HEADER)
|
||||
.serializers(builder -> builder.register(new LowercaseEnumSerializer())))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Processor.Factory<ExcludePlatform, Object> excludePlatform(String thisPlatform) {
|
||||
return (data, fieldType) -> (value, destination) -> {
|
||||
for (String platform : data.platforms()) {
|
||||
if (thisPlatform.equals(platform)) {
|
||||
//noinspection DataFlowIssue
|
||||
destination.parent().removeChild(destination.key());
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Processor.Factory<PluginSpecific, Object> integrationSpecific(boolean thisConfigPlugin) {
|
||||
return (data, fieldType) -> (value, destination) -> {
|
||||
if (data.forPlugin() != thisConfigPlugin) {
|
||||
//noinspection DataFlowIssue
|
||||
destination.parent().removeChild(destination.key());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.remove;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.rename;
|
||||
|
||||
public class ConfigMigrations {
|
||||
|
||||
public static final BiFunction<Class<? extends GeyserConfig>, GeyserBootstrap, ConfigurationTransformation.Versioned> TRANSFORMER = (configClass, bootstrap) ->
|
||||
ConfigurationTransformation.versionedBuilder()
|
||||
.versionKey("config-version")
|
||||
.addVersion(5, ConfigurationTransformation.builder()
|
||||
// Java section
|
||||
.addAction(path("remote"), rename("java"))
|
||||
.addAction(path("remote", "address"), (path, value) -> {
|
||||
if ("auto".equals(value.getString())) {
|
||||
// Auto-convert back to localhost
|
||||
value.set("127.0.0.1");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
// Motd section
|
||||
.addAction(path("bedrock", "motd1"), renameAndMove("motd", "primary-motd"))
|
||||
.addAction(path("bedrock", "motd2"), renameAndMove("motd", "secondary-motd"))
|
||||
.addAction(path("passthrough-motd"), moveTo("motd"))
|
||||
.addAction(path("passthrough-player-counts"), moveTo("motd"))
|
||||
.addAction(path("ping-passthrough-interval"), moveTo("motd"))
|
||||
.addAction(path("max-players"), moveTo("motd"))
|
||||
.addAction(path("legacy-ping-passthrough"), configClass == GeyserRemoteConfig.class ? remove() : (path, value) -> {
|
||||
// Invert value
|
||||
value.set(!value.getBoolean());
|
||||
return new Object[]{ "motd", "integrated-ping-passthrough" };
|
||||
})
|
||||
|
||||
// gameplay
|
||||
.addAction(path("command-suggestions"), moveTo("gameplay"))
|
||||
.addAction(path("forward-player-ping"), moveTo("gameplay"))
|
||||
.addAction(path("show-cooldown"), (path, value) -> {
|
||||
String s = value.getString();
|
||||
if (s != null) {
|
||||
switch (s) {
|
||||
case "true" -> value.set("title");
|
||||
case "false" -> value.set("disabled");
|
||||
}
|
||||
}
|
||||
return new Object[]{ "gameplay", "show-cooldown" };
|
||||
})
|
||||
.addAction(path("bedrock", "server-name"), moveTo("gameplay"))
|
||||
.addAction(path("show-coordinates"), moveTo("gameplay"))
|
||||
.addAction(path("disable-bedrock-scaffolding"), moveTo("gameplay"))
|
||||
.addAction(path("custom-skull-render-distance"), moveTo("gameplay"))
|
||||
.addAction(path("force-resource-packs"), moveTo("gameplay"))
|
||||
.addAction(path("xbox-achievements-enabled"), moveTo("gameplay"))
|
||||
.addAction(path("unusable-space-block"), moveTo("gameplay"))
|
||||
.addAction(path("unusable-space-block"), moveTo("gameplay"))
|
||||
.addAction(path("add-non-bedrock-items"), renameAndMove("gameplay", "enable-custom-content"))
|
||||
.addAction(path("above-bedrock-nether-building"), renameAndMove("gameplay", "nether-roof-workaround"))
|
||||
.addAction(path("xbox-achievements-enabled"), moveTo("gameplay"))
|
||||
// NOTE: We're not explicitly removing the allow-custom-skulls option, it will already be removed since it
|
||||
// won't be written back. If we remove it, we can't query the value of it!
|
||||
.addAction(path("max-visible-custom-skulls"), (path, value) -> {
|
||||
ConfigurationNode parent = value.parent();
|
||||
if (parent != null && parent.isMap()) {
|
||||
ConfigurationNode allowCustomSkulls = parent.childrenMap().get("allow-custom-skulls");
|
||||
if (allowCustomSkulls != null && !allowCustomSkulls.getBoolean()) {
|
||||
value.set(0);
|
||||
}
|
||||
}
|
||||
return new Object[]{ "gameplay", "max-visible-custom-skulls" };
|
||||
})
|
||||
.addAction(path("emote-offhand-workaround"), (path, value) -> {
|
||||
String previous = value.getString();
|
||||
if (!Objects.equals(previous, "disabled") && bootstrap != null) {
|
||||
bootstrap.getGeyserLogger().warning("The emote-offhand-workaround option has been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/EmoteOffhandExtension/");
|
||||
}
|
||||
if (Objects.equals(previous, "no-emotes")) {
|
||||
value.set(false);
|
||||
return new Object[]{ "gameplay", "show-emotes" };
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
// For the warning!
|
||||
.addAction(path("allow-third-party-capes"), (node, value) -> {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.getGeyserLogger().warning("Third-party ears/capes have been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/ThirdPartyCosmetics");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.addAction(path("allow-third-party-ears"), (node, value) -> {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.getGeyserLogger().warning("Third-party ears/capes have been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/ThirdPartyCosmetics");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
// Advanced section
|
||||
.addAction(path("cache-images"), moveTo("advanced"))
|
||||
.addAction(path("scoreboard-packet-threshold"), moveTo("advanced"))
|
||||
.addAction(path("add-team-suggestions"), moveTo("advanced"))
|
||||
.addAction(path("floodgate-key-file"), (path, value) -> {
|
||||
// Elimate any legacy config values
|
||||
if ("public-key.pem".equals(value.getString())) {
|
||||
value.set("key.pem");
|
||||
}
|
||||
return new Object[]{ "advanced", "floodgate-key-file" };
|
||||
})
|
||||
|
||||
// Bedrock
|
||||
.addAction(path("bedrock", "broadcast-port"), moveTo("advanced", "bedrock"))
|
||||
.addAction(path("bedrock", "compression-level"), moveTo("advanced", "bedrock"))
|
||||
.addAction(path("bedrock", "enable-proxy-protocol"), renameAndMove("advanced", "bedrock", "use-haproxy-protocol"))
|
||||
.addAction(path("bedrock", "proxy-protocol-whitelisted-ips"), renameAndMove("advanced", "bedrock", "haproxy-protocol-whitelisted-ips"))
|
||||
.addAction(path("mtu"), moveTo("advanced", "bedrock"))
|
||||
|
||||
// Java
|
||||
.addAction(path("remote", "use-proxy-protocol"), renameAndMove("advanced", "java", "use-haproxy-protocol"))
|
||||
.addAction(path("disable-compression"), moveTo("advanced", "java"))
|
||||
.addAction(path("use-direct-connection"), moveTo("advanced", "java"))
|
||||
|
||||
// Other
|
||||
.addAction(path("default-locale"), (path, value) -> {
|
||||
if (value.getString() == null) {
|
||||
value.set("system");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.addAction(path("metrics", "uuid"), (path, value) -> {
|
||||
if ("generateduuid".equals(value.getString())) {
|
||||
// Manually copied config without Metrics UUID creation?
|
||||
value.set(UUID.randomUUID());
|
||||
}
|
||||
return new Object[]{ "metrics-uuid" };
|
||||
})
|
||||
.addAction(path("metrics", "enabled"), (path, value) -> {
|
||||
// Move to the root, not in the Metrics class.
|
||||
return new Object[]{ "enable-metrics" };
|
||||
})
|
||||
|
||||
.build())
|
||||
.build();
|
||||
|
||||
static TransformAction renameAndMove(String... newPath) {
|
||||
return ((path, value) -> Arrays.stream(newPath).toArray());
|
||||
}
|
||||
|
||||
static TransformAction moveTo(String... newPath) {
|
||||
return (path, value) -> {
|
||||
Object[] arr = path.array();
|
||||
if (arr.length == 0) {
|
||||
throw new ConfigurateException(value, "The root node cannot be renamed!");
|
||||
} else {
|
||||
// create a new array with space for newPath segments + the original last segment
|
||||
Object[] result = new Object[newPath.length + 1];
|
||||
System.arraycopy(newPath, 0, result, 0, newPath.length);
|
||||
result[newPath.length] = arr[arr.length - 1];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationVisitor;
|
||||
|
||||
/**
|
||||
* Moves comments from a different node and puts them on this node
|
||||
*/
|
||||
public final class ConfigurationCommentMover implements ConfigurationVisitor.Stateless<RuntimeException> {
|
||||
|
||||
private final CommentedConfigurationNode otherRoot;
|
||||
|
||||
private ConfigurationCommentMover(@NonNull CommentedConfigurationNode otherNode) {
|
||||
this.otherRoot = otherNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterNode(final ConfigurationNode node) {
|
||||
if (!(node instanceof CommentedConfigurationNode destination)) {
|
||||
// Should not occur because all nodes in a tree are the same type,
|
||||
// and our static method below ensures this visitor is only used on CommentedConfigurationNodes
|
||||
throw new IllegalStateException(node.path() + " is not a CommentedConfigurationNode");
|
||||
}
|
||||
// Node with the same path
|
||||
CommentedConfigurationNode source = otherRoot.node(node.path());
|
||||
|
||||
moveSingle(source, destination);
|
||||
}
|
||||
|
||||
private static void moveSingle(@NonNull CommentedConfigurationNode source, @NonNull CommentedConfigurationNode destination) {
|
||||
// Only transfer the comment, overriding if necessary
|
||||
String comment = source.comment();
|
||||
if (comment != null) {
|
||||
destination.comment(comment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves comments from a source node and its children to a destination node and its children (of a different tree), overriding if necessary.
|
||||
* Comments are only moved to the destination node and its children which exist.
|
||||
* Comments are only moved to and from nodes with the exact same path.
|
||||
*
|
||||
* @param source the source of the comments, which must be the topmost parent of a tree
|
||||
* @param destination the destination of the comments, any node in a different tree
|
||||
*/
|
||||
public static void moveComments(@NonNull CommentedConfigurationNode source, @NonNull CommentedConfigurationNode destination) {
|
||||
if (source.parent() != null) {
|
||||
throw new IllegalArgumentException("source is not the base of the tree it is within: " + source.path());
|
||||
}
|
||||
|
||||
if (source.isNull()) {
|
||||
// It has no value(s), but may still have a comment on it. Don't both traversing the whole destination tree.
|
||||
moveSingle(source, destination);
|
||||
} else {
|
||||
destination.visit(new ConfigurationCommentMover(source));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -23,20 +23,15 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.platform.standalone;
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration {
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
return Paths.get(getFloodgateKeyFile());
|
||||
}
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExcludePlatform {
|
||||
String[] platforms();
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.BedrockListener;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.CooldownUtils;
|
||||
import org.spongepowered.configurate.interfaces.meta.Exclude;
|
||||
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean;
|
||||
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric;
|
||||
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString;
|
||||
import org.spongepowered.configurate.interfaces.meta.range.NumericRange;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@ConfigSerializable
|
||||
public interface GeyserConfig {
|
||||
@Comment("Network settings for the Bedrock listener")
|
||||
BedrockConfig bedrock();
|
||||
|
||||
@Comment("Network settings for the Java server connection")
|
||||
JavaConfig java();
|
||||
|
||||
@Comment("MOTD settings")
|
||||
MotdConfig motd();
|
||||
|
||||
@Comment("Gameplay options that affect Bedrock players")
|
||||
GameplayConfig gameplay();
|
||||
|
||||
@Comment("The default locale if we don't have the one the client requested. If set to \"system\", the system's language will be used.")
|
||||
@DefaultString(GeyserLocale.SYSTEM_LOCALE)
|
||||
@NonNull
|
||||
String defaultLocale();
|
||||
|
||||
@Comment("Whether player IP addresses will be logged by the server.")
|
||||
@DefaultBoolean(true)
|
||||
boolean logPlayerIpAddresses();
|
||||
|
||||
@Comment("""
|
||||
For online mode authentication type only.
|
||||
Stores a list of Bedrock player usernames that should have their Java Edition account saved after login.
|
||||
This saves a token that can be reused to authenticate the player later. This does not save emails or passwords,
|
||||
but you should still be cautious when adding to this list and giving others access to this Geyser instance's files.
|
||||
Removing a name from this list will delete its cached login information on the next Geyser startup.
|
||||
The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json".""")
|
||||
default List<String> savedUserLogins() {
|
||||
return List.of("ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername",
|
||||
"ThisOtherExampleUsernameShouldAlsoBeLongEnough");
|
||||
}
|
||||
|
||||
@Comment("""
|
||||
For online mode authentication type only.
|
||||
Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account.
|
||||
User is allowed to disconnect from the server during this period.""")
|
||||
@DefaultNumeric(120)
|
||||
int pendingAuthenticationTimeout();
|
||||
|
||||
@Comment("""
|
||||
Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version
|
||||
that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms
|
||||
auto-update.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean notifyOnNewBedrockUpdate();
|
||||
|
||||
@Comment("Advanced configuration options. These usually do not need modifications.")
|
||||
AdvancedConfig advanced();
|
||||
|
||||
@Comment("""
|
||||
bStats is a stat tracker that is entirely anonymous and tracks only basic information
|
||||
about Geyser, such as how many people are online, how many servers are using Geyser,
|
||||
what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.
|
||||
https://bstats.org/plugin/server-implementation/GeyserMC""")
|
||||
@DefaultBoolean(true)
|
||||
@ExcludePlatform(platforms = {"BungeeCord", "Spigot", "Velocity"}) // bStats platform versions used
|
||||
boolean enableMetrics();
|
||||
|
||||
@Comment("The bstats metrics uuid. Do not touch!")
|
||||
@ExcludePlatform(platforms = {"BungeeCord", "Spigot", "Velocity"}) // bStats platform versions used
|
||||
default UUID metricsUuid() {
|
||||
return UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Comment("If debug messages should be sent through console")
|
||||
boolean debugMode();
|
||||
|
||||
@Comment("Do not change!")
|
||||
@SuppressWarnings("unused")
|
||||
default int configVersion() {
|
||||
return Constants.CONFIG_VERSION;
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface BedrockConfig extends BedrockListener {
|
||||
@Comment("""
|
||||
The IP address that Geyser will bind on to listen for incoming Bedrock connections.
|
||||
Generally, you should only change this if you want to limit what IPs can connect to your server.""")
|
||||
@NonNull
|
||||
@Override
|
||||
@DefaultString("0.0.0.0")
|
||||
@AsteriskSerializer.Asterisk
|
||||
String address();
|
||||
|
||||
@Comment("""
|
||||
The port that will Geyser will listen on for incoming Bedrock connections.
|
||||
Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic.""")
|
||||
@Override
|
||||
@DefaultNumeric(19132)
|
||||
@NumericRange(from = 0, to = 65535)
|
||||
int port();
|
||||
|
||||
@Comment("""
|
||||
Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock.
|
||||
This option makes the Bedrock port the same as the Java port every time you start the server.""")
|
||||
@DefaultBoolean
|
||||
@PluginSpecific
|
||||
boolean cloneRemotePort();
|
||||
|
||||
void address(String address);
|
||||
void port(int port);
|
||||
|
||||
@Exclude
|
||||
@Override
|
||||
default int broadcastPort() {
|
||||
return GeyserImpl.getInstance().config().advanced().bedrock().broadcastPort();
|
||||
}
|
||||
|
||||
@Exclude
|
||||
@Override
|
||||
default String primaryMotd() {
|
||||
return GeyserImpl.getInstance().config().motd().primaryMotd();
|
||||
}
|
||||
|
||||
@Exclude
|
||||
@Override
|
||||
default String secondaryMotd() {
|
||||
return GeyserImpl.getInstance().config().motd().secondaryMotd();
|
||||
}
|
||||
|
||||
@Exclude
|
||||
@Override
|
||||
default String serverName() {
|
||||
return GeyserImpl.getInstance().config().gameplay().serverName();
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface JavaConfig extends RemoteServer {
|
||||
void address(String address);
|
||||
void port(int port);
|
||||
|
||||
@Comment("""
|
||||
What type of authentication Bedrock players will be checked against when logging into the Java server.
|
||||
Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline".""")
|
||||
@NonNull
|
||||
@Override
|
||||
default AuthType authType() {
|
||||
return AuthType.ONLINE;
|
||||
}
|
||||
|
||||
void authType(AuthType authType);
|
||||
boolean forwardHostname();
|
||||
|
||||
@Override
|
||||
@Exclude
|
||||
default String minecraftVersion() {
|
||||
return GameProtocol.getJavaMinecraftVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Exclude
|
||||
default int protocolVersion() {
|
||||
return GameProtocol.getJavaProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Exclude
|
||||
default boolean resolveSrv() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface MotdConfig {
|
||||
@Comment("""
|
||||
The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true.
|
||||
If either of these are empty, the respective string will default to "Geyser\"""")
|
||||
@DefaultString("Geyser")
|
||||
String primaryMotd();
|
||||
@DefaultString("Another Geyser server.")
|
||||
String secondaryMotd();
|
||||
|
||||
@Comment("Whether Geyser should relay the MOTD from the Java server to Bedrock players.")
|
||||
@DefaultBoolean(true)
|
||||
boolean passthroughMotd();
|
||||
|
||||
@Comment("""
|
||||
Maximum amount of players that can connect.
|
||||
This is only visual, and is only applied if passthrough-motd is disabled.""")
|
||||
@DefaultNumeric(100)
|
||||
int maxPlayers();
|
||||
|
||||
@Comment("Whether to relay the player count and max players from the Java server to Bedrock players.")
|
||||
@DefaultBoolean(true)
|
||||
boolean passthroughPlayerCounts();
|
||||
|
||||
@Comment("""
|
||||
Whether to use server API methods to determine the Java server's MOTD and ping passthrough.
|
||||
There is no need to disable this unless your MOTD or player count does not appear properly.""")
|
||||
@DefaultBoolean(true)
|
||||
@PluginSpecific
|
||||
boolean integratedPingPassthrough();
|
||||
|
||||
@Comment("How often to ping the Java server to refresh MOTD and player count, in seconds.")
|
||||
@DefaultNumeric(3)
|
||||
int pingPassthroughInterval();
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface GameplayConfig {
|
||||
|
||||
@Comment("The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.")
|
||||
@DefaultString("Geyser")
|
||||
String serverName();
|
||||
|
||||
@Comment("""
|
||||
Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat.
|
||||
Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below:
|
||||
https://geysermc.org/img/external/cooldown_indicator.png
|
||||
This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0
|
||||
This setting can be set to "title", "actionbar" or "false\"""")
|
||||
default CooldownUtils.CooldownType showCooldown() {
|
||||
return CooldownUtils.CooldownType.TITLE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
@Comment("""
|
||||
Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
|
||||
Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean commandSuggestions();
|
||||
|
||||
@Comment("Controls if coordinates are shown to players.")
|
||||
@DefaultBoolean(true)
|
||||
boolean showCoordinates();
|
||||
|
||||
@Comment("Whether Bedrock players are blocked from performing their scaffolding-style bridging.")
|
||||
boolean disableBedrockScaffolding();
|
||||
|
||||
@Comment("""
|
||||
Bedrock prevents building and displaying blocks above Y127 in the Nether.
|
||||
This config option works around that by changing the Nether dimension ID to the End ID.
|
||||
The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome.""")
|
||||
boolean netherRoofWorkaround();
|
||||
|
||||
@Comment("""
|
||||
Whether to show Bedrock Edition emotes to other Bedrock Edition players.
|
||||
""")
|
||||
@DefaultBoolean(true)
|
||||
boolean emotesEnabled();
|
||||
|
||||
@Comment("""
|
||||
Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative,
|
||||
or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item.
|
||||
This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:<mapping-name>"
|
||||
""")
|
||||
@DefaultString("minecraft:barrier")
|
||||
String unusableSpaceBlock();
|
||||
|
||||
@Comment("""
|
||||
Whether to add any items and blocks which normally does not exist in Bedrock Edition.
|
||||
This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching.
|
||||
If this is disabled, furnace minecart items will be mapped to hopper minecart items.
|
||||
Geyser's block, item, and skull mappings systems will also be disabled.
|
||||
This option requires a restart of Geyser in order to change its setting.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean enableCustomContent();
|
||||
|
||||
@Comment("""
|
||||
Force clients to load all resource packs if there are any.
|
||||
If set to false, it allows the user to connect to the server even if they don't
|
||||
want to download the resource packs.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean forceResourcePacks();
|
||||
|
||||
@Comment("""
|
||||
Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players.
|
||||
If enabled, force-resource-packs will be enabled.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean enableIntegratedPack();
|
||||
|
||||
@Comment("""
|
||||
Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate
|
||||
ping, it may also cause players to time out more easily.""")
|
||||
boolean forwardPlayerPing();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
@Comment("""
|
||||
Allows Xbox achievements to be unlocked.
|
||||
If a player types in an unknown command, they will receive a message that states cheats are disabled.
|
||||
Otherwise, commands work as expected.""")
|
||||
boolean xboxAchievementsEnabled();
|
||||
|
||||
@Comment("""
|
||||
The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices.
|
||||
A value of 0 will disable all custom skulls.
|
||||
Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number.""")
|
||||
@DefaultNumeric(128)
|
||||
int maxVisibleCustomSkulls();
|
||||
|
||||
@Comment("The radius in blocks around the player in which custom skulls are displayed.")
|
||||
@DefaultNumeric(32)
|
||||
int customSkullRenderDistance();
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface AdvancedBedrockConfig {
|
||||
@Comment("""
|
||||
The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server.
|
||||
A value of 0 will broadcast the port specified above.
|
||||
DO NOT change this unless Geyser runs on a different port than the one that is used to connect.""")
|
||||
@DefaultNumeric(0)
|
||||
@NumericRange(from = 0, to = 65535)
|
||||
int broadcastPort();
|
||||
|
||||
void broadcastPort(int port);
|
||||
|
||||
@Comment("""
|
||||
How much to compress network traffic to the Bedrock client. The higher the number, the more CPU usage used, but
|
||||
the smaller the bandwidth used. Does not have any effect below -1 or above 9. Set to -1 to disable.""")
|
||||
@DefaultNumeric(6)
|
||||
@NumericRange(from = -1, to = 9)
|
||||
int compressionLevel();
|
||||
|
||||
@Comment("""
|
||||
Whether to expect HAPROXY protocol for connecting Bedrock clients.
|
||||
This is useful only when you are running a UDP reverse proxy in front of your Geyser instance.
|
||||
IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!""")
|
||||
@DefaultBoolean
|
||||
boolean useHaproxyProtocol();
|
||||
|
||||
@Comment("""
|
||||
A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and
|
||||
should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.).
|
||||
Keeping this list empty means there is no IP address whitelist.
|
||||
IP addresses, subnets, and links to plain text files are supported.""")
|
||||
default List<String> haproxyProtocolWhitelistedIps() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Comment("""
|
||||
The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation.
|
||||
1400 is the default.""")
|
||||
@DefaultNumeric(1400)
|
||||
int mtu();
|
||||
|
||||
@Comment("""
|
||||
This option disables the auth step Geyser performs for connecting Bedrock players.
|
||||
It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users
|
||||
cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for
|
||||
assistance - and use UDP instead of TCP.
|
||||
Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk.
|
||||
All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean validateBedrockLogin();
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface AdvancedJavaConfig {
|
||||
@Comment("""
|
||||
Whether to enable HAPROXY protocol when connecting to the Java server.
|
||||
This is useful only when:
|
||||
1) Your Java server supports HAPROXY protocol (it probably doesn't)
|
||||
2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
|
||||
IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!""")
|
||||
boolean useHaproxyProtocol();
|
||||
|
||||
@Comment("""
|
||||
Whether to connect directly into the Java server without creating a TCP connection.
|
||||
This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser.
|
||||
If enabled, the remote address and port sections are ignored.
|
||||
If disabled, expect performance decrease and latency increase.
|
||||
""")
|
||||
@DefaultBoolean(true)
|
||||
@PluginSpecific
|
||||
boolean useDirectConnection();
|
||||
|
||||
@Comment("""
|
||||
Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players.
|
||||
This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network.
|
||||
This requires use-direct-connection to be true.
|
||||
""")
|
||||
@DefaultBoolean(true)
|
||||
@PluginSpecific
|
||||
boolean disableCompression();
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface AdvancedConfig {
|
||||
@Comment("""
|
||||
Specify how many days player skin images will be cached to disk to save downloading them from the internet.
|
||||
A value of 0 is disabled. (Default: 0)""")
|
||||
int cacheImages();
|
||||
|
||||
@Comment("""
|
||||
Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle
|
||||
a lot of scoreboard packets per second, this can cause serious lag.
|
||||
This option allows you to specify after how many Scoreboard packets per seconds
|
||||
the Scoreboard updates will be limited to four updates per second.""")
|
||||
@DefaultNumeric(20)
|
||||
int scoreboardPacketThreshold();
|
||||
|
||||
@Comment("""
|
||||
Whether Geyser should send team names in command suggestions.
|
||||
Disable this if you have a lot of teams used that you don't need as suggestions.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean addTeamSuggestions();
|
||||
|
||||
@Comment("""
|
||||
A list of remote resource pack urls to send to the Bedrock client for downloading.
|
||||
The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/
|
||||
""")
|
||||
default List<String> resourcePackUrls() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Cannot be type File yet because we may want to hide it in plugin instances.
|
||||
@Comment("""
|
||||
Floodgate uses encryption to ensure use from authorized sources.
|
||||
This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity)
|
||||
You can ignore this when not using Floodgate.
|
||||
If you're using a plugin version of Floodgate on the same server, the key will automatically be picked up from Floodgate.""")
|
||||
@DefaultString("key.pem")
|
||||
String floodgateKeyFile();
|
||||
|
||||
@Comment("Advanced networking options for the Geyser to Java server connection")
|
||||
AdvancedJavaConfig java();
|
||||
|
||||
@Comment("Advanced networking options for Geyser's Bedrock listener")
|
||||
AdvancedBedrockConfig bedrock();
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.BedrockListener;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.network.CIDRMatcher;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public interface GeyserConfiguration {
|
||||
/**
|
||||
* If the config was originally 'auto' before the values changed
|
||||
*/
|
||||
void setAutoconfiguredRemote(boolean autoconfiguredRemote);
|
||||
|
||||
// Modify this when you introduce breaking changes into the config
|
||||
int CURRENT_CONFIG_VERSION = 4;
|
||||
|
||||
IBedrockConfiguration getBedrock();
|
||||
|
||||
IRemoteConfiguration getRemote();
|
||||
|
||||
List<String> getSavedUserLogins();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isCommandSuggestions();
|
||||
|
||||
@JsonIgnore
|
||||
boolean isPassthroughMotd();
|
||||
|
||||
@JsonIgnore
|
||||
boolean isPassthroughPlayerCounts();
|
||||
|
||||
@JsonIgnore
|
||||
boolean isLegacyPingPassthrough();
|
||||
|
||||
int getPingPassthroughInterval();
|
||||
|
||||
boolean isForwardPlayerPing();
|
||||
|
||||
int getMaxPlayers();
|
||||
|
||||
boolean isDebugMode();
|
||||
|
||||
@Deprecated
|
||||
boolean isAllowThirdPartyCapes();
|
||||
|
||||
@Deprecated
|
||||
boolean isAllowThirdPartyEars();
|
||||
|
||||
String getShowCooldown();
|
||||
|
||||
boolean isShowCoordinates();
|
||||
|
||||
boolean isDisableBedrockScaffolding();
|
||||
|
||||
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
|
||||
|
||||
String getDefaultLocale();
|
||||
|
||||
Path getFloodgateKeyPath();
|
||||
|
||||
boolean isAddNonBedrockItems();
|
||||
|
||||
boolean isAboveBedrockNetherBuilding();
|
||||
|
||||
boolean isForceResourcePacks();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isXboxAchievementsEnabled();
|
||||
|
||||
int getCacheImages();
|
||||
|
||||
boolean isAllowCustomSkulls();
|
||||
|
||||
int getMaxVisibleCustomSkulls();
|
||||
|
||||
int getCustomSkullRenderDistance();
|
||||
|
||||
boolean isLogPlayerIpAddresses();
|
||||
|
||||
boolean isNotifyOnNewBedrockUpdate();
|
||||
|
||||
String getUnusableSpaceBlock();
|
||||
|
||||
IMetricsInfo getMetrics();
|
||||
|
||||
int getPendingAuthenticationTimeout();
|
||||
|
||||
boolean isAutoconfiguredRemote();
|
||||
|
||||
interface IBedrockConfiguration extends BedrockListener {
|
||||
void setAddress(String address);
|
||||
|
||||
void setPort(int port);
|
||||
|
||||
void setBroadcastPort(int broadcastPort);
|
||||
|
||||
boolean isCloneRemotePort();
|
||||
|
||||
int getCompressionLevel();
|
||||
|
||||
boolean isEnableProxyProtocol();
|
||||
|
||||
List<String> getProxyProtocolWhitelistedIPs();
|
||||
|
||||
/**
|
||||
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()}
|
||||
*/
|
||||
List<CIDRMatcher> getWhitelistedIPsMatchers();
|
||||
}
|
||||
|
||||
interface IRemoteConfiguration extends RemoteServer {
|
||||
|
||||
void setAddress(String address);
|
||||
|
||||
void setPort(int port);
|
||||
|
||||
boolean isUseProxyProtocol();
|
||||
|
||||
boolean isForwardHost();
|
||||
|
||||
default String minecraftVersion() {
|
||||
return GameProtocol.getJavaMinecraftVersion();
|
||||
}
|
||||
|
||||
default int protocolVersion() {
|
||||
return GameProtocol.getJavaProtocolVersion();
|
||||
}
|
||||
|
||||
void setAuthType(AuthType authType);
|
||||
}
|
||||
|
||||
interface IMetricsInfo {
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
String getUniqueId();
|
||||
}
|
||||
|
||||
int getScoreboardPacketThreshold();
|
||||
|
||||
// if u have offline mode enabled pls be safe
|
||||
boolean isEnableProxyConnections();
|
||||
|
||||
int getMtu();
|
||||
|
||||
boolean isUseDirectConnection();
|
||||
|
||||
boolean isDisableCompression();
|
||||
|
||||
int getConfigVersion();
|
||||
|
||||
static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) {
|
||||
if (geyserConfig.getConfigVersion() < CURRENT_CONFIG_VERSION) {
|
||||
geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.outdated"));
|
||||
} else if (geyserConfig.getConfigVersion() > CURRENT_CONFIG_VERSION) {
|
||||
geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.too_new"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,26 +25,20 @@
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
@ConfigSerializable
|
||||
public class GeyserCustomSkullConfiguration {
|
||||
@JsonProperty("player-usernames")
|
||||
private List<String> playerUsernames;
|
||||
|
||||
@JsonProperty("player-uuids")
|
||||
private List<String> playerUUIDs;
|
||||
|
||||
@JsonProperty("player-profiles")
|
||||
private List<String> playerProfiles;
|
||||
|
||||
@JsonProperty("skin-hashes")
|
||||
private List<String> skinHashes;
|
||||
|
||||
public List<String> getPlayerUsernames() {
|
||||
@@ -62,4 +56,14 @@ public class GeyserCustomSkullConfiguration {
|
||||
public List<String> getPlayerSkinHashes() {
|
||||
return Objects.requireNonNullElse(skinHashes, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GeyserCustomSkullConfiguration{" +
|
||||
"playerUsernames=" + playerUsernames +
|
||||
", playerUUIDs=" + playerUUIDs +
|
||||
", playerProfiles=" + playerProfiles +
|
||||
", skinHashes=" + skinHashes +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.network.CIDRMatcher;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
public abstract class GeyserJacksonConfiguration implements GeyserConfiguration {
|
||||
|
||||
@Setter
|
||||
private boolean autoconfiguredRemote = false;
|
||||
|
||||
private BedrockConfiguration bedrock = new BedrockConfiguration();
|
||||
private RemoteConfiguration remote = new RemoteConfiguration();
|
||||
|
||||
@JsonProperty("saved-user-logins")
|
||||
private List<String> savedUserLogins = Collections.emptyList();
|
||||
|
||||
@JsonProperty("floodgate-key-file")
|
||||
private String floodgateKeyFile = "key.pem";
|
||||
|
||||
public abstract Path getFloodgateKeyPath();
|
||||
|
||||
@JsonProperty("command-suggestions")
|
||||
private boolean commandSuggestions = true;
|
||||
|
||||
@JsonProperty("passthrough-motd")
|
||||
private boolean isPassthroughMotd = false;
|
||||
|
||||
@JsonProperty("passthrough-player-counts")
|
||||
private boolean isPassthroughPlayerCounts = false;
|
||||
|
||||
@JsonProperty("legacy-ping-passthrough")
|
||||
private boolean isLegacyPingPassthrough = false;
|
||||
|
||||
@JsonProperty("ping-passthrough-interval")
|
||||
private int pingPassthroughInterval = 3;
|
||||
|
||||
@JsonProperty("forward-player-ping")
|
||||
private boolean forwardPlayerPing = false;
|
||||
|
||||
@JsonProperty("max-players")
|
||||
private int maxPlayers = 100;
|
||||
|
||||
@JsonProperty("debug-mode")
|
||||
private boolean debugMode = false;
|
||||
|
||||
@JsonProperty("allow-third-party-capes")
|
||||
private boolean allowThirdPartyCapes = false;
|
||||
|
||||
@JsonProperty("show-cooldown")
|
||||
private String showCooldown = "title";
|
||||
|
||||
@JsonProperty("show-coordinates")
|
||||
private boolean showCoordinates = true;
|
||||
|
||||
@JsonProperty("disable-bedrock-scaffolding")
|
||||
private boolean disableBedrockScaffolding = false;
|
||||
|
||||
@JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class)
|
||||
@JsonProperty("emote-offhand-workaround")
|
||||
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;
|
||||
|
||||
@JsonProperty("allow-third-party-ears")
|
||||
private boolean allowThirdPartyEars = false;
|
||||
|
||||
@JsonProperty("default-locale")
|
||||
private String defaultLocale = null; // is null by default so system language takes priority
|
||||
|
||||
@JsonProperty("cache-images")
|
||||
private int cacheImages = 0;
|
||||
|
||||
@JsonProperty("allow-custom-skulls")
|
||||
private boolean allowCustomSkulls = true;
|
||||
|
||||
@JsonProperty("max-visible-custom-skulls")
|
||||
private int maxVisibleCustomSkulls = 128;
|
||||
|
||||
@JsonProperty("custom-skull-render-distance")
|
||||
private int customSkullRenderDistance = 32;
|
||||
|
||||
@JsonProperty("add-non-bedrock-items")
|
||||
private boolean addNonBedrockItems = true;
|
||||
|
||||
@JsonProperty("above-bedrock-nether-building")
|
||||
private boolean aboveBedrockNetherBuilding = false;
|
||||
|
||||
@JsonProperty("force-resource-packs")
|
||||
private boolean forceResourcePacks = true;
|
||||
|
||||
@JsonProperty("xbox-achievements-enabled")
|
||||
private boolean xboxAchievementsEnabled = false;
|
||||
|
||||
@JsonProperty("log-player-ip-addresses")
|
||||
private boolean logPlayerIpAddresses = true;
|
||||
|
||||
@JsonProperty("notify-on-new-bedrock-update")
|
||||
private boolean notifyOnNewBedrockUpdate = true;
|
||||
|
||||
@JsonProperty("unusable-space-block")
|
||||
private String unusableSpaceBlock = "minecraft:barrier";
|
||||
|
||||
private MetricsInfo metrics = new MetricsInfo();
|
||||
|
||||
@JsonProperty("pending-authentication-timeout")
|
||||
private int pendingAuthenticationTimeout = 120;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class BedrockConfiguration implements IBedrockConfiguration {
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonProperty("address")
|
||||
@Setter
|
||||
private String address = "0.0.0.0";
|
||||
|
||||
@Override
|
||||
public @NonNull String address() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@JsonProperty("port")
|
||||
private int port = 19132;
|
||||
|
||||
@Override
|
||||
public int port() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@JsonProperty("broadcast-port")
|
||||
private int broadcastPort = 0;
|
||||
|
||||
@Override
|
||||
public int broadcastPort() {
|
||||
return broadcastPort;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonProperty("clone-remote-port")
|
||||
private boolean cloneRemotePort = false;
|
||||
|
||||
@JsonProperty("motd1")
|
||||
private String motd1 = "GeyserMC";
|
||||
|
||||
@Override
|
||||
public String primaryMotd() {
|
||||
return motd1;
|
||||
}
|
||||
|
||||
@JsonProperty("motd2")
|
||||
private String motd2 = "Geyser";
|
||||
|
||||
@Override
|
||||
public String secondaryMotd() {
|
||||
return motd2;
|
||||
}
|
||||
|
||||
@JsonProperty("server-name")
|
||||
private String serverName = GeyserImpl.NAME;
|
||||
|
||||
@Override
|
||||
public String serverName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
@JsonProperty("compression-level")
|
||||
private int compressionLevel = 6;
|
||||
|
||||
public int getCompressionLevel() {
|
||||
return Math.max(-1, Math.min(compressionLevel, 9));
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonProperty("enable-proxy-protocol")
|
||||
private boolean enableProxyProtocol = false;
|
||||
|
||||
@Getter
|
||||
@JsonProperty("proxy-protocol-whitelisted-ips")
|
||||
private List<String> proxyProtocolWhitelistedIPs = Collections.emptyList();
|
||||
|
||||
@JsonIgnore
|
||||
private List<CIDRMatcher> whitelistedIPsMatchers = null;
|
||||
|
||||
@Override
|
||||
public List<CIDRMatcher> getWhitelistedIPsMatchers() {
|
||||
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
|
||||
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
|
||||
if (matchers == null) {
|
||||
synchronized (this) {
|
||||
// Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line
|
||||
List<String> whitelistedCIDRs = new ArrayList<>();
|
||||
for (String ip: proxyProtocolWhitelistedIPs) {
|
||||
if (!ip.startsWith("http")) {
|
||||
whitelistedCIDRs.add(ip);
|
||||
continue;
|
||||
}
|
||||
|
||||
WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add);
|
||||
}
|
||||
|
||||
this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream()
|
||||
.map(CIDRMatcher::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(matchers);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class RemoteConfiguration implements IRemoteConfiguration {
|
||||
@Setter
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonProperty("address")
|
||||
private String address = "auto";
|
||||
|
||||
@Override
|
||||
public String address() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@JsonDeserialize(using = PortDeserializer.class)
|
||||
@Setter
|
||||
@JsonProperty("port")
|
||||
private int port = 25565;
|
||||
|
||||
@Override
|
||||
public int port() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@JsonDeserialize(using = AuthTypeDeserializer.class)
|
||||
@JsonProperty("auth-type")
|
||||
private AuthType authType = AuthType.ONLINE;
|
||||
|
||||
@Override
|
||||
public @NonNull AuthType authType() {
|
||||
return authType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolveSrv() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonProperty("use-proxy-protocol")
|
||||
private boolean useProxyProtocol = false;
|
||||
|
||||
@Getter
|
||||
@JsonProperty("forward-hostname")
|
||||
private boolean forwardHost = false;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class MetricsInfo implements IMetricsInfo {
|
||||
private boolean enabled = true;
|
||||
|
||||
@JsonDeserialize(using = MetricsIdDeserializer.class)
|
||||
@JsonProperty("uuid")
|
||||
private String uniqueId = UUID.randomUUID().toString();
|
||||
|
||||
private static class MetricsIdDeserializer extends JsonDeserializer<String> {
|
||||
@Override
|
||||
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
String uuid = p.getValueAsString();
|
||||
if ("generateduuid".equals(uuid)) {
|
||||
// Compensate for configs not copied from the jar
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("scoreboard-packet-threshold")
|
||||
private int scoreboardPacketThreshold = 10;
|
||||
|
||||
@JsonProperty("enable-proxy-connections")
|
||||
private boolean enableProxyConnections = false;
|
||||
|
||||
@JsonProperty("mtu")
|
||||
private int mtu = 1400;
|
||||
|
||||
@JsonProperty("use-direct-connection")
|
||||
private boolean useDirectConnection = true;
|
||||
|
||||
@JsonProperty("disable-compression")
|
||||
private boolean isDisableCompression = true;
|
||||
|
||||
@JsonProperty("config-version")
|
||||
private int configVersion = 0;
|
||||
|
||||
/**
|
||||
* Ensure that the port deserializes in the config as a number no matter what.
|
||||
*/
|
||||
protected static class PortDeserializer extends JsonDeserializer<Integer> {
|
||||
@Override
|
||||
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
String value = p.getValueAsString();
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.invalid_port"));
|
||||
return 25565;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthTypeDeserializer extends JsonDeserializer<AuthType> {
|
||||
@Override
|
||||
public AuthType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return AuthType.getByName(p.getValueAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import org.spongepowered.configurate.interfaces.meta.Exclude;
|
||||
import org.spongepowered.configurate.interfaces.meta.Field;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public interface GeyserPluginConfig extends GeyserConfig {
|
||||
@Override
|
||||
IntegratedJavaConfig java();
|
||||
|
||||
@Override
|
||||
PluginMotdConfig motd();
|
||||
|
||||
@ConfigSerializable
|
||||
interface PluginMotdConfig extends MotdConfig {
|
||||
@Comment("""
|
||||
How often to ping the Java server to refresh MOTD and player count, in seconds.
|
||||
Only relevant if integrated-ping-passthrough is disabled.""")
|
||||
@Override
|
||||
int pingPassthroughInterval();
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
interface IntegratedJavaConfig extends JavaConfig {
|
||||
@Override
|
||||
@Field
|
||||
String address();
|
||||
|
||||
@Override
|
||||
void address(String address);
|
||||
|
||||
@Override
|
||||
@Field
|
||||
int port();
|
||||
|
||||
@Override
|
||||
void port(int port);
|
||||
|
||||
@Override
|
||||
@Exclude
|
||||
default boolean forwardHostname() {
|
||||
return true; // No need to worry about suspicious behavior flagging the server.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric;
|
||||
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString;
|
||||
import org.spongepowered.configurate.interfaces.meta.range.NumericRange;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
/**
|
||||
* Used for any instance where the Java server is detached from Geyser.
|
||||
*/
|
||||
@ConfigSerializable
|
||||
public interface GeyserRemoteConfig extends GeyserConfig {
|
||||
@Override
|
||||
RemoteConfig java();
|
||||
|
||||
@ConfigSerializable
|
||||
interface RemoteConfig extends JavaConfig {
|
||||
@Override
|
||||
@Comment("The IP address of the Java Edition server.")
|
||||
@DefaultString("127.0.0.1")
|
||||
@AsteriskSerializer.Asterisk
|
||||
String address();
|
||||
|
||||
@Override
|
||||
@Comment("The port of the Java Edition server.")
|
||||
@DefaultNumeric(25565)
|
||||
@NumericRange(from = 0, to = 65535)
|
||||
int port();
|
||||
|
||||
@Override
|
||||
@Comment("""
|
||||
Whether to forward the hostname that the Bedrock client used to connect over to the Java server.
|
||||
This is designed to be used for forced hosts on proxies.""")
|
||||
boolean forwardHostname();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.Scalars;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Ensures enum values are written to lowercase. {@link Scalars#ENUM} will read enum values
|
||||
* in any case.
|
||||
*/
|
||||
final class LowercaseEnumSerializer extends ScalarSerializer<Enum<?>> {
|
||||
LowercaseEnumSerializer() {
|
||||
super(new TypeToken<Enum<?>>() {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum<?> deserialize(Type type, Object obj) throws SerializationException {
|
||||
return Scalars.ENUM.deserialize(type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serialize(Enum<?> item, Predicate<Class<?>> typeSupported) {
|
||||
return item.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,26 +25,17 @@
|
||||
|
||||
package org.geysermc.geyser.configuration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public enum EmoteOffhandWorkaroundOption {
|
||||
NO_EMOTES,
|
||||
EMOTES_AND_OFFHAND,
|
||||
DISABLED;
|
||||
|
||||
public static class Deserializer extends JsonDeserializer<EmoteOffhandWorkaroundOption> {
|
||||
@Override
|
||||
public EmoteOffhandWorkaroundOption deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
String value = p.getValueAsString();
|
||||
return switch (value) {
|
||||
case "no-emotes" -> NO_EMOTES;
|
||||
case "emotes-and-offhand" -> EMOTES_AND_OFFHAND;
|
||||
default -> DISABLED;
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add to a config value to indicate this field is only for plugin versions of Geyser,
|
||||
* or vice-versa.
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PluginSpecific {
|
||||
boolean forPlugin() default true;
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.dump;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
@@ -38,7 +39,7 @@ public class BootstrapDumpInfo {
|
||||
private final PlatformType platform;
|
||||
|
||||
public BootstrapDumpInfo() {
|
||||
this.platform = GeyserImpl.getInstance().getPlatformType();
|
||||
this.platform = GeyserImpl.getInstance().platformType();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -55,7 +56,7 @@ public class BootstrapDumpInfo {
|
||||
@AllArgsConstructor
|
||||
public static class ListenerInfo {
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
@JsonAdapter(value = AsteriskSerializer.class)
|
||||
public String ip;
|
||||
public int port;
|
||||
}
|
||||
|
||||
@@ -25,28 +25,39 @@
|
||||
|
||||
package org.geysermc.geyser.dump;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.floodgate.util.DeviceOs;
|
||||
import org.geysermc.floodgate.util.FloodgateInfoHolder;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.geysermc.geyser.util.CpuUtils;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -57,12 +68,15 @@ import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
public class DumpInfo {
|
||||
@JsonIgnore
|
||||
private static final long MEGABYTE = 1024L * 1024L;
|
||||
|
||||
private final DumpInfo.VersionInfo versionInfo;
|
||||
@@ -71,16 +85,17 @@ public class DumpInfo {
|
||||
private final Locale systemLocale;
|
||||
private final String systemEncoding;
|
||||
private final GitInfo gitInfo;
|
||||
private final GeyserConfiguration config;
|
||||
private final Floodgate floodgate;
|
||||
private Object config;
|
||||
private final Object2IntMap<DeviceOs> userPlatforms;
|
||||
private final int connectionAttempts;
|
||||
private final HashInfo hashInfo;
|
||||
private final String hash;
|
||||
private final RamInfo ramInfo;
|
||||
private LogsInfo logsInfo;
|
||||
private final BootstrapDumpInfo bootstrapInfo;
|
||||
private final FlagsInfo flagsInfo;
|
||||
private final List<ExtensionInfo> extensionInfo;
|
||||
private final List<PackInfo> packInfo;
|
||||
private final MappingInfo mappingInfo;
|
||||
|
||||
public DumpInfo(GeyserImpl geyser, boolean addLog) {
|
||||
this.versionInfo = new VersionInfo();
|
||||
@@ -92,27 +107,35 @@ public class DumpInfo {
|
||||
|
||||
this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY);
|
||||
|
||||
this.config = geyser.getConfig();
|
||||
this.floodgate = new Floodgate();
|
||||
|
||||
String md5Hash = "unknown";
|
||||
String sha256Hash = "unknown";
|
||||
try {
|
||||
// https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file
|
||||
// https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java
|
||||
File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
ByteSource byteSource = Files.asByteSource(file);
|
||||
// Jenkins uses MD5 for its hash - TODO remove
|
||||
//noinspection UnstableApiUsage,deprecation
|
||||
md5Hash = byteSource.hash(Hashing.md5()).toString();
|
||||
//noinspection UnstableApiUsage
|
||||
sha256Hash = byteSource.hash(Hashing.sha256()).toString();
|
||||
} catch (Exception e) {
|
||||
if (this.config.isDebugMode()) {
|
||||
// Workaround for JsonAdapter not being allowed on methods
|
||||
ConfigurationOptions options = InterfaceDefaultOptions.addTo(ConfigurationOptions.defaults(), builder ->
|
||||
builder.addProcessor(AsteriskSerializer.Asterisk.class, String.class, AsteriskSerializer.CONFIGURATE_SERIALIZER))
|
||||
.shouldCopyDefaults(false);
|
||||
|
||||
ConfigurationNode configNode = CommentedConfigurationNode.root(options);
|
||||
configNode.set(geyser.config());
|
||||
this.config = toGson(configNode);
|
||||
} catch (SerializationException e) {
|
||||
e.printStackTrace();
|
||||
if (geyser.config().debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.hashInfo = new HashInfo(md5Hash, sha256Hash);
|
||||
|
||||
String sha256Hash = "unknown";
|
||||
try {
|
||||
// https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file
|
||||
File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
ByteSource byteSource = Files.asByteSource(file);
|
||||
//noinspection UnstableApiUsage
|
||||
sha256Hash = byteSource.hash(Hashing.sha256()).toString();
|
||||
} catch (Exception e) {
|
||||
if (geyser.config().debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.hash = sha256Hash;
|
||||
|
||||
this.ramInfo = new RamInfo();
|
||||
|
||||
@@ -140,6 +163,44 @@ public class DumpInfo {
|
||||
for (Extension extension : GeyserApi.api().extensionManager().extensions()) {
|
||||
this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().apiVersion(), extension.description().main(), extension.description().authors()));
|
||||
}
|
||||
|
||||
this.packInfo = Registries.RESOURCE_PACKS.get().values().stream()
|
||||
.map(PackInfo::new)
|
||||
.toList();
|
||||
this.mappingInfo = new MappingInfo(BlockRegistries.CUSTOM_BLOCKS.get().length,
|
||||
BlockRegistries.CUSTOM_SKULLS.get().size(),
|
||||
Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_PROTOCOL).getCustomIdMappings().size()
|
||||
);
|
||||
}
|
||||
|
||||
private JsonElement toGson(ConfigurationNode node) {
|
||||
if (node.isMap()) {
|
||||
JsonObject object = new JsonObject();
|
||||
node.childrenMap().forEach((key, value) -> {
|
||||
JsonElement json = toGson(value);
|
||||
object.add(key.toString(), json);
|
||||
});
|
||||
return object;
|
||||
} else if (node.isList()) {
|
||||
JsonArray array = new JsonArray();
|
||||
node.childrenList().forEach(childNode -> array.add(toGson(childNode)));
|
||||
return array;
|
||||
} else {
|
||||
return convertRawScalar(node);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonElement convertRawScalar(ConfigurationNode node) {
|
||||
final @Nullable Object value = node.rawScalar();
|
||||
if (value == null) {
|
||||
return JsonNull.INSTANCE;
|
||||
} else if (value instanceof Number n) {
|
||||
return new JsonPrimitive(n);
|
||||
} else if (value instanceof Boolean b) {
|
||||
return new JsonPrimitive(b);
|
||||
} else {
|
||||
return new JsonPrimitive(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -233,17 +294,6 @@ public class DumpInfo {
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Floodgate {
|
||||
private final Properties gitInfo;
|
||||
private final Object config;
|
||||
|
||||
Floodgate() {
|
||||
this.gitInfo = FloodgateInfoHolder.getGitProperties();
|
||||
this.config = FloodgateInfoHolder.getConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class LogsInfo {
|
||||
private String link;
|
||||
@@ -253,16 +303,13 @@ public class DumpInfo {
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put("content", FileUtils.readAllLines(geyser.getBootstrap().getLogsPath()).collect(Collectors.joining("\n")));
|
||||
|
||||
JsonNode logData = GeyserImpl.JSON_MAPPER.readTree(WebUtils.postForm("https://api.mclo.gs/1/log", fields));
|
||||
JsonObject logData = new JsonParser().parse(WebUtils.postForm("https://api.mclo.gs/1/log", fields)).getAsJsonObject();
|
||||
|
||||
this.link = logData.get("url").textValue();
|
||||
this.link = logData.get("url").getAsString();
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
public record HashInfo(String md5Hash, String sha256Hash) {
|
||||
}
|
||||
|
||||
public record RamInfo(long free, long total, long max) {
|
||||
public RamInfo() {
|
||||
this(Runtime.getRuntime().freeMemory() / MEGABYTE,
|
||||
@@ -283,7 +330,17 @@ public class DumpInfo {
|
||||
public record ExtensionInfo(boolean enabled, String name, String version, String apiVersion, String main, List<String> authors) {
|
||||
}
|
||||
|
||||
public record GitInfo(String buildNumber, @JsonProperty("git.commit.id.abbrev") String commitHashAbbrev, @JsonProperty("git.commit.id") String commitHash,
|
||||
@JsonProperty("git.branch") String branchName, @JsonProperty("git.remote.origin.url") String originUrl) {
|
||||
public record GitInfo(String buildNumber, @SerializedName("git.commit.id.abbrev") String commitHashAbbrev, @SerializedName("git.commit.id") String commitHash,
|
||||
@SerializedName("git.branch") String branchName, @SerializedName("git.remote.origin.url") String originUrl) {
|
||||
}
|
||||
|
||||
public record PackInfo(String name, String type) {
|
||||
|
||||
public PackInfo(ResourcePackHolder holder) {
|
||||
this(holder.pack().manifest().header().name(), holder.codec().getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
public record MappingInfo(int customBlocks, int customSkulls, int customItems) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
|
||||
|
||||
if (translator.acceptedType() != metadata.getType()) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Metadata ID " + metadata.getId() + " was received with type " + metadata.getType() + " but we expected " + translator.acceptedType() + " for " + entity.getDefinition().entityType());
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
if (GeyserImpl.getInstance().config().debugMode()) {
|
||||
GeyserImpl.getInstance().getLogger().debug(metadata.toString());
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -220,7 +220,7 @@ public class Entity implements GeyserEntity {
|
||||
|
||||
flagsDirty = false;
|
||||
|
||||
if (session.getGeyser().getConfig().isDebugMode() && PRINT_ENTITY_SPAWN_DEBUG) {
|
||||
if (session.getGeyser().config().debugMode() && PRINT_ENTITY_SPAWN_DEBUG) {
|
||||
EntityType type = definition.entityType();
|
||||
String name = type != null ? type.name() : getClass().getSimpleName();
|
||||
session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.geysermc.geyser.api.pack.exception.ResourcePackException;
|
||||
import org.geysermc.geyser.api.pack.option.ResourcePackOption;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.util.GeyserIntegratedPackUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -42,11 +43,12 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent {
|
||||
public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent implements GeyserIntegratedPackUtil {
|
||||
private final Map<UUID, ResourcePackHolder> packs;
|
||||
|
||||
public GeyserDefineResourcePacksEventImpl(Map<UUID, ResourcePackHolder> packMap) {
|
||||
this.packs = packMap;
|
||||
registerGeyserPack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,6 +63,8 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack
|
||||
throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION);
|
||||
}
|
||||
|
||||
preProcessPack(pack);
|
||||
|
||||
UUID uuid = resourcePack.uuid();
|
||||
if (packs.containsKey(uuid)) {
|
||||
throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE);
|
||||
@@ -120,4 +124,14 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack
|
||||
|
||||
holder.optionHolder().validateAndAdd(holder.pack(), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterIntegratedPack() {
|
||||
unregister(INTEGRATED_PACK_UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean integratedPackRegistered() {
|
||||
return packs.containsKey(INTEGRATED_PACK_UUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.pack.option.OptionHolder;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.GeyserIntegratedPackUtil;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
@@ -55,7 +56,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
|
||||
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent implements GeyserIntegratedPackUtil {
|
||||
|
||||
/**
|
||||
* The packs for this Session. A {@link ResourcePackHolder} may contain resource pack options registered
|
||||
@@ -103,6 +104,8 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION);
|
||||
}
|
||||
|
||||
preProcessPack(pack);
|
||||
|
||||
UUID uuid = resourcePack.uuid();
|
||||
if (packs.containsKey(uuid)) {
|
||||
throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE);
|
||||
@@ -178,6 +181,16 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
holder.validateAndAdd(pack, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterIntegratedPack() {
|
||||
unregister(INTEGRATED_PACK_UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean integratedPackRegistered() {
|
||||
return packs.containsKey(INTEGRATED_PACK_UUID);
|
||||
}
|
||||
|
||||
// Methods used internally for e.g. ordered packs, or resource pack entries
|
||||
|
||||
public List<ResourcePackStackPacket.Entry> orderedPacks() {
|
||||
@@ -201,7 +214,14 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
public List<ResourcePacksInfoPacket.Entry> infoPacketEntries() {
|
||||
List<ResourcePacksInfoPacket.Entry> entries = new ArrayList<>();
|
||||
|
||||
boolean anyCdn = packs.values().stream().anyMatch(holder -> holder.codec() instanceof UrlPackCodec);
|
||||
boolean warned = false;
|
||||
|
||||
for (ResourcePackHolder holder : packs.values()) {
|
||||
if (!warned && anyCdn && !(holder.codec() instanceof UrlPackCodec)) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Mixing pack codecs will result in all UrlPackCodec delivered packs to fall back to non-cdn delivery!");
|
||||
warned = true;
|
||||
}
|
||||
GeyserResourcePack pack = holder.pack();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
entries.add(new ResourcePacksInfoPacket.Entry(
|
||||
|
||||
@@ -460,7 +460,7 @@ public final class ClickPlan {
|
||||
} else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
|
||||
stateIdIncrements = 1;
|
||||
} else {
|
||||
if (session.getGeyser().getConfig().isDebugMode()) {
|
||||
if (session.getGeyser().config().debugMode()) {
|
||||
session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan);
|
||||
}
|
||||
stateIdIncrements = 1;
|
||||
|
||||
@@ -103,7 +103,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
super(geyser, session);
|
||||
|
||||
ZlibCompression compression = new ZlibCompression(Zlib.RAW);
|
||||
compression.setLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
|
||||
compression.setLevel(this.geyser.config().advanced().bedrock().compressionLevel());
|
||||
this.compressionStrategy = new SimpleCompressionStrategy(compression);
|
||||
}
|
||||
|
||||
@@ -236,14 +236,17 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
// Can happen if an error occurs in the resource pack event; that'll disconnect the player
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
session.integratedPackActive(resourcePackLoadEvent.isIntegratedPackActive());
|
||||
|
||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||
resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries());
|
||||
resourcePacksInfo.setVibrantVisualsForceDisabled(!session.isAllowVibrantVisuals());
|
||||
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().config().gameplay().forceResourcePacks() ||
|
||||
resourcePackLoadEvent.isIntegratedPackActive());
|
||||
resourcePacksInfo.setWorldTemplateId(UUID.randomUUID());
|
||||
resourcePacksInfo.setWorldTemplateVersion("*");
|
||||
|
||||
session.sendUpstreamPacket(resourcePacksInfo);
|
||||
|
||||
GeyserLocale.loadGeyserLocale(session.locale());
|
||||
@@ -264,7 +267,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
switch (packet.getStatus()) {
|
||||
case COMPLETED -> {
|
||||
finishedResourcePackSending = true;
|
||||
if (geyser.getConfig().getRemote().authType() != AuthType.ONLINE) {
|
||||
if (geyser.config().java().authType() != AuthType.ONLINE) {
|
||||
session.authenticate(session.getAuthData().name());
|
||||
} else if (!couldLoginUserByName(session.getAuthData().name())) {
|
||||
// We must spawn the white world
|
||||
@@ -296,6 +299,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
session.sendUpstreamPacket(stackPacket);
|
||||
}
|
||||
case REFUSED -> session.disconnect("disconnectionScreen.resourcePack");
|
||||
default -> {
|
||||
GeyserImpl.getInstance().getLogger().debug("received unknown status packet: " + packet);
|
||||
session.disconnect("disconnectionScreen.resourcePack");
|
||||
@@ -315,7 +319,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
}
|
||||
|
||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
||||
if (geyser.config().savedUserLogins().contains(bedrockUsername)) {
|
||||
String authChain = geyser.authChainFor(bedrockUsername);
|
||||
if (authChain != null) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
||||
@@ -425,7 +429,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
// Also flushes packets
|
||||
// Avoids bursting slower / delayed clients
|
||||
session.sendUpstreamPacketImmediately(data);
|
||||
GeyserImpl.getInstance().getScheduledThread().schedule(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS);
|
||||
session.scheduleInEventLoop(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
session.sendUpstreamPacket(data);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class GeyserInjector {
|
||||
* @param bootstrap the bootstrap of the Geyser instance.
|
||||
*/
|
||||
public void initializeLocalChannel(GeyserBootstrap bootstrap) {
|
||||
if (!bootstrap.getGeyserConfig().isUseDirectConnection()) {
|
||||
if (!bootstrap.config().advanced().java().useDirectConnection()) {
|
||||
bootstrap.getGeyserLogger().debug("Disabling direct injection!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.cloudburstmc.protocol.bedrock.BedrockPong;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.connection.ConnectionRequestEvent;
|
||||
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.event.type.GeyserBedrockPingEventImpl;
|
||||
import org.geysermc.geyser.network.CIDRMatcher;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
@@ -60,10 +60,13 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.geysermc.mcprotocollib.network.helper.TransportHelper;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -131,7 +134,7 @@ public final class GeyserServer {
|
||||
this.listenCount = 1;
|
||||
}
|
||||
|
||||
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
if (this.geyser.config().advanced().bedrock().useHaproxyProtocol()) {
|
||||
this.proxiedAddresses = ExpiringMap.builder()
|
||||
.expiration(30 + 1, TimeUnit.MINUTES)
|
||||
.expirationPolicy(ExpirationPolicy.ACCESSED).build();
|
||||
@@ -139,7 +142,7 @@ public final class GeyserServer {
|
||||
this.proxiedAddresses = null;
|
||||
}
|
||||
|
||||
this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort();
|
||||
this.broadcastPort = geyser.config().advanced().bedrock().broadcastPort();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> bind(InetSocketAddress address) {
|
||||
@@ -161,12 +164,12 @@ public final class GeyserServer {
|
||||
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
|
||||
|
||||
// Add proxy handler
|
||||
boolean isProxyProtocol = this.geyser.getConfig().getBedrock().isEnableProxyProtocol();
|
||||
boolean isProxyProtocol = this.geyser.config().advanced().bedrock().useHaproxyProtocol();
|
||||
if (isProxyProtocol) {
|
||||
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
|
||||
}
|
||||
|
||||
boolean isWhitelistedProxyProtocol = isProxyProtocol && !this.geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs().isEmpty();
|
||||
boolean isWhitelistedProxyProtocol = isProxyProtocol && !this.geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps().isEmpty();
|
||||
if (Boolean.parseBoolean(System.getProperty("Geyser.RakRateLimitingDisabled", "false")) || isWhitelistedProxyProtocol) {
|
||||
// We would already block any non-whitelisted IP addresses in onConnectionRequest so we can remove the rate limiter
|
||||
channel.pipeline().remove(RakServerRateLimiter.NAME);
|
||||
@@ -200,7 +203,7 @@ public final class GeyserServer {
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
private ServerBootstrap createBootstrap() {
|
||||
if (this.geyser.getConfig().isDebugMode()) {
|
||||
if (this.geyser.config().debugMode()) {
|
||||
this.geyser.getLogger().debug("Transport type: " + TRANSPORT.method().name());
|
||||
if (TRANSPORT.datagramChannelClass() == NioDatagramChannel.class) {
|
||||
if (System.getProperties().contains("disableNativeEventLoop")) {
|
||||
@@ -216,7 +219,7 @@ public final class GeyserServer {
|
||||
|
||||
GeyserServerInitializer serverInitializer = new GeyserServerInitializer(this.geyser);
|
||||
playerGroup = serverInitializer.getEventLoopGroup();
|
||||
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu());
|
||||
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.config().advanced().bedrock().mtu());
|
||||
|
||||
int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT);
|
||||
this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit);
|
||||
@@ -231,7 +234,7 @@ public final class GeyserServer {
|
||||
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannelClass()))
|
||||
.group(group, childGroup)
|
||||
.option(RakChannelOption.RAK_HANDLE_PING, true)
|
||||
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
|
||||
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.config().advanced().bedrock().mtu())
|
||||
.option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit)
|
||||
.option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit)
|
||||
.option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie)
|
||||
@@ -239,10 +242,10 @@ public final class GeyserServer {
|
||||
}
|
||||
|
||||
public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
|
||||
List<String> allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs();
|
||||
if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) {
|
||||
List<String> allowedProxyIPs = geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps();
|
||||
if (geyser.config().advanced().bedrock().useHaproxyProtocol() && !allowedProxyIPs.isEmpty()) {
|
||||
boolean isWhitelistedIP = false;
|
||||
for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) {
|
||||
for (CIDRMatcher matcher : getWhitelistedIPsMatchers()) {
|
||||
if (matcher.matches(inetSocketAddress.getAddress())) {
|
||||
isWhitelistedIP = true;
|
||||
break;
|
||||
@@ -256,8 +259,8 @@ public final class GeyserServer {
|
||||
}
|
||||
|
||||
String ip;
|
||||
if (geyser.getConfig().isLogPlayerIpAddresses()) {
|
||||
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
if (geyser.config().logPlayerIpAddresses()) {
|
||||
if (geyser.config().advanced().bedrock().useHaproxyProtocol()) {
|
||||
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
|
||||
} else {
|
||||
ip = inetSocketAddress.toString();
|
||||
@@ -283,10 +286,10 @@ public final class GeyserServer {
|
||||
}
|
||||
|
||||
public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) {
|
||||
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
|
||||
if (geyser.config().debugMode() && PRINT_DEBUG_PINGS) {
|
||||
String ip;
|
||||
if (geyser.getConfig().isLogPlayerIpAddresses()) {
|
||||
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
if (geyser.config().logPlayerIpAddresses()) {
|
||||
if (geyser.config().advanced().bedrock().useHaproxyProtocol()) {
|
||||
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
|
||||
} else {
|
||||
ip = inetSocketAddress.toString();
|
||||
@@ -297,10 +300,10 @@ public final class GeyserServer {
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip));
|
||||
}
|
||||
|
||||
GeyserConfiguration config = geyser.getConfig();
|
||||
GeyserConfig config = geyser.config();
|
||||
|
||||
GeyserPingInfo pingInfo = null;
|
||||
if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) {
|
||||
if (config.motd().passthroughMotd() || config.motd().passthroughPlayerCounts()) {
|
||||
IGeyserPingPassthrough pingPassthrough = geyser.getBootstrap().getGeyserPingPassthrough();
|
||||
if (pingPassthrough != null) {
|
||||
pingInfo = pingPassthrough.getPingInformation(inetSocketAddress);
|
||||
@@ -317,25 +320,25 @@ public final class GeyserServer {
|
||||
.ipv6Port(this.broadcastPort)
|
||||
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID));
|
||||
|
||||
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||
if (config.motd().passthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
||||
String mainMotd = (motd.length > 0) ? motd[0] : config.getBedrock().primaryMotd(); // First line of the motd.
|
||||
String subMotd = (motd.length > 1) ? motd[1] : config.getBedrock().secondaryMotd(); // Second line of the motd if present, otherwise default.
|
||||
String mainMotd = (motd.length > 0) ? motd[0] : config.motd().primaryMotd(); // First line of the motd.
|
||||
String subMotd = (motd.length > 1) ? motd[1] : config.motd().secondaryMotd(); // Second line of the motd if present, otherwise default.
|
||||
|
||||
pong.motd(mainMotd.trim());
|
||||
pong.subMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
|
||||
} else {
|
||||
pong.motd(config.getBedrock().primaryMotd());
|
||||
pong.subMotd(config.getBedrock().secondaryMotd());
|
||||
pong.motd(config.motd().primaryMotd());
|
||||
pong.subMotd(config.motd().secondaryMotd());
|
||||
}
|
||||
|
||||
// Placed here to prevent overriding values set in the ping event.
|
||||
if (config.isPassthroughPlayerCounts() && pingInfo != null) {
|
||||
if (config.motd().passthroughPlayerCounts() && pingInfo != null) {
|
||||
pong.playerCount(pingInfo.getPlayers().getOnline());
|
||||
pong.maximumPlayerCount(pingInfo.getPlayers().getMax());
|
||||
} else {
|
||||
pong.playerCount(geyser.getSessionManager().getSessions().size());
|
||||
pong.maximumPlayerCount(config.getMaxPlayers());
|
||||
pong.maximumPlayerCount(config.motd().maxPlayers());
|
||||
}
|
||||
|
||||
this.geyser.eventBus().fire(new GeyserBedrockPingEventImpl(pong, inetSocketAddress));
|
||||
@@ -386,6 +389,35 @@ public final class GeyserServer {
|
||||
return pong;
|
||||
}
|
||||
|
||||
private List<CIDRMatcher> whitelistedIPsMatchers = null;
|
||||
|
||||
/**
|
||||
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link GeyserConfig.AdvancedBedrockConfig#haproxyProtocolWhitelistedIps()}
|
||||
*/
|
||||
public List<CIDRMatcher> getWhitelistedIPsMatchers() {
|
||||
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
|
||||
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
|
||||
if (matchers == null) {
|
||||
synchronized (this) {
|
||||
// Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line
|
||||
List<String> whitelistedCIDRs = new ArrayList<>();
|
||||
for (String ip: geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps()) {
|
||||
if (!ip.startsWith("http")) {
|
||||
whitelistedCIDRs.add(ip);
|
||||
continue;
|
||||
}
|
||||
|
||||
WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add);
|
||||
}
|
||||
|
||||
this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream()
|
||||
.map(CIDRMatcher::new)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(matchers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the throwable from the given supplier, or the throwable caught while calling the supplier.
|
||||
*/
|
||||
|
||||
@@ -25,21 +25,23 @@
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
public record GeyserResourcePackManifest(
|
||||
@JsonProperty("format_version") int formatVersion,
|
||||
@SerializedName("format_version") int formatVersion,
|
||||
Header header,
|
||||
Collection<Module> modules,
|
||||
Collection<Dependency> dependencies,
|
||||
@@ -60,13 +62,13 @@ public record GeyserResourcePackManifest(
|
||||
this.settings = ensureNonNull(settings);
|
||||
}
|
||||
|
||||
public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
|
||||
public record Header(UUID uuid, Version version, String name, String description, @SerializedName("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
|
||||
|
||||
public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
|
||||
|
||||
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { }
|
||||
|
||||
public record Subpack(@JsonProperty("folder_name") String folderName, String name, @JsonProperty("memory_tier") Float memoryTier) implements ResourcePackManifest.Subpack { }
|
||||
public record Subpack(@SerializedName("folder_name") String folderName, String name, @SerializedName("memory_tier") Float memoryTier) implements ResourcePackManifest.Subpack { }
|
||||
|
||||
public record Setting(String type, String text) implements ResourcePackManifest.Setting { }
|
||||
|
||||
@@ -75,7 +77,7 @@ public record GeyserResourcePackManifest(
|
||||
return Collections.unmodifiableCollection(collection);
|
||||
}
|
||||
|
||||
@JsonDeserialize(using = Version.VersionDeserializer.class)
|
||||
@JsonAdapter(value = Version.VersionDeserializer.class)
|
||||
public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
|
||||
|
||||
@Override
|
||||
@@ -83,11 +85,11 @@ public record GeyserResourcePackManifest(
|
||||
return major + "." + minor + "." + patch;
|
||||
}
|
||||
|
||||
public static class VersionDeserializer extends JsonDeserializer<Version> {
|
||||
public static class VersionDeserializer implements JsonDeserializer<Version> {
|
||||
@Override
|
||||
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
int[] version = ctxt.readValue(p, int[].class);
|
||||
return new Version(version[0], version[1], version[2]);
|
||||
public Version deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonArray array = json.getAsJsonArray();
|
||||
return new Version(array.get(0).getAsInt(), array.get(1).getAsInt(), array.get(2).getAsInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class SkullResourcePackManager {
|
||||
|
||||
Path packPath = cachePath.resolve("player_skulls.mcpack");
|
||||
File packFile = packPath.toFile();
|
||||
if (BlockRegistries.CUSTOM_SKULLS.get().isEmpty() || !GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
if (BlockRegistries.CUSTOM_SKULLS.get().isEmpty() || !GeyserImpl.getInstance().config().gameplay().enableCustomContent()) {
|
||||
packFile.delete(); // No need to keep resource pack
|
||||
return null;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public class SkullResourcePackManager {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unable to clean up skull skin cache.");
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
if (GeyserImpl.getInstance().config().debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,8 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.ping;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import io.netty.handler.codec.haproxy.HAProxyCommand;
|
||||
import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
|
||||
import io.netty.util.NetUtil;
|
||||
@@ -34,18 +33,29 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.util.VarInts;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable {
|
||||
public class GeyserLegacyPingPassthrough extends Thread implements IGeyserPingPassthrough, Runnable {
|
||||
private static final byte[] HAPROXY_BINARY_PREFIX = new byte[]{13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10};
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private final long interval;
|
||||
|
||||
public GeyserLegacyPingPassthrough(GeyserImpl geyser) {
|
||||
public GeyserLegacyPingPassthrough(GeyserImpl geyser, int interval) {
|
||||
this.geyser = geyser;
|
||||
this.interval = interval * 1000L;
|
||||
}
|
||||
|
||||
private GeyserPingInfo pingInfo;
|
||||
@@ -56,12 +66,14 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
|
||||
* @return GeyserPingPassthrough, or null if not initialized
|
||||
*/
|
||||
public static @Nullable IGeyserPingPassthrough init(GeyserImpl geyser) {
|
||||
if (geyser.getConfig().isPassthroughMotd() || geyser.getConfig().isPassthroughPlayerCounts()) {
|
||||
GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser);
|
||||
if (geyser.config().motd().passthroughMotd() || geyser.config().motd().passthroughPlayerCounts()) {
|
||||
// Ensure delay is not zero
|
||||
int interval = (geyser.getConfig().getPingPassthroughInterval() == 0) ? 1 : geyser.getConfig().getPingPassthroughInterval();
|
||||
int interval = (geyser.config().motd().pingPassthroughInterval() == 0) ? 1 : geyser.config().motd().pingPassthroughInterval();
|
||||
geyser.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s).");
|
||||
geyser.getScheduledThread().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS);
|
||||
GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser, interval);
|
||||
pingPassthrough.setName("Geyser LegacyPingPassthrough Thread");
|
||||
pingPassthrough.setDaemon(true);
|
||||
pingPassthrough.start();
|
||||
return pingPassthrough;
|
||||
}
|
||||
return null;
|
||||
@@ -74,9 +86,10 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!geyser.isShuttingDown() && !geyser.isReloading()) {
|
||||
try (Socket socket = new Socket()) {
|
||||
String address = geyser.getConfig().getRemote().address();
|
||||
int port = geyser.getConfig().getRemote().port();
|
||||
String address = geyser.config().java().address();
|
||||
int port = geyser.config().java().port();
|
||||
InetSocketAddress endpoint = new InetSocketAddress(address, port);
|
||||
socket.connect(endpoint, 5000);
|
||||
|
||||
@@ -93,7 +106,7 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
|
||||
byte[] buffer;
|
||||
|
||||
try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream())) {
|
||||
if (geyser.getConfig().getRemote().isUseProxyProtocol()) {
|
||||
if (geyser.config().advanced().java().useHaproxyProtocol()) {
|
||||
// HAProxy support
|
||||
// Based on https://github.com/netty/netty/blob/d8ad931488f6b942dabe28ecd6c399b4438da0a8/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessageEncoder.java#L78
|
||||
dataOutputStream.write(HAPROXY_BINARY_PREFIX);
|
||||
@@ -130,11 +143,11 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
|
||||
}
|
||||
}
|
||||
|
||||
this.pingInfo = GeyserImpl.JSON_MAPPER.readValue(buffer, GeyserPingInfo.class);
|
||||
this.pingInfo = JsonUtils.fromJson(buffer, GeyserPingInfo.class);
|
||||
} catch (SocketTimeoutException | ConnectException ex) {
|
||||
this.pingInfo = null;
|
||||
this.geyser.getLogger().debug("Connection timeout for ping passthrough.");
|
||||
} catch (JsonParseException | JsonMappingException ex) {
|
||||
} catch (JsonSyntaxException ex) {
|
||||
this.geyser.getLogger().error("Failed to parse json when pinging server!", ex);
|
||||
} catch (EOFException e) {
|
||||
this.pingInfo = null;
|
||||
@@ -145,5 +158,12 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
|
||||
} catch (IOException e) {
|
||||
this.geyser.getLogger().error("IO error while trying to use legacy ping passthrough", e);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
} catch (InterruptedException ex) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,21 +25,25 @@
|
||||
|
||||
package org.geysermc.geyser.ping;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.Data;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* The structure of this class and its nested classes are specifically
|
||||
* designed for the format received by {@link GeyserLegacyPingPassthrough}.
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GeyserPingInfo {
|
||||
|
||||
@Nullable
|
||||
@JsonAdapter(DescriptionDeserializer.class)
|
||||
private String description;
|
||||
|
||||
private Players players;
|
||||
@@ -58,13 +62,7 @@ public class GeyserPingInfo {
|
||||
this.players = new Players(maxPlayers, onlinePlayers);
|
||||
}
|
||||
|
||||
@JsonSetter("description")
|
||||
void setDescription(JsonNode description) {
|
||||
this.description = description.toString();
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Players {
|
||||
|
||||
private int max;
|
||||
@@ -79,4 +77,14 @@ public class GeyserPingInfo {
|
||||
this.online = online;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* So GSON does not complain how we are treating Description - it will be converted to a proper Component later.
|
||||
*/
|
||||
private static final class DescriptionDeserializer implements JsonDeserializer<String> {
|
||||
@Override
|
||||
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return json.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
if (GeyserImpl.getInstance().config().debugMode()) {
|
||||
if (!IGNORED_PACKETS.contains(clazz)) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
|
||||
}
|
||||
|
||||
@@ -25,14 +25,16 @@
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Object2IntMap<String>> {
|
||||
@@ -43,12 +45,12 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Obj
|
||||
// crashes the client. Therefore, we need to have a list of all valid Bedrock biome IDs with which we can use from.
|
||||
// The server sends the corresponding Java network IDs, so we don't need to worry about that now.
|
||||
|
||||
// Reference variable for Jackson to read off of
|
||||
TypeReference<Map<String, BiomeEntry>> biomeEntriesType = new TypeReference<>() { };
|
||||
// Reference variable for Gson to read off of
|
||||
Type biomeEntriesType = new TypeToken<Map<String, BiomeEntry>>() { }.getType();
|
||||
Map<String, BiomeEntry> biomeEntries;
|
||||
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/biomes.json")) {
|
||||
biomeEntries = GeyserImpl.JSON_MAPPER.readValue(stream, biomeEntriesType);
|
||||
biomeEntries = JsonUtils.fromJson(stream, biomeEntriesType);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Unable to load Bedrock runtime biomes", e);
|
||||
}
|
||||
@@ -66,7 +68,7 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Obj
|
||||
/**
|
||||
* The Bedrock network ID for this biome.
|
||||
*/
|
||||
@JsonProperty("bedrock_id")
|
||||
@SerializedName("bedrock_id")
|
||||
private int bedrockId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* An abstract registry loader for loading effects from a resource path.
|
||||
@@ -38,21 +38,14 @@ import java.util.WeakHashMap;
|
||||
* @param <T> the value
|
||||
*/
|
||||
public abstract class EffectRegistryLoader<T> implements RegistryLoader<String, T> {
|
||||
private static final Map<String, JsonNode> loadedFiles = new WeakHashMap<>();
|
||||
|
||||
public void loadFile(String input) {
|
||||
if (!loadedFiles.containsKey(input)) {
|
||||
JsonNode effects;
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) {
|
||||
effects = GeyserImpl.JSON_MAPPER.readTree(stream);
|
||||
public JsonObject loadFile(String input) {
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input);
|
||||
InputStreamReader reader = new InputStreamReader(stream)) {
|
||||
//noinspection deprecation
|
||||
return new JsonParser().parse(reader).getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load registrations for " + input, e);
|
||||
}
|
||||
loadedFiles.put(input, effects);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonNode get(String input) {
|
||||
return loadedFiles.get(input);
|
||||
}
|
||||
}
|
||||
@@ -25,15 +25,15 @@
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.type.ParticleMapping;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -44,16 +44,13 @@ public class ParticleTypesRegistryLoader extends EffectRegistryLoader<Map<Partic
|
||||
|
||||
@Override
|
||||
public Map<ParticleType, ParticleMapping> load(String input) {
|
||||
this.loadFile(input);
|
||||
|
||||
Iterator<Map.Entry<String, JsonNode>> particlesIterator = this.get(input).fields();
|
||||
JsonObject particlesJson = this.loadFile(input);
|
||||
Map<ParticleType, ParticleMapping> particles = new Object2ObjectOpenHashMap<>();
|
||||
try {
|
||||
while (particlesIterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = particlesIterator.next();
|
||||
for (Map.Entry<String, JsonElement> entry : particlesJson.entrySet()) {
|
||||
String key = entry.getKey().toUpperCase(Locale.ROOT);
|
||||
JsonNode bedrockId = entry.getValue().get("bedrockId");
|
||||
JsonNode eventType = entry.getValue().get("eventType");
|
||||
JsonElement bedrockId = entry.getValue().getAsJsonObject().get("bedrockId");
|
||||
JsonElement eventType = entry.getValue().getAsJsonObject().get("eventType");
|
||||
if (eventType == null && bedrockId == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Skipping particle mapping " + key + " because no Bedrock equivalent exists.");
|
||||
continue;
|
||||
@@ -63,16 +60,16 @@ public class ParticleTypesRegistryLoader extends EffectRegistryLoader<Map<Partic
|
||||
if (eventType != null) {
|
||||
try {
|
||||
// Check if we have a particle type mapping
|
||||
type = org.cloudburstmc.protocol.bedrock.data.ParticleType.valueOf(eventType.asText().toUpperCase(Locale.ROOT));
|
||||
type = org.cloudburstmc.protocol.bedrock.data.ParticleType.valueOf(eventType.getAsString().toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// No particle type; try level event
|
||||
type = LevelEvent.valueOf(eventType.asText().toUpperCase(Locale.ROOT));
|
||||
type = LevelEvent.valueOf(eventType.getAsString().toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
particles.put(ParticleType.valueOf(key), new ParticleMapping(
|
||||
type,
|
||||
bedrockId == null ? null : bedrockId.asText())
|
||||
bedrockId == null ? null : bedrockId.getAsString())
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -124,20 +124,19 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
GeyserDefineResourcePacksEventImpl defineEvent = new GeyserDefineResourcePacksEventImpl(packMap);
|
||||
|
||||
for (Path path : event.resourcePacks()) {
|
||||
try {
|
||||
GeyserResourcePack pack = readPack(path).build();
|
||||
packMap.put(pack.uuid(), ResourcePackHolder.of(pack));
|
||||
defineEvent.register(readPack(path).build());
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path));
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Load all remote resource packs from the config before firing the new event
|
||||
// TODO configurate
|
||||
//packMap.putAll(loadRemotePacks());
|
||||
|
||||
GeyserDefineResourcePacksEventImpl defineEvent = new GeyserDefineResourcePacksEventImpl(packMap);
|
||||
loadRemotePacks(defineEvent);
|
||||
GeyserImpl.getInstance().eventBus().fire(defineEvent);
|
||||
|
||||
// After loading the new resource packs: let's clean up the old url packs
|
||||
@@ -225,7 +224,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
}
|
||||
}
|
||||
|
||||
private Map<UUID, ResourcePackHolder> loadRemotePacks() {
|
||||
private void loadRemotePacks(GeyserDefineResourcePacksEventImpl event) {
|
||||
GeyserImpl instance = GeyserImpl.getInstance();
|
||||
// Unable to make this a static variable, as the test would fail
|
||||
final Path cachedDirectory = instance.getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
|
||||
@@ -235,19 +234,14 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
Files.createDirectories(cachedDirectory);
|
||||
} catch (IOException e) {
|
||||
instance.getLogger().error("Could not create remote pack cache directory", e);
|
||||
return new Object2ObjectOpenHashMap<>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//List<String> remotePackUrls = instance.getConfig().getResourcePackUrls();
|
||||
List<String> remotePackUrls = List.of();
|
||||
Map<UUID, ResourcePackHolder> packMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
List<String> remotePackUrls = instance.config().advanced().resourcePackUrls();
|
||||
for (String url : remotePackUrls) {
|
||||
try {
|
||||
GeyserUrlPackCodec codec = new GeyserUrlPackCodec(url);
|
||||
GeyserResourcePack pack = codec.create();
|
||||
packMap.put(pack.uuid(), ResourcePackHolder.of(pack));
|
||||
event.register(new GeyserUrlPackCodec(url).create());
|
||||
} catch (Throwable e) {
|
||||
instance.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url));
|
||||
instance.getLogger().error(e.getMessage());
|
||||
@@ -256,8 +250,6 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packMap;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
@@ -34,8 +34,8 @@ import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
|
||||
import org.geysermc.geyser.translator.level.event.PlaySoundEventTranslator;
|
||||
import org.geysermc.geyser.translator.level.event.SoundEventEventTranslator;
|
||||
import org.geysermc.geyser.translator.level.event.SoundLevelEventTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -47,37 +47,36 @@ public class SoundEventsRegistryLoader extends EffectRegistryLoader<Map<LevelEve
|
||||
public Map<LevelEvent, LevelEventTranslator> load(String input) {
|
||||
this.loadFile(input);
|
||||
|
||||
Iterator<Map.Entry<String, JsonNode>> effectsIterator = this.get(input).fields();
|
||||
JsonObject effectsJson = this.loadFile(input);
|
||||
Map<LevelEvent, LevelEventTranslator> soundEffects = new Object2ObjectOpenHashMap<>();
|
||||
while (effectsIterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = effectsIterator.next();
|
||||
JsonNode node = entry.getValue();
|
||||
for (Map.Entry<String, JsonElement> entry : effectsJson.entrySet()) {
|
||||
JsonObject node = entry.getValue().getAsJsonObject();
|
||||
try {
|
||||
String type = node.get("type").asText();
|
||||
String type = node.get("type").getAsString();
|
||||
LevelEvent javaEffect = null;
|
||||
LevelEventTranslator transformer = null;
|
||||
switch (type) {
|
||||
case "soundLevel" -> {
|
||||
javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
|
||||
LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").asText());
|
||||
int data = node.has("data") ? node.get("data").intValue() : 0;
|
||||
LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").getAsString());
|
||||
int data = node.has("data") ? node.get("data").getAsInt() : 0;
|
||||
transformer = new SoundLevelEventTranslator(levelEventType, data);
|
||||
}
|
||||
case "soundEvent" -> {
|
||||
javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
|
||||
org.cloudburstmc.protocol.bedrock.data.SoundEvent soundEvent = org.cloudburstmc.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").asText());
|
||||
String identifier = node.has("identifier") ? node.get("identifier").asText() : "";
|
||||
int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1;
|
||||
org.cloudburstmc.protocol.bedrock.data.SoundEvent soundEvent = org.cloudburstmc.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").getAsString());
|
||||
String identifier = node.has("identifier") ? node.get("identifier").getAsString() : "";
|
||||
int extraData = node.has("extraData") ? node.get("extraData").getAsInt() : -1;
|
||||
transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData);
|
||||
}
|
||||
case "playSound" -> {
|
||||
javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
|
||||
String name = node.get("name").asText();
|
||||
float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f;
|
||||
boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue();
|
||||
float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").floatValue() : 1.0f;
|
||||
float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").floatValue() : 0.0f;
|
||||
boolean relative = !node.has("relative") || node.get("relative").booleanValue();
|
||||
String name = node.get("name").getAsString();
|
||||
float volume = node.has("volume") ? node.get("volume").getAsFloat() : 1.0f;
|
||||
boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").getAsBoolean();
|
||||
float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").getAsFloat() : 1.0f;
|
||||
float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").getAsFloat() : 0.0f;
|
||||
boolean relative = !node.has("relative") || node.get("relative").getAsBoolean();
|
||||
transformer = new PlaySoundEventTranslator(name, volume, pitchSub, pitchMul, pitchAdd, relative);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,16 @@
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.type.SoundMapping;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -42,27 +43,27 @@ import java.util.Map;
|
||||
public class SoundRegistryLoader implements RegistryLoader<String, Map<String, SoundMapping>> {
|
||||
@Override
|
||||
public Map<String, SoundMapping> load(String input) {
|
||||
JsonNode soundsTree;
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) {
|
||||
soundsTree = GeyserImpl.JSON_MAPPER.readTree(stream);
|
||||
JsonObject soundsJson;
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input);
|
||||
InputStreamReader isr = new InputStreamReader(stream)) {
|
||||
//noinspection deprecation
|
||||
soundsJson = new JsonParser().parse(isr).getAsJsonObject();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Unable to load sound mappings", e);
|
||||
}
|
||||
|
||||
Map<String, SoundMapping> soundMappings = new HashMap<>();
|
||||
Iterator<Map.Entry<String, JsonNode>> soundsIterator = soundsTree.fields();
|
||||
while (soundsIterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> next = soundsIterator.next();
|
||||
JsonNode brMap = next.getValue();
|
||||
String javaSound = next.getKey();
|
||||
for (Map.Entry<String, JsonElement> entry : soundsJson.entrySet()) {
|
||||
JsonObject brMap = entry.getValue().getAsJsonObject();
|
||||
String javaSound = entry.getKey();
|
||||
soundMappings.put(javaSound, new SoundMapping(
|
||||
javaSound,
|
||||
brMap.has("bedrock_mapping") && brMap.get("bedrock_mapping").isTextual() ? brMap.get("bedrock_mapping").asText() : null,
|
||||
brMap.has("playsound_mapping") && brMap.get("playsound_mapping").isTextual() ? brMap.get("playsound_mapping").asText() : null,
|
||||
brMap.has("extra_data") && brMap.get("extra_data").isInt() ? brMap.get("extra_data").asInt() : -1,
|
||||
brMap.has("identifier") && brMap.get("identifier").isTextual() ? brMap.get("identifier").asText() : null,
|
||||
brMap.has("level_event") && brMap.get("level_event").isBoolean() && brMap.get("level_event").asBoolean(),
|
||||
brMap.has("pitch_adjust") && brMap.get("pitch_adjust").isNumber() ? brMap.get("pitch_adjust").floatValue() : 1.0f
|
||||
brMap.has("bedrock_mapping") ? brMap.get("bedrock_mapping").getAsString() : null,
|
||||
brMap.has("playsound_mapping") ? brMap.get("playsound_mapping").getAsString() : null,
|
||||
brMap.has("extra_data") ? brMap.get("extra_data").getAsInt() : -1,
|
||||
brMap.has("identifier") ? brMap.get("identifier").getAsString() : null,
|
||||
brMap.has("level_event") && brMap.get("level_event").getAsBoolean(),
|
||||
brMap.has("pitch_adjust") ? brMap.get("pitch_adjust").getAsFloat() : 1.0f
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.registry.mappings;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@@ -35,6 +36,7 @@ import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping;
|
||||
import org.geysermc.geyser.registry.mappings.versions.MappingsReader;
|
||||
import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -95,10 +97,10 @@ public class MappingsConfigReader {
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable JsonNode getMappingsRoot(Path file) {
|
||||
JsonNode mappingsRoot;
|
||||
try {
|
||||
mappingsRoot = GeyserImpl.JSON_MAPPER.readTree(file.toFile());
|
||||
public @Nullable JsonObject getMappingsRoot(Path file) {
|
||||
JsonObject mappingsRoot;
|
||||
try (FileReader reader = new FileReader(file.toFile())) {
|
||||
mappingsRoot = (JsonObject) new JsonParser().parse(reader);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e);
|
||||
return null;
|
||||
@@ -112,8 +114,8 @@ public class MappingsConfigReader {
|
||||
return mappingsRoot;
|
||||
}
|
||||
|
||||
public int getFormatVersion(JsonNode mappingsRoot, Path file) {
|
||||
int formatVersion = mappingsRoot.get("format_version").asInt();
|
||||
public int getFormatVersion(JsonObject mappingsRoot, Path file) {
|
||||
int formatVersion = mappingsRoot.get("format_version").getAsInt();
|
||||
if (!this.mappingReaders.containsKey(formatVersion)) {
|
||||
GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion);
|
||||
return -1;
|
||||
@@ -122,7 +124,7 @@ public class MappingsConfigReader {
|
||||
}
|
||||
|
||||
public void readItemMappingsFromJson(Path file, BiConsumer<String, CustomItemData> consumer) {
|
||||
JsonNode mappingsRoot = getMappingsRoot(file);
|
||||
JsonObject mappingsRoot = getMappingsRoot(file);
|
||||
|
||||
if (mappingsRoot == null) {
|
||||
return;
|
||||
@@ -138,7 +140,7 @@ public class MappingsConfigReader {
|
||||
}
|
||||
|
||||
public void readBlockMappingsFromJson(Path file, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
JsonNode mappingsRoot = getMappingsRoot(file);
|
||||
JsonObject mappingsRoot = getMappingsRoot(file);
|
||||
|
||||
if (mappingsRoot == null) {
|
||||
return;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.registry.mappings.versions;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
|
||||
@@ -36,14 +36,14 @@ import java.nio.file.Path;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public abstract class MappingsReader {
|
||||
public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer);
|
||||
public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer);
|
||||
public abstract void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomItemData> consumer);
|
||||
public abstract void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer);
|
||||
|
||||
public abstract CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException;
|
||||
public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException;
|
||||
public abstract CustomItemData readItemMappingEntry(JsonObject node) throws InvalidCustomMappingsFileException;
|
||||
public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonObject node) throws InvalidCustomMappingsFileException;
|
||||
|
||||
protected @Nullable CustomRenderOffsets fromJsonNode(JsonNode node) {
|
||||
if (node == null || !node.isObject()) {
|
||||
protected @Nullable CustomRenderOffsets fromJsonObject(JsonObject node) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -53,9 +53,8 @@ public abstract class MappingsReader {
|
||||
);
|
||||
}
|
||||
|
||||
protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonNode node, String hand) {
|
||||
JsonNode tmpNode = node.get(hand);
|
||||
if (tmpNode == null || !tmpNode.isObject()) {
|
||||
protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonObject node, String hand) {
|
||||
if (!(node.get(hand) instanceof JsonObject tmpNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -65,9 +64,8 @@ public abstract class MappingsReader {
|
||||
);
|
||||
}
|
||||
|
||||
protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonNode node, String perspective) {
|
||||
JsonNode tmpNode = node.get(perspective);
|
||||
if (tmpNode == null || !tmpNode.isObject()) {
|
||||
protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonObject node, String perspective) {
|
||||
if (!(node.get(perspective) instanceof JsonObject tmpNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -78,9 +76,8 @@ public abstract class MappingsReader {
|
||||
);
|
||||
}
|
||||
|
||||
protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) {
|
||||
JsonNode tmpNode = node.get(offsetType);
|
||||
if (tmpNode == null || !tmpNode.isObject()) {
|
||||
protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonObject node, String offsetType) {
|
||||
if (!(node.get(offsetType) instanceof JsonObject tmpNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -89,9 +86,9 @@ public abstract class MappingsReader {
|
||||
}
|
||||
|
||||
return new CustomRenderOffsets.OffsetXYZ(
|
||||
tmpNode.get("x").floatValue(),
|
||||
tmpNode.get("y").floatValue(),
|
||||
tmpNode.get("z").floatValue()
|
||||
tmpNode.get("x").getAsFloat(),
|
||||
tmpNode.get("y").getAsFloat(),
|
||||
tmpNode.get("z").getAsFloat()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
|
||||
package org.geysermc.geyser.registry.mappings.versions;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@@ -34,9 +36,14 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.*;
|
||||
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.block.custom.component.GeometryComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face;
|
||||
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
@@ -57,7 +64,13 @@ import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@@ -68,7 +81,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class MappingsReader_v1 extends MappingsReader {
|
||||
@Override
|
||||
public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
public void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
this.readItemMappingsV1(file, mappingsRoot, consumer);
|
||||
}
|
||||
|
||||
@@ -76,24 +89,24 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
* Read item block from a JSON node
|
||||
*
|
||||
* @param file The path to the file
|
||||
* @param mappingsRoot The {@link JsonNode} containing the mappings
|
||||
* @param mappingsRoot The {@link JsonObject} containing the mappings
|
||||
* @param consumer The consumer to accept the mappings
|
||||
* @see #readBlockMappingsV1(Path, JsonNode, BiConsumer)
|
||||
* @see #readBlockMappingsV1(Path, JsonObject, BiConsumer)
|
||||
*/
|
||||
@Override
|
||||
public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
public void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
this.readBlockMappingsV1(file, mappingsRoot, consumer);
|
||||
}
|
||||
|
||||
public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
JsonNode itemsNode = mappingsRoot.get("items");
|
||||
public void readItemMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
JsonObject itemsNode = mappingsRoot.getAsJsonObject("items");
|
||||
|
||||
if (itemsNode != null && itemsNode.isObject()) {
|
||||
itemsNode.fields().forEachRemaining(entry -> {
|
||||
if (entry.getValue().isArray()) {
|
||||
entry.getValue().forEach(data -> {
|
||||
if (itemsNode != null) {
|
||||
itemsNode.entrySet().forEach(entry -> {
|
||||
if (entry.getValue() instanceof JsonArray array) {
|
||||
array.forEach(data -> {
|
||||
try {
|
||||
CustomItemData customItemData = this.readItemMappingEntry(data);
|
||||
CustomItemData customItemData = this.readItemMappingEntry((JsonObject) data);
|
||||
consumer.accept(entry.getKey(), customItemData);
|
||||
} catch (InvalidCustomMappingsFileException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e);
|
||||
@@ -108,19 +121,17 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
* Read block mappings from a JSON node
|
||||
*
|
||||
* @param file The path to the file
|
||||
* @param mappingsRoot The {@link JsonNode} containing the mappings
|
||||
* @param mappingsRoot The {@link JsonObject} containing the mappings
|
||||
* @param consumer The consumer to accept the mappings
|
||||
* @see #readBlockMappings(Path, JsonNode, BiConsumer)
|
||||
* @see #readBlockMappings(Path, JsonObject, BiConsumer)
|
||||
*/
|
||||
public void readBlockMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
JsonNode blocksNode = mappingsRoot.get("blocks");
|
||||
|
||||
if (blocksNode != null && blocksNode.isObject()) {
|
||||
blocksNode.fields().forEachRemaining(entry -> {
|
||||
if (entry.getValue().isObject()) {
|
||||
public void readBlockMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
if (mappingsRoot.get("blocks") instanceof JsonObject blocksNode) {
|
||||
blocksNode.entrySet().forEach(entry -> {
|
||||
if (entry.getValue() instanceof JsonObject jsonObject) {
|
||||
try {
|
||||
String identifier = MinecraftKey.key(entry.getKey()).asString();
|
||||
CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue());
|
||||
CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, jsonObject);
|
||||
consumer.accept(identifier, customBlockMapping);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error in registering blocks for custom mapping file: " + file.toString());
|
||||
@@ -131,85 +142,85 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
}
|
||||
}
|
||||
|
||||
private CustomItemOptions readItemCustomItemOptions(JsonNode node) {
|
||||
private CustomItemOptions readItemCustomItemOptions(JsonObject node) {
|
||||
CustomItemOptions.Builder customItemOptions = CustomItemOptions.builder();
|
||||
|
||||
JsonNode customModelData = node.get("custom_model_data");
|
||||
if (customModelData != null && customModelData.isInt()) {
|
||||
customItemOptions.customModelData(customModelData.asInt());
|
||||
JsonElement customModelData = node.get("custom_model_data");
|
||||
if (customModelData != null && customModelData.isJsonPrimitive()) {
|
||||
customItemOptions.customModelData(customModelData.getAsInt());
|
||||
}
|
||||
|
||||
JsonNode damagePredicate = node.get("damage_predicate");
|
||||
if (damagePredicate != null && damagePredicate.isInt()) {
|
||||
customItemOptions.damagePredicate(damagePredicate.asInt());
|
||||
JsonElement damagePredicate = node.get("damage_predicate");
|
||||
if (damagePredicate != null && damagePredicate.isJsonPrimitive()) {
|
||||
customItemOptions.damagePredicate(damagePredicate.getAsInt());
|
||||
}
|
||||
|
||||
JsonNode unbreakable = node.get("unbreakable");
|
||||
if (unbreakable != null && unbreakable.isBoolean()) {
|
||||
customItemOptions.unbreakable(unbreakable.asBoolean());
|
||||
JsonElement unbreakable = node.get("unbreakable");
|
||||
if (unbreakable != null && unbreakable.isJsonPrimitive()) {
|
||||
customItemOptions.unbreakable(unbreakable.getAsBoolean());
|
||||
}
|
||||
|
||||
JsonNode defaultItem = node.get("default");
|
||||
if (defaultItem != null && defaultItem.isBoolean()) {
|
||||
customItemOptions.defaultItem(defaultItem.asBoolean());
|
||||
JsonElement defaultItem = node.get("default");
|
||||
if (defaultItem != null && defaultItem.isJsonPrimitive()) {
|
||||
customItemOptions.defaultItem(defaultItem.getAsBoolean());
|
||||
}
|
||||
|
||||
return customItemOptions.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null || !node.isObject()) {
|
||||
public CustomItemData readItemMappingEntry(JsonObject node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null) {
|
||||
throw new InvalidCustomMappingsFileException("Invalid item mappings entry");
|
||||
}
|
||||
|
||||
JsonNode name = node.get("name");
|
||||
if (name == null || !name.isTextual() || name.asText().isEmpty()) {
|
||||
JsonElement name = node.get("name");
|
||||
if (name == null || !name.isJsonPrimitive() || name.getAsString().isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("An item entry has no name");
|
||||
}
|
||||
|
||||
CustomItemData.Builder customItemData = CustomItemData.builder()
|
||||
.name(name.asText())
|
||||
.name(name.getAsString())
|
||||
.customItemOptions(this.readItemCustomItemOptions(node));
|
||||
|
||||
//The next entries are optional
|
||||
if (node.has("display_name")) {
|
||||
customItemData.displayName(node.get("display_name").asText());
|
||||
customItemData.displayName(node.get("display_name").getAsString());
|
||||
}
|
||||
|
||||
if (node.has("icon")) {
|
||||
customItemData.icon(node.get("icon").asText());
|
||||
customItemData.icon(node.get("icon").getAsString());
|
||||
}
|
||||
|
||||
if (node.has("creative_category")) {
|
||||
customItemData.creativeCategory(node.get("creative_category").asInt());
|
||||
customItemData.creativeCategory(node.get("creative_category").getAsInt());
|
||||
}
|
||||
|
||||
if (node.has("creative_group")) {
|
||||
customItemData.creativeGroup(node.get("creative_group").asText());
|
||||
customItemData.creativeGroup(node.get("creative_group").getAsString());
|
||||
}
|
||||
|
||||
if (node.has("allow_offhand")) {
|
||||
customItemData.allowOffhand(node.get("allow_offhand").asBoolean());
|
||||
customItemData.allowOffhand(node.get("allow_offhand").getAsBoolean());
|
||||
}
|
||||
|
||||
if (node.has("display_handheld")) {
|
||||
customItemData.displayHandheld(node.get("display_handheld").asBoolean());
|
||||
customItemData.displayHandheld(node.get("display_handheld").getAsBoolean());
|
||||
}
|
||||
|
||||
if (node.has("texture_size")) {
|
||||
customItemData.textureSize(node.get("texture_size").asInt());
|
||||
customItemData.textureSize(node.get("texture_size").getAsInt());
|
||||
}
|
||||
|
||||
if (node.has("render_offsets")) {
|
||||
JsonNode tmpNode = node.get("render_offsets");
|
||||
JsonObject tmpNode = node.getAsJsonObject("render_offsets");
|
||||
|
||||
customItemData.renderOffsets(fromJsonNode(tmpNode));
|
||||
customItemData.renderOffsets(fromJsonObject(tmpNode));
|
||||
}
|
||||
|
||||
if (node.get("tags") instanceof ArrayNode tags) {
|
||||
if (node.get("tags") instanceof JsonArray tags) {
|
||||
Set<String> tagsSet = new ObjectOpenHashSet<>();
|
||||
tags.forEach(tag -> tagsSet.add(tag.asText()));
|
||||
tags.forEach(tag -> tagsSet.add(tag.getAsString()));
|
||||
customItemData.tags(tagsSet);
|
||||
}
|
||||
|
||||
@@ -220,26 +231,26 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
* Read a block mapping entry from a JSON node and Java identifier
|
||||
*
|
||||
* @param identifier The Java identifier of the block
|
||||
* @param node The {@link JsonNode} containing the block mapping entry
|
||||
* @param node The {@link JsonObject} containing the block mapping entry
|
||||
* @return The {@link CustomBlockMapping} record to be read by {@link org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator}
|
||||
* @throws InvalidCustomMappingsFileException If the JSON node is invalid
|
||||
*/
|
||||
@Override
|
||||
public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null || !node.isObject()) {
|
||||
public CustomBlockMapping readBlockMappingEntry(String identifier, JsonObject node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null) {
|
||||
throw new InvalidCustomMappingsFileException("Invalid block mappings entry:" + node);
|
||||
}
|
||||
|
||||
String name = node.get("name").asText();
|
||||
String name = node.get("name").getAsString();
|
||||
if (name == null || name.isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("A block entry has no name");
|
||||
}
|
||||
|
||||
boolean includedInCreativeInventory = node.has("included_in_creative_inventory") && node.get("included_in_creative_inventory").asBoolean();
|
||||
boolean includedInCreativeInventory = node.has("included_in_creative_inventory") && node.get("included_in_creative_inventory").getAsBoolean();
|
||||
|
||||
CreativeCategory creativeCategory = CreativeCategory.NONE;
|
||||
if (node.has("creative_category")) {
|
||||
String categoryName = node.get("creative_category").asText();
|
||||
String categoryName = node.get("creative_category").getAsString();
|
||||
try {
|
||||
creativeCategory = CreativeCategory.valueOf(categoryName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -249,11 +260,11 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
|
||||
String creativeGroup = "";
|
||||
if (node.has("creative_group")) {
|
||||
creativeGroup = node.get("creative_group").asText();
|
||||
creativeGroup = node.get("creative_group").getAsString();
|
||||
}
|
||||
|
||||
// If this is true, we will only register the states the user has specified rather than all the possible block states
|
||||
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean();
|
||||
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").getAsBoolean();
|
||||
|
||||
// Create the data for the overall block
|
||||
CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder()
|
||||
@@ -273,12 +284,9 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
|
||||
Map<String, CustomBlockComponentsMapping> componentsMap = new LinkedHashMap<>();
|
||||
|
||||
JsonNode stateOverrides = node.get("state_overrides");
|
||||
if (stateOverrides != null && stateOverrides.isObject()) {
|
||||
if (node.get("state_overrides") instanceof JsonObject stateOverrides) {
|
||||
// Load components for specific Java block states
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = stateOverrides.fields();
|
||||
while (fields.hasNext()) {
|
||||
Map.Entry<String, JsonNode> overrideEntry = fields.next();
|
||||
for (Map.Entry<String, JsonElement> overrideEntry : stateOverrides.entrySet()) {
|
||||
String state = identifier + "[" + overrideEntry.getKey() + "]";
|
||||
if (!BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().containsKey(state)) {
|
||||
throw new InvalidCustomMappingsFileException("Unknown Java block state: " + state + " for state_overrides.");
|
||||
@@ -358,12 +366,12 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
/**
|
||||
* Creates a {@link CustomBlockComponents} object for the passed state override or base block node, Java block state identifier, and custom block name
|
||||
*
|
||||
* @param node the state override or base block {@link JsonNode}
|
||||
* @param element the state override or base block {@link JsonObject}
|
||||
* @param stateKey the Java block state identifier
|
||||
* @param name the name of the custom block
|
||||
* @return the {@link CustomBlockComponents} object
|
||||
*/
|
||||
private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode node, String stateKey, String name) {
|
||||
private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonElement element, String stateKey, String name) {
|
||||
// This is needed to find the correct selection box for the given block
|
||||
int id = BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1);
|
||||
BoxComponent boxComponent = createBoxComponent(id);
|
||||
@@ -372,7 +380,7 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
.collisionBox(boxComponent)
|
||||
.selectionBox(boxComponent);
|
||||
|
||||
if (node == null) {
|
||||
if (!(element instanceof JsonObject node)) {
|
||||
// No other components were defined
|
||||
return new CustomBlockComponentsMapping(builder.build(), extendedBoxComponent);
|
||||
}
|
||||
@@ -394,28 +402,28 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
// We set this to max value by default so that we may dictate the correct destroy time ourselves
|
||||
float destructibleByMining = Float.MAX_VALUE;
|
||||
if (node.has("destructible_by_mining")) {
|
||||
destructibleByMining = node.get("destructible_by_mining").floatValue();
|
||||
destructibleByMining = node.get("destructible_by_mining").getAsFloat();
|
||||
}
|
||||
builder.destructibleByMining(destructibleByMining);
|
||||
|
||||
if (node.has("geometry")) {
|
||||
if (node.get("geometry").isTextual()) {
|
||||
if (node.get("geometry").isJsonPrimitive()) {
|
||||
builder.geometry(new GeyserGeometryComponent.Builder()
|
||||
.identifier(node.get("geometry").asText())
|
||||
.identifier(node.get("geometry").getAsString())
|
||||
.build());
|
||||
} else {
|
||||
JsonNode geometry = node.get("geometry");
|
||||
JsonObject geometry = node.getAsJsonObject("geometry");
|
||||
GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder();
|
||||
if (geometry.has("identifier")) {
|
||||
geometryBuilder.identifier(geometry.get("identifier").asText());
|
||||
geometryBuilder.identifier(geometry.get("identifier").getAsString());
|
||||
}
|
||||
if (geometry.has("bone_visibility")) {
|
||||
JsonNode boneVisibility = geometry.get("bone_visibility");
|
||||
if (boneVisibility.isObject()) {
|
||||
if (geometry.get("bone_visibility") instanceof JsonObject boneVisibility) {
|
||||
Map<String, String> boneVisibilityMap = new Object2ObjectOpenHashMap<>();
|
||||
boneVisibility.fields().forEachRemaining(entry -> {
|
||||
boneVisibility.entrySet().forEach(entry -> {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue().isBoolean() ? (entry.getValue().asBoolean() ? "1" : "0") : entry.getValue().asText();
|
||||
String value = entry.getValue() instanceof JsonPrimitive primitive && primitive.isBoolean()
|
||||
? (entry.getValue().getAsBoolean() ? "1" : "0") : entry.getValue().getAsString();
|
||||
boneVisibilityMap.put(key, value);
|
||||
});
|
||||
geometryBuilder.boneVisibility(boneVisibilityMap);
|
||||
@@ -427,30 +435,30 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
|
||||
String displayName = name;
|
||||
if (node.has("display_name")) {
|
||||
displayName = node.get("display_name").asText();
|
||||
displayName = node.get("display_name").getAsString();
|
||||
}
|
||||
builder.displayName(displayName);
|
||||
|
||||
if (node.has("friction")) {
|
||||
builder.friction(node.get("friction").floatValue());
|
||||
builder.friction(node.get("friction").getAsFloat());
|
||||
}
|
||||
|
||||
if (node.has("light_emission")) {
|
||||
builder.lightEmission(node.get("light_emission").asInt());
|
||||
builder.lightEmission(node.get("light_emission").getAsInt());
|
||||
}
|
||||
|
||||
if (node.has("light_dampening")) {
|
||||
builder.lightDampening(node.get("light_dampening").asInt());
|
||||
builder.lightDampening(node.get("light_dampening").getAsInt());
|
||||
}
|
||||
|
||||
boolean placeAir = true;
|
||||
if (node.has("place_air")) {
|
||||
placeAir = node.get("place_air").asBoolean();
|
||||
placeAir = node.get("place_air").getAsBoolean();
|
||||
}
|
||||
builder.placeAir(placeAir);
|
||||
|
||||
if (node.has("transformation")) {
|
||||
JsonNode transformation = node.get("transformation");
|
||||
JsonObject transformation = node.getAsJsonObject("transformation");
|
||||
|
||||
int rotationX = 0;
|
||||
int rotationY = 0;
|
||||
@@ -463,22 +471,22 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
float transformZ = 0;
|
||||
|
||||
if (transformation.has("rotation")) {
|
||||
JsonNode rotation = transformation.get("rotation");
|
||||
rotationX = rotation.get(0).asInt();
|
||||
rotationY = rotation.get(1).asInt();
|
||||
rotationZ = rotation.get(2).asInt();
|
||||
JsonArray rotation = transformation.getAsJsonArray("rotation");
|
||||
rotationX = rotation.get(0).getAsInt();
|
||||
rotationY = rotation.get(1).getAsInt();
|
||||
rotationZ = rotation.get(2).getAsInt();
|
||||
}
|
||||
if (transformation.has("scale")) {
|
||||
JsonNode scale = transformation.get("scale");
|
||||
scaleX = scale.get(0).floatValue();
|
||||
scaleY = scale.get(1).floatValue();
|
||||
scaleZ = scale.get(2).floatValue();
|
||||
JsonArray scale = transformation.getAsJsonArray("scale");
|
||||
scaleX = scale.get(0).getAsFloat();
|
||||
scaleY = scale.get(1).getAsFloat();
|
||||
scaleZ = scale.get(2).getAsFloat();
|
||||
}
|
||||
if (transformation.has("translation")) {
|
||||
JsonNode translation = transformation.get("translation");
|
||||
transformX = translation.get(0).floatValue();
|
||||
transformY = translation.get(1).floatValue();
|
||||
transformZ = translation.get(2).floatValue();
|
||||
JsonArray translation = transformation.getAsJsonArray("translation");
|
||||
transformX = translation.get(0).getAsFloat();
|
||||
transformY = translation.get(1).getAsFloat();
|
||||
transformZ = translation.get(2).getAsFloat();
|
||||
}
|
||||
builder.transformation(new TransformationComponent(rotationX, rotationY, rotationZ, scaleX, scaleY, scaleZ, transformX, transformY, transformZ));
|
||||
}
|
||||
@@ -490,12 +498,10 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
}
|
||||
|
||||
if (node.has("material_instances")) {
|
||||
JsonNode materialInstances = node.get("material_instances");
|
||||
if (materialInstances.isObject()) {
|
||||
materialInstances.fields().forEachRemaining(entry -> {
|
||||
if (node.get("material_instances") instanceof JsonObject materialInstances) {
|
||||
materialInstances.entrySet().forEach(entry -> {
|
||||
String key = entry.getKey();
|
||||
JsonNode value = entry.getValue();
|
||||
if (value.isObject()) {
|
||||
if (entry.getValue() instanceof JsonObject value) {
|
||||
MaterialInstance materialInstance = createMaterialInstanceComponent(value);
|
||||
builder.materialInstance(key, materialInstance);
|
||||
}
|
||||
@@ -503,27 +509,21 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.has("placement_filter")) {
|
||||
JsonNode placementFilter = node.get("placement_filter");
|
||||
if (placementFilter.isObject()) {
|
||||
if (placementFilter.has("conditions")) {
|
||||
JsonNode conditions = placementFilter.get("conditions");
|
||||
if (conditions.isArray()) {
|
||||
if (node.get("placement_filter") instanceof JsonObject placementFilter) {
|
||||
if (placementFilter.get("conditions") instanceof JsonArray conditions) {
|
||||
List<PlacementConditions> filter = createPlacementFilterComponent(conditions);
|
||||
builder.placementFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tags can be applied so that blocks will match return true when queried for the tag
|
||||
// Potentially useful for resource pack creators
|
||||
// Ideally we could programmatically extract the tags here https://wiki.bedrock.dev/blocks/block-tags.html
|
||||
// This would let us automatically apply the correct vanilla tags to blocks
|
||||
// However, its worth noting that vanilla tools do not currently honor these tags anyway
|
||||
if (node.get("tags") instanceof ArrayNode tags) {
|
||||
if (node.get("tags") instanceof JsonArray tags) {
|
||||
Set<String> tagsSet = new ObjectOpenHashSet<>();
|
||||
tags.forEach(tag -> tagsSet.add(tag.asText()));
|
||||
tags.forEach(tag -> tagsSet.add(tag.getAsString()));
|
||||
builder.tags(tagsSet);
|
||||
}
|
||||
|
||||
@@ -613,21 +613,21 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
/**
|
||||
* Creates a {@link BoxComponent} from a JSON Node
|
||||
*
|
||||
* @param node the JSON node
|
||||
* @param element the JSON node
|
||||
* @return the {@link BoxComponent}
|
||||
*/
|
||||
private @Nullable BoxComponent createBoxComponent(JsonNode node) {
|
||||
if (node != null && node.isObject()) {
|
||||
private @Nullable BoxComponent createBoxComponent(JsonElement element) {
|
||||
if (element instanceof JsonObject node) {
|
||||
if (node.has("origin") && node.has("size")) {
|
||||
JsonNode origin = node.get("origin");
|
||||
float originX = origin.get(0).floatValue();
|
||||
float originY = origin.get(1).floatValue();
|
||||
float originZ = origin.get(2).floatValue();
|
||||
JsonArray origin = node.getAsJsonArray("origin");
|
||||
float originX = origin.get(0).getAsFloat();
|
||||
float originY = origin.get(1).getAsFloat();
|
||||
float originZ = origin.get(2).getAsFloat();
|
||||
|
||||
JsonNode size = node.get("size");
|
||||
float sizeX = size.get(0).floatValue();
|
||||
float sizeY = size.get(1).floatValue();
|
||||
float sizeZ = size.get(2).floatValue();
|
||||
JsonArray size = node.getAsJsonArray("size");
|
||||
float sizeX = size.get(0).getAsFloat();
|
||||
float sizeY = size.get(1).getAsFloat();
|
||||
float sizeZ = size.get(2).getAsFloat();
|
||||
|
||||
return new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
@@ -642,26 +642,26 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
* @param node the material instance node
|
||||
* @return the {@link MaterialInstance}
|
||||
*/
|
||||
private MaterialInstance createMaterialInstanceComponent(JsonNode node) {
|
||||
private MaterialInstance createMaterialInstanceComponent(JsonObject node) {
|
||||
// Set default values, and use what the user provides if they have provided something
|
||||
String texture = null;
|
||||
if (node.has("texture")) {
|
||||
texture = node.get("texture").asText();
|
||||
texture = node.get("texture").getAsString();
|
||||
}
|
||||
|
||||
String renderMethod = "opaque";
|
||||
if (node.has("render_method")) {
|
||||
renderMethod = node.get("render_method").asText();
|
||||
renderMethod = node.get("render_method").getAsString();
|
||||
}
|
||||
|
||||
boolean faceDimming = true;
|
||||
if (node.has("face_dimming")) {
|
||||
faceDimming = node.get("face_dimming").asBoolean();
|
||||
faceDimming = node.get("face_dimming").getAsBoolean();
|
||||
}
|
||||
|
||||
boolean ambientOcclusion = true;
|
||||
if (node.has("ambient_occlusion")) {
|
||||
ambientOcclusion = node.get("ambient_occlusion").asBoolean();
|
||||
ambientOcclusion = node.get("ambient_occlusion").getAsBoolean();
|
||||
}
|
||||
|
||||
return new GeyserMaterialInstance.Builder()
|
||||
@@ -678,32 +678,33 @@ public class MappingsReader_v1 extends MappingsReader {
|
||||
* @param node the conditions node
|
||||
* @return the list of {@link PlacementConditions}
|
||||
*/
|
||||
private List<PlacementConditions> createPlacementFilterComponent(JsonNode node) {
|
||||
private List<PlacementConditions> createPlacementFilterComponent(JsonArray node) {
|
||||
List<PlacementConditions> conditions = new ArrayList<>();
|
||||
|
||||
// The structure of the placement filter component is the most complex of the current components
|
||||
// Each condition effectively separated into two arrays: one of allowed faces, and one of blocks/block Molang queries
|
||||
node.forEach(condition -> {
|
||||
node.forEach(json -> {
|
||||
if (!(json instanceof JsonObject condition)) {
|
||||
return;
|
||||
}
|
||||
Set<Face> faces = EnumSet.noneOf(Face.class);
|
||||
if (condition.has("allowed_faces")) {
|
||||
JsonNode allowedFaces = condition.get("allowed_faces");
|
||||
if (allowedFaces.isArray()) {
|
||||
allowedFaces.forEach(face -> faces.add(Face.valueOf(face.asText().toUpperCase())));
|
||||
if (condition.get("allowed_faces") instanceof JsonArray allowedFaces) {
|
||||
allowedFaces.forEach(face -> faces.add(Face.valueOf(face.getAsString().toUpperCase())));
|
||||
}
|
||||
}
|
||||
|
||||
LinkedHashMap<String, BlockFilterType> blockFilters = new LinkedHashMap<>();
|
||||
if (condition.has("block_filter")) {
|
||||
JsonNode blockFilter = condition.get("block_filter");
|
||||
if (blockFilter.isArray()) {
|
||||
if (condition.get("block_filter") instanceof JsonArray blockFilter) {
|
||||
blockFilter.forEach(filter -> {
|
||||
if (filter.isObject()) {
|
||||
if (filter.has("tags")) {
|
||||
JsonNode tags = filter.get("tags");
|
||||
blockFilters.put(tags.asText(), BlockFilterType.TAG);
|
||||
if (filter instanceof JsonObject jsonObject) {
|
||||
if (jsonObject.has("tags")) {
|
||||
JsonElement tags = jsonObject.get("tags");
|
||||
blockFilters.put(tags.getAsString(), BlockFilterType.TAG);
|
||||
}
|
||||
} else if (filter.isTextual()) {
|
||||
blockFilters.put(filter.asText(), BlockFilterType.BLOCK);
|
||||
} else if (filter instanceof JsonPrimitive primitive && primitive.isString()) {
|
||||
blockFilters.put(filter.getAsString(), BlockFilterType.BLOCK);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
|
||||
package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Interner;
|
||||
import com.google.common.collect.Interners;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
@@ -65,6 +66,7 @@ import org.geysermc.geyser.registry.populator.conversion.Conversion827_819;
|
||||
import org.geysermc.geyser.registry.populator.conversion.Conversion844_827;
|
||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||
import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -441,21 +443,21 @@ public final class BlockRegistryPopulator {
|
||||
BLOCKS_NBT = blocksNbt;
|
||||
JAVA_BLOCKS_SIZE = blocksNbt.size();
|
||||
|
||||
JsonNode blockInteractionsJson;
|
||||
JsonObject blockInteractionsJson;
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/interactions.json")) {
|
||||
blockInteractionsJson = GeyserImpl.JSON_MAPPER.readTree(stream);
|
||||
blockInteractionsJson = JsonUtils.fromJson(stream);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load Java block interaction mappings", e);
|
||||
}
|
||||
|
||||
BlockRegistries.INTERACTIVE.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("always_consumes")));
|
||||
BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("requires_may_build")));
|
||||
BlockRegistries.INTERACTIVE.set(toBlockStateSet(blockInteractionsJson.getAsJsonArray("always_consumes")));
|
||||
BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet(blockInteractionsJson.getAsJsonArray("requires_may_build")));
|
||||
}
|
||||
|
||||
private static BitSet toBlockStateSet(ArrayNode node) {
|
||||
private static BitSet toBlockStateSet(JsonArray node) {
|
||||
BitSet blockStateSet = new BitSet(node.size());
|
||||
for (JsonNode javaIdentifier : node) {
|
||||
blockStateSet.set(BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.textValue()));
|
||||
for (JsonElement javaIdentifier : node) {
|
||||
blockStateSet.set(BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.getAsString()));
|
||||
}
|
||||
return blockStateSet;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
@@ -40,6 +42,7 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||
import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
import org.geysermc.geyser.registry.type.GeyserMappingItem;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -62,20 +65,21 @@ public class CreativeItemRegistryPopulator {
|
||||
static List<CreativeItemGroup> readCreativeItemGroups(ItemRegistryPopulator.PaletteVersion palette, List<CreativeItemData> creativeItemData) {
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
JsonNode creativeItemEntries;
|
||||
JsonArray creativeItemEntries;
|
||||
try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/creative_items.%s.json", palette.version()))) {
|
||||
creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("groups");
|
||||
creativeItemEntries = JsonUtils.fromJson(stream).getAsJsonArray("groups");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load creative item groups", e);
|
||||
}
|
||||
|
||||
List<CreativeItemGroup> creativeItemGroups = new ArrayList<>();
|
||||
for (JsonNode creativeItemEntry : creativeItemEntries) {
|
||||
CreativeItemCategory category = CreativeItemCategory.valueOf(creativeItemEntry.get("category").asText().toUpperCase(Locale.ROOT));
|
||||
String name = creativeItemEntry.get("name").asText();
|
||||
for (JsonElement creativeItemEntry : creativeItemEntries) {
|
||||
JsonObject creativeItemEntryObject = creativeItemEntry.getAsJsonObject();
|
||||
CreativeItemCategory category = CreativeItemCategory.valueOf(creativeItemEntryObject.get("category").getAsString().toUpperCase(Locale.ROOT));
|
||||
String name = creativeItemEntryObject.get("name").getAsString();
|
||||
|
||||
JsonNode icon = creativeItemEntry.get("icon");
|
||||
String identifier = icon.get("id").asText();
|
||||
JsonElement icon = creativeItemEntryObject.get("icon");
|
||||
String identifier = icon.getAsJsonObject().get("id").getAsString();
|
||||
|
||||
ItemData itemData;
|
||||
if (identifier.equals("minecraft:air")) {
|
||||
@@ -98,32 +102,33 @@ public class CreativeItemRegistryPopulator {
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
// Load creative items
|
||||
JsonNode creativeItemEntries;
|
||||
JsonArray creativeItemEntries;
|
||||
try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/creative_items.%s.json", palette.version()))) {
|
||||
creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items");
|
||||
creativeItemEntries = JsonUtils.fromJson(stream).getAsJsonArray("items");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load creative items", e);
|
||||
}
|
||||
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
|
||||
for (JsonNode itemNode : creativeItemEntries) {
|
||||
ItemData.Builder itemBuilder = createItemData(itemNode, items, blockMappings, definitions);
|
||||
for (JsonElement itemNode : creativeItemEntries) {
|
||||
ItemData.Builder itemBuilder = createItemData((JsonObject) itemNode, items, blockMappings, definitions);
|
||||
if (itemBuilder == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int groupId = itemNode.get("groupId") != null ? itemNode.get("groupId").asInt() : 0;
|
||||
var groupIdElement = itemNode.getAsJsonObject().get("groupId");
|
||||
int groupId = groupIdElement != null ? groupIdElement.getAsInt() : 0;
|
||||
|
||||
itemConsumer.accept(itemBuilder, groupId);
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, Map<String, GeyserMappingItem> items, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
|
||||
private static ItemData.@Nullable Builder createItemData(JsonObject itemNode, Map<String, GeyserMappingItem> items, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
|
||||
int count = 1;
|
||||
int damage = 0;
|
||||
NbtMap tag = null;
|
||||
|
||||
String identifier = itemNode.get("id").textValue();
|
||||
String identifier = itemNode.get("id").getAsString();
|
||||
for (BiPredicate<String, Integer> predicate : JAVA_ONLY_ITEM_FILTER) {
|
||||
if (predicate.test(identifier, damage)) {
|
||||
return null;
|
||||
@@ -150,20 +155,20 @@ public class CreativeItemRegistryPopulator {
|
||||
// }
|
||||
}
|
||||
|
||||
JsonNode damageNode = itemNode.get("damage");
|
||||
JsonElement damageNode = itemNode.get("damage");
|
||||
if (damageNode != null) {
|
||||
damage = damageNode.asInt();
|
||||
damage = damageNode.getAsInt();
|
||||
}
|
||||
|
||||
JsonNode countNode = itemNode.get("count");
|
||||
JsonElement countNode = itemNode.get("count");
|
||||
if (countNode != null) {
|
||||
count = countNode.asInt();
|
||||
count = countNode.getAsInt();
|
||||
}
|
||||
|
||||
GeyserBedrockBlock blockDefinition = null;
|
||||
JsonNode blockStateNode;
|
||||
JsonElement blockStateNode;
|
||||
if ((blockStateNode = itemNode.get("block_state_b64")) != null) {
|
||||
byte[] bytes = Base64.getDecoder().decode(blockStateNode.asText());
|
||||
byte[] bytes = Base64.getDecoder().decode(blockStateNode.getAsString());
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
try {
|
||||
NbtMap stateTag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
|
||||
@@ -182,9 +187,9 @@ public class CreativeItemRegistryPopulator {
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode nbtNode = itemNode.get("nbt_b64");
|
||||
JsonElement nbtNode = itemNode.get("nbt_b64");
|
||||
if (nbtNode != null) {
|
||||
byte[] bytes = Base64.getDecoder().decode(nbtNode.asText());
|
||||
byte[] bytes = Base64.getDecoder().decode(nbtNode.getAsString());
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
try {
|
||||
tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
|
||||
|
||||
@@ -104,7 +104,7 @@ public class CustomBlockRegistryPopulator {
|
||||
* @param stage the stage to populate
|
||||
*/
|
||||
public static void populate(Stage stage) {
|
||||
if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
if (!GeyserImpl.getInstance().config().gameplay().enableCustomContent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public class CustomSkullRegistryPopulator {
|
||||
SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading
|
||||
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
|
||||
|
||||
if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
if (!GeyserImpl.getInstance().config().gameplay().enableCustomContent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
@@ -82,8 +82,10 @@ import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
|
||||
import org.geysermc.geyser.registry.type.PaletteItem;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -201,17 +203,17 @@ public class ItemRegistryPopulator {
|
||||
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
TypeReference<Map<String, GeyserMappingItem>> mappingItemsType = new TypeReference<>() { };
|
||||
Type mappingItemsType = new TypeToken<Map<String, GeyserMappingItem>>() { }.getType();
|
||||
|
||||
Map<String, GeyserMappingItem> items;
|
||||
try (InputStream stream = bootstrap.getResourceOrThrow("mappings/items.json")) {
|
||||
// Load item mappings from Java Edition to Bedrock Edition
|
||||
items = GeyserImpl.JSON_MAPPER.readValue(stream, mappingItemsType);
|
||||
items = JsonUtils.fromJson(stream, mappingItemsType);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load Java runtime item IDs", e);
|
||||
}
|
||||
|
||||
boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems();
|
||||
boolean customItemsAllowed = GeyserImpl.getInstance().config().gameplay().enableCustomContent();
|
||||
|
||||
// List values here is important compared to HashSet - we need to preserve the order of what's given to us
|
||||
// (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom
|
||||
@@ -228,11 +230,11 @@ public class ItemRegistryPopulator {
|
||||
|
||||
/* Load item palette */
|
||||
for (PaletteVersion palette : paletteVersions) {
|
||||
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
|
||||
Type paletteEntriesType = new TypeToken<List<PaletteItem>>() { }.getType();
|
||||
|
||||
List<PaletteItem> itemEntries;
|
||||
try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/runtime_item_states.%s.json", palette.version()))) {
|
||||
itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType);
|
||||
itemEntries = JsonUtils.fromJson(stream, paletteEntriesType);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load Bedrock runtime item IDs", e);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.registry.type;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
@@ -43,13 +43,13 @@ import lombok.With;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class GeyserMappingItem {
|
||||
@JsonProperty("bedrock_identifier") String bedrockIdentifier;
|
||||
@JsonProperty("bedrock_data") int bedrockData;
|
||||
@SerializedName("bedrock_identifier") String bedrockIdentifier;
|
||||
@SerializedName("bedrock_data") int bedrockData;
|
||||
Integer firstBlockRuntimeId;
|
||||
Integer lastBlockRuntimeId;
|
||||
@JsonProperty("tool_type") String toolType;
|
||||
@JsonProperty("armor_type") String armorType;
|
||||
@JsonProperty("protection_value") int protectionValue;
|
||||
@JsonProperty("is_edible") boolean edible = false;
|
||||
@JsonProperty("is_entity_placer") boolean entityPlacer = false;
|
||||
@SerializedName("tool_type") String toolType;
|
||||
@SerializedName("armor_type") String armorType;
|
||||
@SerializedName("protection_value") int protectionValue;
|
||||
@SerializedName("is_edible") boolean edible = false;
|
||||
@SerializedName("is_entity_placer") boolean entityPlacer = false;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,9 @@ import static org.geysermc.geyser.scoreboard.UpdateType.REMOVE;
|
||||
*/
|
||||
public final class Scoreboard {
|
||||
private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true"));
|
||||
private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean(System.getProperty("Geyser.AddTeamSuggestions", "true"));
|
||||
private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean(
|
||||
System.getProperty("Geyser.AddTeamSuggestions", String.valueOf(GeyserImpl.getInstance().config().advanced().addTeamSuggestions()))
|
||||
);
|
||||
|
||||
private final GeyserSession session;
|
||||
private final GeyserLogger logger;
|
||||
|
||||
@@ -28,7 +28,7 @@ package org.geysermc.geyser.scoreboard;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.WorldCache;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
@@ -46,9 +46,9 @@ public final class ScoreboardUpdater extends Thread {
|
||||
private static final boolean DEBUG_ENABLED;
|
||||
|
||||
static {
|
||||
GeyserConfiguration config = GeyserImpl.getInstance().getConfig();
|
||||
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
|
||||
DEBUG_ENABLED = config.isDebugMode();
|
||||
GeyserConfig config = GeyserImpl.getInstance().config();
|
||||
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.advanced().scoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
|
||||
DEBUG_ENABLED = config.debugMode();
|
||||
}
|
||||
|
||||
private final GeyserImpl geyser = GeyserImpl.getInstance();
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
package org.geysermc.geyser.session;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.EventLoop;
|
||||
@@ -125,8 +124,7 @@ import org.geysermc.geyser.api.skin.SkinData;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.GeyserEntityData;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
@@ -256,8 +254,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
@Getter
|
||||
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private final UpstreamSession upstream;
|
||||
private DownstreamSession downstream;
|
||||
@@ -715,7 +711,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
/**
|
||||
* A cache of IDs from ClientboundKeepAlivePackets that have been sent to the Bedrock client, but haven't been returned to the server.
|
||||
* Only used if {@link GeyserConfiguration#isForwardPlayerPing()} is enabled.
|
||||
* Only used if {@link GeyserConfig.GameplayConfig#forwardPlayerPing()} is enabled.
|
||||
*/
|
||||
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@@ -753,6 +749,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
@Accessors(fluent = true)
|
||||
private boolean hasAcceptedCodeOfConduct = false;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Setter
|
||||
private boolean integratedPackActive = false;
|
||||
|
||||
private final Set<InputLocksFlag> inputLocksSet = EnumSet.noneOf(InputLocksFlag.class);
|
||||
private boolean inputLockDirty;
|
||||
|
||||
@@ -799,12 +799,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
this.spawned = false;
|
||||
this.loggedIn = false;
|
||||
|
||||
if (geyser.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) {
|
||||
this.emotes = new HashSet<>();
|
||||
geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes()));
|
||||
} else {
|
||||
this.emotes = null;
|
||||
}
|
||||
|
||||
this.remoteServer = geyser.defaultRemoteServer();
|
||||
}
|
||||
@@ -870,7 +866,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
upstream.sendPacket(playStatusPacket);
|
||||
|
||||
SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket();
|
||||
setCommandsEnabledPacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled());
|
||||
setCommandsEnabledPacket.setCommandsEnabled(!geyser.config().gameplay().xboxAchievementsEnabled());
|
||||
upstream.sendPacket(setCommandsEnabledPacket);
|
||||
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
@@ -929,7 +925,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
MinecraftProfile mcProfile;
|
||||
MinecraftToken mcToken;
|
||||
try {
|
||||
JsonObject parsedAuthChain = GSON.fromJson(authChain, JsonObject.class);
|
||||
JsonObject parsedAuthChain = GeyserImpl.GSON.fromJson(authChain, JsonObject.class);
|
||||
if (parsedAuthChain.has("mcProfile")) { // Old Minecraft v4 auth chain
|
||||
parsedAuthChain = MinecraftAuth4To5Migrator.migrateJavaSave(parsedAuthChain, GeyserImpl.OAUTH_CONFIG);
|
||||
}
|
||||
@@ -946,7 +942,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
new GameProfile(mcProfile.getId(), mcProfile.getName()),
|
||||
mcToken.getToken()
|
||||
);
|
||||
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(JavaAuthManager.toJson(authManager)));
|
||||
geyser.saveAuthChain(bedrockUsername(), GeyserImpl.GSON.toJson(JavaAuthManager.toJson(authManager)));
|
||||
return Boolean.TRUE;
|
||||
}).whenComplete((successful, ex) -> {
|
||||
if (this.closed) {
|
||||
@@ -1039,7 +1035,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
}
|
||||
|
||||
// Save our auth chain for later use
|
||||
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(JavaAuthManager.toJson(result)));
|
||||
geyser.saveAuthChain(bedrockUsername(), GeyserImpl.GSON.toJson(JavaAuthManager.toJson(result)));
|
||||
return true;
|
||||
}).getNow(false);
|
||||
}
|
||||
@@ -1091,10 +1087,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
// Disable automatic creation of a new TcpClientSession when transferring - we don't use that functionality.
|
||||
this.downstream.getSession().setFlag(MinecraftConstants.FOLLOW_TRANSFERS, false);
|
||||
|
||||
if (geyser.getConfig().getRemote().isUseProxyProtocol()) {
|
||||
if (geyser.config().advanced().java().useHaproxyProtocol()) {
|
||||
downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
|
||||
}
|
||||
if (geyser.getConfig().isForwardPlayerPing()) {
|
||||
if (geyser.config().gameplay().forwardPlayerPing()) {
|
||||
// Let Geyser handle sending the keep alive
|
||||
downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
||||
}
|
||||
@@ -1140,7 +1136,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
} else {
|
||||
// Downstream's disconnect will fire an event that prints a log message
|
||||
// Otherwise, we print a message here
|
||||
String address = geyser.getConfig().isLogPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : "<IP address withheld>";
|
||||
String address = geyser.config().logPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : "<IP address withheld>";
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, MessageTranslator.convertMessage(reason)));
|
||||
}
|
||||
|
||||
@@ -1740,7 +1736,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
startGamePacket.setLevelGameType(GameType.SURVIVAL);
|
||||
startGamePacket.setDifficulty(1);
|
||||
startGamePacket.setDefaultSpawn(Vector3i.ZERO);
|
||||
startGamePacket.setAchievementsDisabled(!geyser.getConfig().isXboxAchievementsEnabled());
|
||||
startGamePacket.setAchievementsDisabled(!geyser.config().gameplay().xboxAchievementsEnabled());
|
||||
startGamePacket.setCurrentTick(-1);
|
||||
startGamePacket.setEduEditionOffers(0);
|
||||
startGamePacket.setEduFeaturesEnabled(false);
|
||||
@@ -1750,7 +1746,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
startGamePacket.setBroadcastingToLan(true);
|
||||
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
|
||||
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
|
||||
startGamePacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled());
|
||||
startGamePacket.setCommandsEnabled(!geyser.config().gameplay().xboxAchievementsEnabled());
|
||||
startGamePacket.setTexturePacksRequired(false);
|
||||
startGamePacket.setBonusChestEnabled(false);
|
||||
startGamePacket.setStartingWithMap(false);
|
||||
@@ -1768,7 +1764,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
startGamePacket.setEducationProductionId("");
|
||||
startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty());
|
||||
|
||||
String serverName = geyser.getConfig().getBedrock().serverName();
|
||||
String serverName = geyser.config().gameplay().serverName();
|
||||
startGamePacket.setLevelId(serverName);
|
||||
startGamePacket.setLevelName(serverName);
|
||||
|
||||
@@ -1914,7 +1910,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
public void sendDownstreamPacket(Packet packet, ProtocolState intendedState) {
|
||||
// protocol can be null when we're not yet logged in (online auth)
|
||||
if (protocol == null) {
|
||||
if (geyser.getConfig().isDebugMode()) {
|
||||
if (geyser.config().debugMode()) {
|
||||
geyser.getLogger().debug("Tried to send downstream packet with no downstream session!");
|
||||
Thread.dumpStack();
|
||||
}
|
||||
@@ -1940,7 +1936,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
if (channel == null) {
|
||||
// Channel is only null before the connection has initialized
|
||||
geyser.getLogger().warning("Tried to send a packet to the Java server too early!");
|
||||
if (geyser.getConfig().isDebugMode()) {
|
||||
if (geyser.config().debugMode()) {
|
||||
Thread.dumpStack();
|
||||
}
|
||||
return;
|
||||
@@ -2431,7 +2427,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
private void softEnumPacket(String name, SoftEnumUpdateType type, String enums) {
|
||||
// There is no need to send command enums if command suggestions are disabled
|
||||
if (!this.geyser.getConfig().isCommandSuggestions()) {
|
||||
if (!this.geyser.config().gameplay().commandSuggestions()) {
|
||||
return;
|
||||
}
|
||||
UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket();
|
||||
|
||||
@@ -110,7 +110,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
}
|
||||
|
||||
String address;
|
||||
if (geyser.getConfig().getRemote().isForwardHost()) {
|
||||
if (geyser.config().java().forwardHostname()) {
|
||||
address = session.joinAddress();
|
||||
} else {
|
||||
address = intentionPacket.getHostname();
|
||||
@@ -172,7 +172,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale);
|
||||
// Explain that they may be looking for Floodgate.
|
||||
geyser.getLogger().warning(GeyserLocale.getLocaleStringLog(
|
||||
geyser.getPlatformType() == PlatformType.STANDALONE ?
|
||||
geyser.platformType() == PlatformType.STANDALONE ?
|
||||
"geyser.network.remote.floodgate_explanation_standalone"
|
||||
: "geyser.network.remote.floodgate_explanation_plugin",
|
||||
Constants.FLOODGATE_DOWNLOAD_LOCATION
|
||||
@@ -180,7 +180,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
} else {
|
||||
// Likely that Floodgate is not configured correctly.
|
||||
customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.floodgate_login_error", locale);
|
||||
if (geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
if (geyser.platformType() == PlatformType.STANDALONE) {
|
||||
geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.remote.floodgate_login_error_standalone"));
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause);
|
||||
}
|
||||
if (geyser.getConfig().isDebugMode()) {
|
||||
if (geyser.config().debugMode()) {
|
||||
cause.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
(event.getPacketClass() != null ? "(" + event.getPacketClass().getSimpleName() + ") " : "") +
|
||||
event.getCause().getMessage())
|
||||
);
|
||||
if (geyser.getConfig().isDebugMode())
|
||||
if (geyser.config().debugMode())
|
||||
event.getCause().printStackTrace();
|
||||
event.setSuppress(true);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public final class SessionDisconnectListener {
|
||||
String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale());
|
||||
if (testForOutdatedServer(disconnectReason)) {
|
||||
String locale = session.locale();
|
||||
PlatformType platform = session.getGeyser().getPlatformType();
|
||||
PlatformType platform = session.getGeyser().platformType();
|
||||
String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY || platform == PlatformType.VIAPROXY) ?
|
||||
"geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server";
|
||||
event.disconnectReason(GeyserLocale.getPlayerLocaleString(outdatedType, locale, GameProtocol.getJavaVersions().get(0)) + '\n'
|
||||
|
||||
@@ -25,93 +25,97 @@
|
||||
|
||||
package org.geysermc.geyser.session.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.floodgate.util.DeviceOs;
|
||||
import org.geysermc.floodgate.util.InputMode;
|
||||
import org.geysermc.floodgate.util.UiProfile;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Getter
|
||||
public final class BedrockClientData {
|
||||
@JsonProperty(value = "GameVersion")
|
||||
@SerializedName(value = "GameVersion")
|
||||
private String gameVersion;
|
||||
@JsonProperty(value = "ServerAddress")
|
||||
@SerializedName(value = "ServerAddress")
|
||||
private String serverAddress;
|
||||
@JsonProperty(value = "ThirdPartyName")
|
||||
@SerializedName(value = "ThirdPartyName")
|
||||
private String username;
|
||||
@JsonProperty(value = "LanguageCode")
|
||||
@SerializedName(value = "LanguageCode")
|
||||
private String languageCode;
|
||||
|
||||
@JsonProperty(value = "SkinId")
|
||||
@SerializedName(value = "SkinId")
|
||||
private String skinId;
|
||||
@JsonProperty(value = "SkinData")
|
||||
@SerializedName(value = "SkinData")
|
||||
private String skinData;
|
||||
@JsonProperty(value = "SkinImageHeight")
|
||||
@SerializedName(value = "SkinImageHeight")
|
||||
private int skinImageHeight;
|
||||
@JsonProperty(value = "SkinImageWidth")
|
||||
@SerializedName(value = "SkinImageWidth")
|
||||
private int skinImageWidth;
|
||||
@JsonProperty(value = "CapeId")
|
||||
@SerializedName(value = "CapeId")
|
||||
private String capeId;
|
||||
@JsonProperty(value = "CapeData")
|
||||
@SerializedName(value = "CapeData")
|
||||
@JsonAdapter(value = StringToByteDeserializer.class)
|
||||
private byte[] capeData;
|
||||
@JsonProperty(value = "CapeImageHeight")
|
||||
@SerializedName(value = "CapeImageHeight")
|
||||
private int capeImageHeight;
|
||||
@JsonProperty(value = "CapeImageWidth")
|
||||
@SerializedName(value = "CapeImageWidth")
|
||||
private int capeImageWidth;
|
||||
@JsonProperty(value = "CapeOnClassicSkin")
|
||||
@SerializedName(value = "CapeOnClassicSkin")
|
||||
private boolean capeOnClassicSkin;
|
||||
@JsonProperty(value = "SkinResourcePatch")
|
||||
@SerializedName(value = "SkinResourcePatch")
|
||||
private String geometryName;
|
||||
@JsonProperty(value = "SkinGeometryData")
|
||||
@SerializedName(value = "SkinGeometryData")
|
||||
private String geometryData;
|
||||
@JsonProperty(value = "PersonaSkin")
|
||||
@SerializedName(value = "PersonaSkin")
|
||||
private boolean personaSkin;
|
||||
@JsonProperty(value = "PremiumSkin")
|
||||
@SerializedName(value = "PremiumSkin")
|
||||
private boolean premiumSkin;
|
||||
|
||||
@JsonProperty(value = "DeviceId")
|
||||
@SerializedName(value = "DeviceId")
|
||||
private String deviceId;
|
||||
@JsonProperty(value = "DeviceModel")
|
||||
@SerializedName(value = "DeviceModel")
|
||||
private String deviceModel;
|
||||
@JsonProperty(value = "DeviceOS")
|
||||
@SerializedName(value = "DeviceOS")
|
||||
private DeviceOs deviceOs;
|
||||
@JsonProperty(value = "UIProfile")
|
||||
@SerializedName(value = "UIProfile")
|
||||
private UiProfile uiProfile;
|
||||
@JsonProperty(value = "GuiScale")
|
||||
@SerializedName(value = "GuiScale")
|
||||
private int guiScale;
|
||||
@JsonProperty(value = "CurrentInputMode")
|
||||
@SerializedName(value = "CurrentInputMode")
|
||||
private InputMode currentInputMode;
|
||||
@JsonProperty(value = "DefaultInputMode")
|
||||
@SerializedName(value = "DefaultInputMode")
|
||||
private InputMode defaultInputMode;
|
||||
@JsonProperty("PlatformOnlineId")
|
||||
@SerializedName("PlatformOnlineId")
|
||||
private String platformOnlineId;
|
||||
@JsonProperty(value = "PlatformOfflineId")
|
||||
@SerializedName(value = "PlatformOfflineId")
|
||||
private String platformOfflineId;
|
||||
@JsonProperty(value = "SelfSignedId")
|
||||
@SerializedName(value = "SelfSignedId")
|
||||
private UUID selfSignedId;
|
||||
@JsonProperty(value = "ClientRandomId")
|
||||
@SerializedName(value = "ClientRandomId")
|
||||
private long clientRandomId;
|
||||
|
||||
@JsonProperty(value = "ArmSize")
|
||||
@SerializedName(value = "ArmSize")
|
||||
private String armSize;
|
||||
@JsonProperty(value = "SkinAnimationData")
|
||||
@SerializedName(value = "SkinAnimationData")
|
||||
private String skinAnimationData;
|
||||
@JsonProperty(value = "SkinColor")
|
||||
@SerializedName(value = "SkinColor")
|
||||
private String skinColor;
|
||||
@JsonProperty(value = "ThirdPartyNameOnly")
|
||||
@SerializedName(value = "ThirdPartyNameOnly")
|
||||
private boolean thirdPartyNameOnly;
|
||||
@JsonProperty(value = "PlayFabId")
|
||||
@SerializedName(value = "PlayFabId")
|
||||
private String playFabId;
|
||||
|
||||
@JsonIgnore
|
||||
@Setter
|
||||
private String originalString = null;
|
||||
private transient String originalString = null;
|
||||
|
||||
public DeviceOs getDeviceOs() {
|
||||
return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN;
|
||||
@@ -128,4 +132,11 @@ public final class BedrockClientData {
|
||||
public UiProfile getUiProfile() {
|
||||
return uiProfile != null ? uiProfile : UiProfile.CLASSIC;
|
||||
}
|
||||
|
||||
private static final class StringToByteDeserializer implements JsonDeserializer<byte[]> {
|
||||
@Override
|
||||
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return json.getAsString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public final class BundleCache {
|
||||
if (itemStack.is(session, ItemTag.BUNDLES)) {
|
||||
if (itemStack.getBundleData() != null) {
|
||||
session.getGeyser().getLogger().warning("Stack has bundle data already! It should not!");
|
||||
if (session.getGeyser().getConfig().isDebugMode()) {
|
||||
if (session.getGeyser().getLogger().isDebug()) {
|
||||
session.getGeyser().getLogger().debug("Player: " + session.javaUsername());
|
||||
session.getGeyser().getLogger().debug("Stack: " + itemStack);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ package org.geysermc.geyser.session.cache;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.CooldownUtils;
|
||||
|
||||
@@ -54,15 +54,16 @@ public class PreferencesCache {
|
||||
private boolean prefersCustomSkulls;
|
||||
|
||||
/**
|
||||
* Which CooldownType the client prefers. Initially set to {@link CooldownUtils#getDefaultShowCooldown()}.
|
||||
* Which CooldownType the client prefers. Initially set to the config default.
|
||||
*/
|
||||
@Setter
|
||||
private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown();
|
||||
private CooldownUtils.CooldownType cooldownPreference;
|
||||
|
||||
public PreferencesCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
|
||||
prefersCustomSkulls = session.getGeyser().getConfig().isAllowCustomSkulls();
|
||||
prefersCustomSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0;
|
||||
cooldownPreference = session.getGeyser().config().gameplay().showCooldown();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,10 +72,10 @@ public class PreferencesCache {
|
||||
* If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply: <br>
|
||||
* <br>
|
||||
* {@link GeyserSession#isReducedDebugInfo()} is enabled
|
||||
* {@link GeyserConfiguration#isShowCoordinates()} is disabled
|
||||
* {@link GeyserConfig.GameplayConfig#showCoordinates()} is disabled
|
||||
*/
|
||||
public void updateShowCoordinates() {
|
||||
allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().getConfig().isShowCoordinates();
|
||||
allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().config().gameplay().showCoordinates();
|
||||
session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates);
|
||||
}
|
||||
|
||||
@@ -82,6 +83,6 @@ public class PreferencesCache {
|
||||
* @return true if the session prefers custom skulls, and the config allows them.
|
||||
*/
|
||||
public boolean showCustomSkulls() {
|
||||
return prefersCustomSkulls && session.getGeyser().getConfig().isAllowCustomSkulls();
|
||||
return prefersCustomSkulls && session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +67,11 @@ public class SkullCache {
|
||||
|
||||
public SkullCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
this.maxVisibleSkulls = session.getGeyser().getConfig().getMaxVisibleCustomSkulls();
|
||||
this.maxVisibleSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls();
|
||||
this.cullingEnabled = this.maxVisibleSkulls != -1;
|
||||
|
||||
// Normal skulls are not rendered beyond 64 blocks
|
||||
int distance = Math.min(session.getGeyser().getConfig().getCustomSkullRenderDistance(), 64);
|
||||
int distance = Math.min(session.getGeyser().config().gameplay().customSkullRenderDistance(), 64);
|
||||
this.skullRenderDistanceSquared = distance * distance;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class SkullCache {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
if (GeyserImpl.getInstance().config().debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,9 @@
|
||||
|
||||
package org.geysermc.geyser.skin;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||
import org.geysermc.floodgate.util.WebsocketEventType;
|
||||
@@ -37,6 +35,7 @@ import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
import org.geysermc.geyser.util.PluginMessageUtils;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
@@ -52,7 +51,6 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class FloodgateSkinUploader {
|
||||
private final ObjectMapper JACKSON = new ObjectMapper();
|
||||
private final List<String> skinQueue = new ArrayList<>();
|
||||
|
||||
private final GeyserLogger logger;
|
||||
@@ -79,15 +77,14 @@ public final class FloodgateSkinUploader {
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
// The reason why I don't like Jackson
|
||||
try {
|
||||
JsonNode node = JACKSON.readTree(message);
|
||||
JsonObject node = JsonUtils.parseJson(message);
|
||||
if (node.has("error")) {
|
||||
logger.error("Got an error: " + node.get("error").asText());
|
||||
logger.error("Got an error: " + node.get("error").getAsString());
|
||||
return;
|
||||
}
|
||||
|
||||
int typeId = node.get("event_id").asInt();
|
||||
int typeId = node.get("event_id").getAsInt();
|
||||
WebsocketEventType type = WebsocketEventType.fromId(typeId);
|
||||
if (type == null) {
|
||||
logger.warning(String.format(
|
||||
@@ -98,11 +95,11 @@ public final class FloodgateSkinUploader {
|
||||
|
||||
switch (type) {
|
||||
case SUBSCRIBER_CREATED:
|
||||
id = node.get("id").asInt();
|
||||
verifyCode = node.get("verify_code").asText();
|
||||
id = node.get("id").getAsInt();
|
||||
verifyCode = node.get("verify_code").getAsString();
|
||||
break;
|
||||
case SUBSCRIBER_COUNT:
|
||||
subscribersCount = node.get("subscribers_count").asInt();
|
||||
subscribersCount = node.get("subscribers_count").getAsInt();
|
||||
break;
|
||||
case SKIN_UPLOADED:
|
||||
// if Geyser is the only subscriber we have send it to the server manually
|
||||
@@ -111,19 +108,19 @@ public final class FloodgateSkinUploader {
|
||||
break;
|
||||
}
|
||||
|
||||
String xuid = node.get("xuid").asText();
|
||||
String xuid = node.get("xuid").getAsString();
|
||||
GeyserSession session = geyser.connectionByXuid(xuid);
|
||||
|
||||
if (session != null) {
|
||||
if (!node.get("success").asBoolean()) {
|
||||
if (!node.get("success").getAsBoolean()) {
|
||||
logger.info("Failed to upload skin for " + session.bedrockUsername());
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode data = node.get("data");
|
||||
JsonObject data = node.getAsJsonObject("data");
|
||||
|
||||
String value = data.get("value").asText();
|
||||
String signature = data.get("signature").asText();
|
||||
String value = data.get("value").getAsString();
|
||||
String signature = data.get("signature").getAsString();
|
||||
|
||||
byte[] bytes = (value + '\0' + signature)
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
@@ -131,8 +128,8 @@ public final class FloodgateSkinUploader {
|
||||
}
|
||||
break;
|
||||
case LOG_MESSAGE:
|
||||
String logMessage = node.get("message").asText();
|
||||
switch (node.get("priority").asInt()) {
|
||||
String logMessage = node.get("message").getAsString();
|
||||
switch (node.get("priority").getAsInt()) {
|
||||
case -1 -> logger.debug("Got a message from skin uploader: " + logMessage);
|
||||
case 0 -> logger.info("Got a message from skin uploader: " + logMessage);
|
||||
case 1 -> logger.error("Got a message from skin uploader: " + logMessage);
|
||||
@@ -150,20 +147,19 @@ public final class FloodgateSkinUploader {
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
if (reason != null && !reason.isEmpty()) {
|
||||
// The reason why I don't like Jackson
|
||||
try {
|
||||
JsonNode node = JACKSON.readTree(reason);
|
||||
JsonObject node = JsonUtils.parseJson(reason);
|
||||
// info means that the uploader itself did nothing wrong
|
||||
if (node.has("info")) {
|
||||
String info = node.get("info").asText();
|
||||
String info = node.get("info").getAsString();
|
||||
logger.debug("Got disconnected from the skin uploader: " + info);
|
||||
}
|
||||
// error means that the uploader did something wrong
|
||||
if (node.has("error")) {
|
||||
String error = node.get("error").asText();
|
||||
String error = node.get("error").getAsString();
|
||||
logger.info("Got disconnected from the skin uploader: " + error);
|
||||
}
|
||||
} catch (JsonProcessingException ignored) {
|
||||
} catch (JsonSyntaxException ignored) {
|
||||
// ignore invalid json
|
||||
} catch (Exception e) {
|
||||
logger.error("Error while handling onClose", e);
|
||||
@@ -198,24 +194,17 @@ public final class FloodgateSkinUploader {
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectNode node = JACKSON.createObjectNode();
|
||||
JsonObject node = new JsonObject();
|
||||
if (chainData != null) {
|
||||
ArrayNode chainDataNode = JACKSON.createArrayNode();
|
||||
JsonArray chainDataNode = new JsonArray();
|
||||
chainData.forEach(chainDataNode::add);
|
||||
node.set("chain_data", chainDataNode);
|
||||
node.add("chain_data", chainDataNode);
|
||||
} else {
|
||||
node.put("token", token);
|
||||
node.addProperty("token", token);
|
||||
}
|
||||
node.put("client_data", clientData);
|
||||
node.addProperty("client_data", clientData);
|
||||
|
||||
// The reason why I don't like Jackson
|
||||
String jsonString;
|
||||
try {
|
||||
jsonString = JACKSON.writeValueAsString(node);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to upload skin", e);
|
||||
return;
|
||||
}
|
||||
String jsonString = node.toString();
|
||||
|
||||
if (client.isOpen()) {
|
||||
client.send(jsonString);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user