From 29f8e294ada243874193799ca4e69ca600a018e7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:16:08 -0400 Subject: [PATCH] New config used in core --- .../bungeecord/GeyserBungeeInjector.java | 4 +- .../GeyserBungeeUpdateListener.java | 2 +- .../platform/mod/GeyserModInjector.java | 4 +- .../mixin/client/IntegratedServerMixin.java | 1 - .../platform/spigot/GeyserSpigotInjector.java | 8 +- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../spigot/GeyserSpigotUpdateListener.java | 2 +- .../command/GeyserPaperCommandListener.java | 2 +- .../standalone/GeyserStandaloneBootstrap.java | 175 ++----- .../velocity/GeyserVelocityInjector.java | 13 +- .../GeyserVelocityUpdateListener.java | 2 +- core/build.gradle.kts | 2 + .../java/org/geysermc/geyser/Constants.java | 4 +- .../org/geysermc/geyser/GeyserBootstrap.java | 17 +- .../java/org/geysermc/geyser/GeyserImpl.java | 183 +++---- .../geyser/GeyserPluginBootstrap.java | 36 ++ .../defaults/ConnectionTestCommand.java | 24 +- .../geyser/command/defaults/DumpCommand.java | 33 +- .../configuration/ConfigLoaderTemp.java | 97 +++- .../geyser/configuration/GeyserConfig.java | 65 ++- .../configuration/GeyserPluginConfig.java | 6 + .../configuration/GeyserRemoteConfig.java | 20 +- .../LowercaseEnumSerializer.java | 55 +++ .../org/geysermc/geyser/dump/DumpInfo.java | 23 +- .../geyser/entity/EntityDefinition.java | 2 +- .../geysermc/geyser/entity/type/Entity.java | 2 +- .../geyser/inventory/click/ClickPlan.java | 2 +- .../geyser/network/UpstreamPacketHandler.java | 10 +- .../geyser/network/netty/GeyserInjector.java | 10 +- .../geyser/network/netty/GeyserServer.java | 87 ++-- .../geyser/pack/SkullResourcePackManager.java | 4 +- .../ping/GeyserLegacyPingPassthrough.java | 10 +- .../registry/PacketTranslatorRegistry.java | 2 +- .../CustomBlockRegistryPopulator.java | 2 +- .../CustomSkullRegistryPopulator.java | 3 +- .../populator/ItemRegistryPopulator.java | 2 +- .../geyser/scoreboard/ScoreboardUpdater.java | 8 +- .../geyser/session/GeyserSession.java | 110 ++++- .../session/auth/BedrockClientData.java | 89 ++-- .../session/cache/PreferencesCache.java | 11 +- .../geyser/session/cache/SkullCache.java | 6 +- .../geyser/skin/FloodgateSkinUploader.java | 65 ++- .../org/geysermc/geyser/skin/SkinManager.java | 37 +- .../geysermc/geyser/skin/SkinProvider.java | 18 +- .../geysermc/geyser/text/GeyserLocale.java | 4 +- .../inventory/InventoryTranslator.java | 8 +- .../entity/SkullBlockEntityTranslator.java | 2 +- .../bedrock/BedrockEmoteListTranslator.java | 5 - ...BedrockInventoryTransactionTranslator.java | 4 +- .../BedrockNetworkStackLatencyTranslator.java | 2 +- ...SetLocalPlayerAsInitializedTranslator.java | 2 +- .../entity/player/BedrockEmoteTranslator.java | 10 - .../protocol/java/JavaCommandsTranslator.java | 2 +- .../java/JavaKeepAliveTranslator.java | 2 +- .../java/JavaUpdateRecipesTranslator.java | 2 +- .../entity/JavaSetEntityDataTranslator.java | 2 +- .../player/JavaPlayerPositionTranslator.java | 2 +- .../translator/text/MessageTranslator.java | 2 +- .../geysermc/geyser/util/CooldownUtils.java | 16 +- .../geysermc/geyser/util/InventoryUtils.java | 4 +- .../org/geysermc/geyser/util/JsonUtils.java | 4 + .../geyser/util/LoginEncryptionUtils.java | 14 +- .../org/geysermc/geyser/util/Metrics.java | 447 ------------------ .../geysermc/geyser/util/SettingsUtils.java | 4 +- .../org/geysermc/geyser/util/WebUtils.java | 2 +- gradle/libs.versions.toml | 3 + 66 files changed, 758 insertions(+), 1045 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/GeyserPluginBootstrap.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/LowercaseEnumSerializer.java delete mode 100644 core/src/main/java/org/geysermc/geyser/util/Metrics.java diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java index 7c60ba95d..fbd3bbd6e 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java @@ -108,7 +108,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().java().useProxyProtocol() // If Geyser is expecting HAProxy, so should the Bungee end ); // The field that stores all listeners in BungeeCord @@ -142,7 +142,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener { } initChannel.invoke(channelInitializer, ch); - if (bootstrap.getGeyserConfig().isDisableCompression()) { + if (bootstrap.config().asPluginConfig().orElseThrow().useDirectConnection()) { ch.pipeline().addAfter(PipelineUtils.PACKET_ENCODER, "geyser-compression-disabler", new GeyserBungeeCompressionDisabler()); } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java index c68839b20..8a1244ac6 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java @@ -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(Constants.UPDATE_PERMISSION)) { VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player)); diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java index 624eccb3f..89a92d789 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java @@ -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().asPluginConfig().orElseThrow().disableCompression()) { ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserModCompressionDisabler()); } } @@ -125,7 +125,7 @@ public class GeyserModInjector extends GeyserInjector { childHandler = (ChannelInitializer) 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(); } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java index ece2f730a..7a4abd50e 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java @@ -56,7 +56,6 @@ 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); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java index 5dcfbd0f8..d1d4b50ea 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java @@ -122,7 +122,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().asPluginConfig().orElseThrow().disableCompression() && GeyserSpigotCompressionDisabler.ENABLED) { ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler()); } } @@ -157,7 +157,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(); } @@ -176,8 +176,8 @@ public class GeyserSpigotInjector extends GeyserInjector { */ private void workAroundWeirdBug(GeyserBootstrap bootstrap) { MinecraftProtocol protocol = new MinecraftProtocol(); - LocalSession session = new LocalSession(bootstrap.getGeyserConfig().getRemote().address(), - bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress, + LocalSession session = new LocalSession(bootstrap.config().java().address(), + bootstrap.config().java().port(), this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper()); session.connect(); session.disconnect(""); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 8e853b629..60be30725 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -487,7 +487,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { 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); - ConfigLoaderTemp.load(GeyserPluginConfig.class); + ConfigLoaderTemp.load(new File(getDataFolder(), "config.yml"), GeyserPluginConfig.class); } catch (IOException ex) { geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java index 5e3c4def8..d244adddb 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java @@ -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(Constants.UPDATE_PERMISSION)) { VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player)); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java index dcec045ab..f1f8cb915 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java @@ -76,7 +76,7 @@ public final class GeyserPaperCommandListener implements Listener { return false; } - if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) { + if (GeyserImpl.getInstance().config().asPluginConfig().orElseThrow().useDirectConnection()) { InetSocketAddress address = player.getAddress(); if (address != null) { return address.getPort() != 0; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 98b67a32b..0037e376d 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -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 net.minecrell.terminalconsole.TerminalConsoleAppender; @@ -44,16 +39,17 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.ConfigLoaderTemp; -import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +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; @@ -61,13 +57,13 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.*; -import java.util.stream.Collectors; +import java.util.HashMap; +import java.util.Map; public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserCommandManager geyserCommandManager; - private GeyserStandaloneConfiguration geyserConfig; + private GeyserConfig geyserConfig; private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger(); private IGeyserPingPassthrough geyserPingPassthrough; private GeyserStandaloneGUI gui; @@ -78,15 +74,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserImpl geyser; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private static final Map argsConfigKeys = new HashMap<>(); + private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { if (System.getProperty("io.netty.leakDetection.level") == null) { ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); // Can eat performance } + System.setProperty("bstats.relocatecheck", "false"); + GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); // Set defaults boolean useGuiOpts = bootstrap.useGui; @@ -94,8 +90,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { GeyserLocale.init(bootstrap); - List 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 @@ -127,34 +121,8 @@ 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; - 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; - } + argsConfigKeys.put(NodePath.of(argParts[0].split("\\.")), argParts[1]); + break; } } System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); @@ -192,18 +160,7 @@ 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); - - ConfigLoaderTemp.load(GeyserRemoteConfig.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"); - } + geyserConfig = ConfigLoaderTemp.load(new File(configFilename), GeyserRemoteConfig.class, this::handleArgsConfigOptions); } catch (IOException ex) { geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); if (gui == null) { @@ -213,11 +170,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { return; } } - geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + geyserLogger.setDebug(geyserConfig.debugMode()); // 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); @@ -266,8 +222,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } @Override - public GeyserConfiguration getGeyserConfig() { - return geyserConfig; + public GeyserConfig config() { + return this.geyserConfig; } @Override @@ -318,100 +274,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 getPOJOForClass(Class clazz) { - JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz); - - // Introspect the given type - BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType); - - // Find properties - List properties = beanDescription.findProperties(); - - // Get the ignored properties - Set 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.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) 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 availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + private void handleArgsConfigOptions(CommentedConfigurationNode node) { + for (Map.Entry 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 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 { - 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; - } + try { + setConfigOption(subNode, configKey.getValue()); + geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + } catch (SerializationException e) { + geyserLogger.error("Failed to set config option: " + path); } } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java index 68a9eb40b..caeaf15b5 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java @@ -27,10 +27,15 @@ package org.geysermc.geyser.platform.velocity; import com.velocitypowered.api.proxy.ProxyServer; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.local.LocalAddress; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserPluginBootstrap; import org.geysermc.geyser.network.netty.GeyserInjector; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; @@ -47,7 +52,7 @@ public class GeyserVelocityInjector extends GeyserInjector { @Override @SuppressWarnings("unchecked") - protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception { + protected void initializeLocalChannel0(GeyserPluginBootstrap bootstrap) throws Exception { Field cm = proxy.getClass().getDeclaredField("cm"); cm.setAccessible(true); Object connectionManager = cm.get(proxy); @@ -80,7 +85,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().disableCompression() && GeyserVelocityCompressionDisabler.ENABLED) { ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler", new GeyserVelocityCompressionDisabler()); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java index 31e584612..d7049b5cc 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java @@ -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(Constants.UPDATE_PERMISSION)) { VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player)); diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1e5a9d675..7d5349f12 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -73,6 +73,8 @@ dependencies { annotationProcessor(projects.ap) api(libs.events) + + api(libs.bstats) } tasks.processResources { diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 40aa8ada1..94ff93c67 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -47,6 +47,8 @@ public final class Constants { public static final int CONFIG_VERSION = 5; + public static final int BSTATS_ID = 5273; + static { URI wsUri = null; try { @@ -56,4 +58,4 @@ public final class Constants { } GLOBAL_API_WS_URI = wsUri; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index a9414d9d0..96a0078bd 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -28,6 +28,7 @@ package org.geysermc.geyser; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.GeyserWorldManager; @@ -72,7 +73,16 @@ public interface GeyserBootstrap { * * @return The current GeyserConfiguration */ - GeyserConfiguration getGeyserConfig(); + default GeyserConfiguration getGeyserConfig() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the current GeyserConfig + * + * @return The current GeyserConfig + */ + GeyserConfig config(); /** * Returns the current GeyserLogger @@ -189,4 +199,9 @@ 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(); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index c8fc94ffb..8015dfd1a 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -25,9 +25,6 @@ package org.geysermc.geyser; -import com.fasterxml.jackson.core.JsonParser; -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; @@ -39,6 +36,11 @@ import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +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; @@ -67,7 +69,7 @@ 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.GeyserCommandManager; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.erosion.UnixSocketClientListener; import org.geysermc.geyser.event.GeyserEventBus; @@ -91,10 +93,8 @@ import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.AssetUtils; -import org.geysermc.geyser.util.CooldownUtils; import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.JsonUtils; -import org.geysermc.geyser.util.Metrics; import org.geysermc.geyser.util.NewsHandler; import org.geysermc.geyser.util.VersionCheckUtils; import org.geysermc.geyser.util.WebUtils; @@ -128,13 +128,6 @@ import java.util.regex.Pattern; @Getter public class GeyserImpl implements GeyserApi { - 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"; @@ -180,7 +173,7 @@ public class GeyserImpl implements GeyserApi { private final EventBus eventBus; private final GeyserExtensionManager extensionManager; - private Metrics metrics; + private MetricsBase metrics; private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; @Getter(AccessLevel.NONE) @@ -269,7 +262,7 @@ public class GeyserImpl implements GeyserApi { 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)); @@ -277,11 +270,11 @@ public class GeyserImpl implements GeyserApi { logger.info(message); if (platformType == PlatformType.STANDALONE) { - if (config.getRemote().authType() != AuthType.FLOODGATE) { + 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); } @@ -296,7 +289,7 @@ public class GeyserImpl implements GeyserApi { GeyserLocale.finalizeDefaultLocale(this); } GeyserLogger logger = bootstrap.getGeyserLogger(); - GeyserConfiguration config = bootstrap.getGeyserConfig(); + GeyserConfig config = bootstrap.config(); ScoreboardUpdater.init(); @@ -314,31 +307,28 @@ public class GeyserImpl implements GeyserApi { 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); - } else { - // Set the remote address to localhost since that is where we are always connecting - try { - config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress()); - } catch (UnknownHostException ex) { - logger.debug("Unknown host when trying to find localhost."); - if (config.isDebugMode()) { - ex.printStackTrace(); - } - config.getRemote().setAddress(InetAddress.getLoopbackAddress().getHostAddress()); + String serverAddress = bootstrap.getServerBindAddress(); + if (!serverAddress.isEmpty() && !"0.0.0.0".equals(serverAddress)) { + config.java().address(serverAddress); + } else { + // Set the remote address to localhost since that is where we are always connecting + try { + config.java().address(InetAddress.getLocalHost().getHostAddress()); + } catch (UnknownHostException ex) { + logger.debug("Unknown host when trying to find localhost."); + if (config.debugMode()) { + ex.printStackTrace(); } + config.java().address(InetAddress.getLoopbackAddress().getHostAddress()); } - if (javaPort != -1) { - config.getRemote().setPort(javaPort); - } + } + if (javaPort != -1 && config.asPluginConfig().isEmpty()) { + config.java().port(javaPort); } boolean forceMatchServerPort = "server".equals(pluginUdpPort); - if ((config.getBedrock().isCloneRemotePort() || forceMatchServerPort) && javaPort != -1) { - config.getBedrock().setPort(javaPort); + if ((config.asPluginConfig().map(pluginConfig -> pluginConfig.bedrock().cloneRemotePort()).orElse(false) || forceMatchServerPort) && javaPort != -1) { + config.bedrock().port(javaPort); if (forceMatchServerPort) { if (geyserUdpPort.isEmpty()) { logger.info("Port set from system generic property to match Java server."); @@ -352,15 +342,15 @@ public class GeyserImpl implements GeyserApi { 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 { @@ -378,42 +368,44 @@ public class GeyserImpl implements GeyserApi { } } catch (NumberFormatException e) { logger.error(String.format("Invalid broadcast port: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")")); - parsedPort = config.getBedrock().port(); + parsedPort = config.bedrock().port(); } - config.getBedrock().setBroadcastPort(parsedPort); + config.bedrock().broadcastPort(parsedPort); logger.info("Broadcast port set from system property: " + parsedPort); } 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); } } } - String remoteAddress = config.getRemote().address(); - // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. - if (!remoteAddress.matches(IP_REGEX) && !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); - logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); + if (config.asPluginConfig().isEmpty()) { + 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 (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { + String[] record = WebUtils.findSrvRecord(this, remoteAddress); + if (record != null) { + int remotePort = Integer.parseInt(record[2]); + config.java().address(remoteAddress = record[3]); + config.java().port(remotePort); + logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); + } } } // Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves TcpSession.USE_EVENT_LOOP_FOR_PACKETS = false; - pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout()); + pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.pendingAuthenticationTimeout()); this.newsHandler = new NewsHandler(BRANCH, this.buildNumber()); @@ -425,8 +417,7 @@ public class GeyserImpl implements GeyserApi { logger.debug("Epoll is not available; Erosion's Unix socket handling will not work."); } - CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); - DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether + DimensionUtils.changeBedrockNetherId(config.aboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads"); if (bedrockThreadCount == null) { @@ -436,14 +427,14 @@ public class GeyserImpl implements GeyserApi { if (shouldStartListener) { 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) -> { if (throwable == null) { - logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.getBedrock().address(), - String.valueOf(config.getBedrock().port()))); + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.bedrock().address(), + String.valueOf(config.bedrock().port()))); } else { - String address = config.getBedrock().address(); - int port = config.getBedrock().port(); + String address = config.bedrock().address(); + int port = config.bedrock().port(); logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port))); if (!"0.0.0.0".equals(address)) { logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN)); @@ -453,9 +444,9 @@ public class GeyserImpl implements GeyserApi { }).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!"); @@ -467,15 +458,39 @@ public class GeyserImpl implements GeyserApi { } } - 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)); + if (config.metrics().enabled()) { + metrics = new MetricsBase( + "server-implementation", + config.metrics().uuid().toString(), + 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, + config.debugMode(), + config.debugMode(), + config.debugMode()); + 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 Metrics.SimplePie("platform", platformType::platformName)); - metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); - metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION)); - metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { + metrics.addCustomChart(new SimplePie("authMode", () -> config.java().authType().toString().toLowerCase(Locale.ROOT))); + metrics.addCustomChart(new SimplePie("platform", platformType::platformName)); + metrics.addCustomChart(new SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); + metrics.addCustomChart(new SimplePie("version", () -> GeyserImpl.VERSION)); + metrics.addCustomChart(new AdvancedPie("playerPlatform", () -> { Map valueMap = new HashMap<>(); for (GeyserSession session : sessionManager.getAllSessions()) { if (session == null) continue; @@ -489,7 +504,7 @@ public class GeyserImpl implements GeyserApi { } return valueMap; })); - metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { + metrics.addCustomChart(new AdvancedPie("playerVersion", () -> { Map valueMap = new HashMap<>(); for (GeyserSession session : sessionManager.getAllSessions()) { if (session == null) continue; @@ -511,7 +526,7 @@ public class GeyserImpl implements GeyserApi { platformMap.put(platformType.platformName(), 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; @@ -520,7 +535,7 @@ public class GeyserImpl implements GeyserApi { // 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> map = new HashMap<>(); String javaVersion = System.getProperty("java.version"); Map entry = new HashMap<>(); @@ -555,7 +570,7 @@ public class GeyserImpl implements GeyserApi { 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 savedRefreshTokens = new ConcurrentHashMap<>(); @@ -570,7 +585,7 @@ public class GeyserImpl implements GeyserApi { logger.error("Cannot load saved user tokens!", e); } if (refreshTokenFile != null) { - List validUsers = config.getSavedUserLogins(); + List validUsers = config.savedUserLogins(); boolean doWrite = false; for (Map.Entry entry : refreshTokenFile.entrySet()) { String user = entry.getKey(); @@ -598,7 +613,7 @@ public class GeyserImpl implements GeyserApi { this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); } - if (config.isNotifyOnNewBedrockUpdate()) { + if (config.notifyOnNewBedrockUpdate()) { VersionCheckUtils.checkForGeyserUpdate(this::getLogger); } } @@ -750,13 +765,13 @@ public class GeyserImpl implements GeyserApi { @NonNull public RemoteServer defaultRemoteServer() { - return getConfig().getRemote(); + return config().java(); } @Override @NonNull public BedrockListener bedrockListener() { - return getConfig().getBedrock(); + return config().bedrock(); } @Override @@ -830,8 +845,8 @@ public class GeyserImpl implements GeyserApi { return bootstrap.getGeyserLogger(); } - public GeyserConfiguration getConfig() { - return bootstrap.getGeyserConfig(); + public GeyserConfig config() { + return bootstrap.config(); } public WorldManager getWorldManager() { @@ -844,7 +859,7 @@ public class GeyserImpl implements GeyserApi { } public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) { - if (!getConfig().getSavedUserLogins().contains(bedrockName)) { + if (!config().savedUserLogins().contains(bedrockName)) { // Do not save this login return; } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserPluginBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserPluginBootstrap.java new file mode 100644 index 000000000..ed05ec1f3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/GeyserPluginBootstrap.java @@ -0,0 +1,36 @@ +/* + * 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; + +import org.geysermc.geyser.configuration.GeyserPluginConfig; + +/** + * Used in any instance where Geyser is directly attached to a server instance of some sort. + */ +public interface GeyserPluginBootstrap extends GeyserBootstrap { + @Override + GeyserPluginConfig config(); +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 84da73239..2a4a2b1af 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -32,7 +32,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; 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.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoopbackUtil; @@ -86,7 +86,7 @@ public class ConnectionTestCommand extends GeyserCommand { return; } } else { - port = geyser.getConfig().getBedrock().broadcastPort(); + port = geyser.config().bedrock().broadcastPort(); } String ip = fullAddress[0]; @@ -114,41 +114,41 @@ 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.bedrock().broadcastPort() == config.bedrock().port()) { + if (port != config.bedrock().port()) { if (fullAddress.length == 2) { sender.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() + ")"); sender.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.asPluginConfig().map(pluginConfig -> pluginConfig.bedrock().cloneRemotePort()).orElse(false)) { sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead."); } } else { sender.sendMessage("You did not specify the port to check (add it with \":\"), " + "and the default port 19132 does not match the port in your Geyser configuration (" - + config.getBedrock().port() + ")!"); + + config.bedrock().port() + ")!"); sender.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.bedrock().broadcastPort() != port) { sender.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.bedrock().broadcastPort() + "). "); sender.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on."); sender.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")) { sender.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()) { + if (config.bedrock().enableProxyProtocol()) { sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + "Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled."); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index b3fee375f..8bcadcc73 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -25,13 +25,14 @@ package org.geysermc.geyser.command.defaults; -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.JsonParser; +import com.google.gson.JsonSyntaxException; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.dump.DumpInfo; @@ -49,7 +50,6 @@ import java.util.List; public class DumpCommand extends GeyserCommand { 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) { @@ -81,18 +81,13 @@ public class DumpCommand extends GeyserCommand { AsteriskSerializer.showSensitive = showSensitive; + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale())); String dumpData; try { - if (offlineDump) { - DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); - // Make arrays easier to read - prettyPrinter.indentArraysWith(new DefaultIndenter(" ", "\n")); - dumpData = MAPPER.writer(prettyPrinter).writeValueAsString(new DumpInfo(addLog)); - } else { - dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog)); - } - } catch (IOException e) { + dumpData = gson.toJson(new DumpInfo(addLog)); + } catch (JsonSyntaxException e) { sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; @@ -118,10 +113,10 @@ public class DumpCommand extends GeyserCommand { sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.locale())); String response; - JsonNode responseNode; + JsonObject responseNode; try { response = WebUtils.post(DUMP_URL + "documents", dumpData); - responseNode = MAPPER.readTree(response); + responseNode = (JsonObject) new JsonParser().parse(response); } catch (IOException e) { sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); @@ -129,11 +124,11 @@ public class DumpCommand extends GeyserCommand { } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").getAsString() : response)); return; } - uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); + uploadedDumpUrl = DUMP_URL + responseNode.get("key").getAsString(); } sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java b/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java index 6d51a2847..59a68ba0e 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoaderTemp.java @@ -25,18 +25,21 @@ package org.geysermc.geyser.configuration; -import org.spongepowered.configurate.ConfigurationNode; -import org.spongepowered.configurate.NodePath; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions; import org.spongepowered.configurate.transformation.ConfigurationTransformation; import org.spongepowered.configurate.yaml.YamlConfigurationLoader; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.function.Consumer; -public class ConfigLoaderTemp { +import static org.spongepowered.configurate.NodePath.path; +import static org.spongepowered.configurate.transformation.TransformAction.remove; +import static org.spongepowered.configurate.transformation.TransformAction.rename; + +public final class ConfigLoaderTemp { private static final String HEADER = """ -------------------------------- Geyser Configuration File @@ -51,35 +54,81 @@ public class ConfigLoaderTemp { In most cases, especially with server hosting providers, further hosting-specific configuration is required. --------------------------------"""; - public static T load(Class configClass) throws IOException { - var loader = YamlConfigurationLoader.builder() - .file(new File("newconfig.yml")) - .defaultOptions(options -> InterfaceDefaultOptions.addTo(options.header(HEADER))) - .build(); - ConfigurationNode node = loader.load(); - // temp fix for node.virtual() being broken - var virtual = !Files.exists(Path.of("newconfig.yml")); + public static T load(File file, Class configClass) throws IOException { + return load(file, configClass, null); + } - // TODO needed or else Configurate breaks + public static T load(File file, Class configClass, @Nullable Consumer transformer) throws IOException { + var loader = YamlConfigurationLoader.builder() + .file(file) + .defaultOptions(options -> InterfaceDefaultOptions.addTo(options + .header(HEADER) + .serializers(builder -> builder.register(new LowercaseEnumSerializer())))) + .build(); + var node = loader.load(); + // temp fix for node.virtual() being broken + boolean virtual = file.exists(); + + // Note for Tim? Needed or else Configurate breaks. var migrations = ConfigurationTransformation.versionedBuilder() + .versionKey("config-version") // Pre-Configurate .addVersion(5, ConfigurationTransformation.builder() - .addAction(NodePath.path("legacyPingPassthrough"), (path, value) -> { - // Invert value - value.set(Boolean.FALSE.equals(value.get(boolean.class))); - return new Object[]{"integratedPingPassthrough"}; - }) - .addAction(NodePath.path("remote"), (path, value) -> - new Object[]{"java"}) - .build()) + .addAction(path("legacy-ping-passthrough"), configClass == GeyserRemoteConfig.class ? remove() : (path, value) -> { + // Invert value + value.set(!value.getBoolean()); + return new Object[]{"integrated-ping-passthrough"}; + }) + .addAction(path("remote"), rename("java")) + .addAction(path("floodgate-key-file"), (path, value) -> { + // Elimate any legacy config values + if ("public-key.pem".equals(value.getString())) { + value.set("key.pem"); + } + return null; + }) + .addAction(path("default-locale"), (path, value) -> { + if (value.getString() == null) { + value.set("system"); + } + return null; + }) + .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 null; + }) + .addAction(path("bedrock", "motd1"), rename("primary-motd")) + .addAction(path("bedrock", "motd2"), rename("secondary-motd")) + // Legacy config values + .addAction(path("emote-offhand-workaround"), remove()) + .addAction(path("allow-third-party-capes"), remove()) + .addAction(path("allow-third-party-ears"), remove()) + .addAction(path("general-thread-pool"), remove()) + .addAction(path("cache-chunks"), remove()) + .build()) .build(); + int currentVersion = migrations.version(node); migrations.apply(node); + int newVersion = migrations.version(node); T config = node.get(configClass); - System.out.println(config); - loader.save(node); + if (virtual || currentVersion != newVersion) { + loader.save(node); + } + + if (transformer != null) { + // Do not let the transformer change the node. + transformer.accept(node); + config = node.get(configClass); + } return config; } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java index 059d1c548..6828200eb 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java @@ -31,6 +31,7 @@ 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.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; @@ -39,8 +40,9 @@ import org.spongepowered.configurate.interfaces.meta.range.NumericRange; import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.Comment; -import java.nio.file.Path; +import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.UUID; @ConfigSerializable @@ -49,7 +51,9 @@ public interface GeyserConfig { JavaConfig java(); - Path floodgateKeyPath(); + // Cannot be type File yet because we want to hide it in plugin instances. + @DefaultString("key.pem") + String floodgateKeyFile(); @Comment(""" For online mode authentication type only. @@ -108,8 +112,9 @@ public interface GeyserConfig { https://cdn.discordapp.com/attachments/613170125696270357/957075682230419466/Screenshot_from_2022-03-25_20-35-08.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\"""") - @DefaultString("title") - String showCooldown(); + default CooldownUtils.CooldownType showCooldown() { + return CooldownUtils.CooldownType.TITLE; + } @Comment("Controls if coordinates are shown to players.") @DefaultBoolean(true) @@ -118,9 +123,9 @@ public interface GeyserConfig { @Comment("Whether Bedrock players are blocked from performing their scaffolding-style bridging.") boolean disableBedrockScaffolding(); - //@DefaultString("disabled") - EmoteOffhandWorkaroundOption emoteOffhandWorkaround(); - + @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.") + @NonNull + @DefaultString("system") String defaultLocale(); @Comment(""" @@ -207,13 +212,15 @@ public interface GeyserConfig { @Override @Comment("The port that will listen for connections") @DefaultNumeric(19132) + @NumericRange(from = 0, to = 65535) int port(); @Override @Comment(""" The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. - DO NOT uncomment and change this unless Geyser runs on a different internal port than the one that is used to connect.""") + DO NOT change this unless Geyser runs on a different internal port than the one that is used to connect.""") @DefaultNumeric(19132) + @NumericRange(from = 0, to = 65535) int broadcastPort(); void address(String address); @@ -223,13 +230,21 @@ public interface GeyserConfig { void broadcastPort(int broadcastPort); @Override + @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(); @Override - @DefaultString("Another Geyser server.") // TODO migrate or change name + @DefaultString("Another Geyser server.") String secondaryMotd(); + @Override + @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(""" 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.""") @@ -237,6 +252,9 @@ public interface GeyserConfig { @NumericRange(from = -1, to = 9) int compressionLevel(); + @Comment(""" + Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy + in front of your Geyser instance.""") @DefaultBoolean boolean enableProxyProtocol(); @@ -245,13 +263,9 @@ public interface GeyserConfig { 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.""") - List proxyProtocolWhitelistedIPs(); - -// /** -// * @return Unmodifiable list of {@link CIDRMatcher}s from {@link #proxyProtocolWhitelistedIPs()} -// */ -// @Exclude -// List whitelistedIPsMatchers(); + default List proxyProtocolWhitelistedIps() { + return Collections.emptyList(); + } } @ConfigSerializable @@ -270,6 +284,8 @@ public interface GeyserConfig { return AuthType.ONLINE; } + void authType(AuthType authType); + @Comment(""" Whether to enable PROXY protocol or not while connecting to the server. This is useful only when: @@ -297,17 +313,16 @@ public interface GeyserConfig { default boolean resolveSrv() { return false; } - - void authType(AuthType authType); } @ConfigSerializable interface MetricsInfo { - + @Comment("If metrics should be enabled") @DefaultBoolean(true) boolean enabled(); - default UUID uniqueId() { //TODO rename? + @Comment("UUID of server. Don't change!") + default UUID uuid() { //TODO rename? return UUID.randomUUID(); } } @@ -333,7 +348,15 @@ public interface GeyserConfig { int mtu(); @Comment("Do not change!") - default int version() { + default int configVersion() { return Constants.CONFIG_VERSION; } + + @Exclude + default Optional asPluginConfig() { + if (this instanceof GeyserPluginConfig config) { + return Optional.of(config); + } + return Optional.empty(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java index 0676f0e8c..c2f9b97a0 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java @@ -32,6 +32,8 @@ import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.meta.Comment; +import java.io.File; + @ConfigSerializable public interface GeyserPluginConfig extends GeyserConfig { @Override @@ -89,6 +91,10 @@ public interface GeyserPluginConfig extends GeyserConfig { } } + @Override + @Hidden + String floodgateKeyFile(); + @Comment(""" 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.""") diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java index e44fdedd0..fa3e53aec 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java @@ -25,10 +25,9 @@ package org.geysermc.geyser.configuration; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.network.AuthType; 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; @@ -37,12 +36,17 @@ import org.spongepowered.configurate.objectmapping.meta.Comment; */ @ConfigSerializable public interface GeyserRemoteConfig extends GeyserConfig { -// @Override // For config placement -// BedrockConfig bedrock(); - @Override RemoteConfig java(); + @Override + @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.""") + String floodgateKeyFile(); + @ConfigSerializable interface RemoteConfig extends JavaConfig { @Override @@ -53,13 +57,9 @@ public interface GeyserRemoteConfig extends GeyserConfig { @Override @Comment("The port of the Java Edition server.") @DefaultNumeric(25565) + @NumericRange(from = 0, to = 65535) int port(); - @Override // For config placement - default @NonNull AuthType authType() { - return JavaConfig.super.authType(); - } - @Override @Comment(""" Forward the hostname that the Bedrock client used to connect over to the Java server diff --git a/core/src/main/java/org/geysermc/geyser/configuration/LowercaseEnumSerializer.java b/core/src/main/java/org/geysermc/geyser/configuration/LowercaseEnumSerializer.java new file mode 100644 index 000000000..5414ab2e2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/LowercaseEnumSerializer.java @@ -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> { + LowercaseEnumSerializer() { + super(new TypeToken>() {}); + } + + @Override + public Enum deserialize(Type type, Object obj) throws SerializationException { + return Scalars.ENUM.deserialize(type, obj); + } + + @Override + protected Object serialize(Enum item, Predicate> typeSupported) { + return item.name().toLowerCase(Locale.ROOT); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index bd8d85440..a6186026c 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -25,13 +25,12 @@ package org.geysermc.geyser.dump; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.hash.Hashing; import com.google.common.io.ByteSource; import com.google.common.io.Files; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Getter; @@ -41,7 +40,7 @@ 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.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.AsteriskSerializer; @@ -57,12 +56,16 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; 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.Properties; import java.util.stream.Collectors; @Getter public class DumpInfo { - @JsonIgnore private static final long MEGABYTE = 1024L * 1024L; private final DumpInfo.VersionInfo versionInfo; @@ -71,7 +74,7 @@ public class DumpInfo { private final Locale systemLocale; private final String systemEncoding; private final GitInfo gitInfo; - private final GeyserConfiguration config; + private final GeyserConfig config; private final Floodgate floodgate; private final Object2IntMap userPlatforms; private final int connectionAttempts; @@ -92,7 +95,7 @@ public class DumpInfo { this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY); - this.config = GeyserImpl.getInstance().getConfig(); + this.config = GeyserImpl.getInstance().config(); this.floodgate = new Floodgate(); String md5Hash = "unknown"; @@ -108,7 +111,7 @@ public class DumpInfo { //noinspection UnstableApiUsage sha256Hash = byteSource.hash(Hashing.sha256()).toString(); } catch (Exception e) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } } @@ -280,7 +283,7 @@ public class DumpInfo { public record ExtensionInfo(boolean enabled, String name, String version, String apiVersion, String main, List 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) { } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java index f9b65a545..85458abb6 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java @@ -71,7 +71,7 @@ public record EntityDefinition(EntityFactory 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; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 08e87dc03..6024cde8f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -185,7 +185,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 + ")"); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index 53b02ef88..dc9773c04 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -342,7 +342,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; diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index c7aabb806..ca95dad00 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -90,7 +90,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().bedrock().compressionLevel()); this.compressionStrategy = new SimpleCompressionStrategy(compression); } @@ -211,7 +211,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(), "", header.uuid().toString(), false, false)); } - resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); + resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().config().forceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); GeyserLocale.loadGeyserLocale(session.locale()); @@ -222,7 +222,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public PacketSignal handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: - 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 @@ -247,7 +247,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), "")); } - if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { + if (GeyserImpl.getInstance().config().addNonBedrockItems()) { // Allow custom items to work stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } @@ -273,7 +273,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean couldLoginUserByName(String bedrockUsername) { - if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) { + if (geyser.config().savedUserLogins().contains(bedrockUsername)) { String refreshToken = geyser.refreshTokenFor(bedrockUsername); if (refreshToken != null) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java index f2ae7c685..2340e021f 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java @@ -27,7 +27,7 @@ package org.geysermc.geyser.network.netty; import io.netty.channel.ChannelFuture; import lombok.Getter; -import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserPluginBootstrap; import java.net.SocketAddress; @@ -49,8 +49,8 @@ public abstract class GeyserInjector { /** * @param bootstrap the bootstrap of the Geyser instance. */ - public void initializeLocalChannel(GeyserBootstrap bootstrap) { - if (!bootstrap.getGeyserConfig().isUseDirectConnection()) { + public void initializeLocalChannel(GeyserPluginBootstrap bootstrap) { + if (!bootstrap.config().useDirectConnection()) { bootstrap.getGeyserLogger().debug("Disabling direct injection!"); return; } @@ -71,9 +71,9 @@ public abstract class GeyserInjector { } /** - * The method to implement that is called by {@link #initializeLocalChannel(GeyserBootstrap)} wrapped around a try/catch. + * The method to implement that is called by {@link #initializeLocalChannel(GeyserPluginBootstrap)} wrapped around a try/catch. */ - protected abstract void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception; + protected abstract void initializeLocalChannel0(GeyserPluginBootstrap bootstrap) throws Exception; public void shutdown() { if (localChannel != null && localChannel.channel().isOpen()) { diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index a67bd8a32..db90c3ef7 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -53,7 +53,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; @@ -66,14 +66,18 @@ 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 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; import java.util.function.IntFunction; import java.util.function.Supplier; +import java.util.stream.Collectors; import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT; import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_PACKET_LIMIT; @@ -135,7 +139,7 @@ public final class GeyserServer { this.listenCount = 1; } - if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + if (this.geyser.config().bedrock().enableProxyProtocol()) { this.proxiedAddresses = ExpiringMap.builder() .expiration(30 + 1, TimeUnit.MINUTES) .expirationPolicy(ExpirationPolicy.ACCESSED).build(); @@ -144,11 +148,11 @@ public final class GeyserServer { } // It's set to 0 only if no system property or manual config value was set - if (geyser.getConfig().getBedrock().broadcastPort() == 0) { - geyser.getConfig().getBedrock().setBroadcastPort(geyser.getConfig().getBedrock().port()); + if (geyser.config().bedrock().broadcastPort() == 0) { + geyser.config().bedrock().broadcastPort(geyser.config().bedrock().port()); } - this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort(); + this.broadcastPort = geyser.config().bedrock().broadcastPort(); } public CompletableFuture bind(InetSocketAddress address) { @@ -170,12 +174,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().bedrock().enableProxyProtocol(); 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().bedrock().proxyProtocolWhitelistedIps().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); @@ -205,7 +209,7 @@ public final class GeyserServer { } private ServerBootstrap createBootstrap() { - if (this.geyser.getConfig().isDebugMode()) { + if (this.geyser.config().debugMode()) { this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel()); if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) { if (System.getProperties().contains("disableNativeEventLoop")) { @@ -220,7 +224,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().mtu()); int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT); this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit); @@ -235,7 +239,7 @@ public final class GeyserServer { .channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel())) .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().mtu()) .option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit) .option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit) .option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie) @@ -243,10 +247,10 @@ public final class GeyserServer { } public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { - List allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs(); - if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) { + List allowedProxyIPs = geyser.config().bedrock().proxyProtocolWhitelistedIps(); + if (geyser.config().bedrock().enableProxyProtocol() && !allowedProxyIPs.isEmpty()) { boolean isWhitelistedIP = false; - for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) { + for (CIDRMatcher matcher : getWhitelistedIPsMatchers()) { if (matcher.matches(inetSocketAddress.getAddress())) { isWhitelistedIP = true; break; @@ -260,8 +264,8 @@ public final class GeyserServer { } String ip; - if (geyser.getConfig().isLogPlayerIpAddresses()) { - if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + if (geyser.config().logPlayerIpAddresses()) { + if (geyser.config().bedrock().enableProxyProtocol()) { ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString(); } else { ip = inetSocketAddress.toString(); @@ -287,10 +291,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().bedrock().enableProxyProtocol()) { ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString(); } else { ip = inetSocketAddress.toString(); @@ -301,10 +305,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.passthroughMotd() || config.passthroughPlayerCounts()) { IGeyserPingPassthrough pingPassthrough = geyser.getBootstrap().getGeyserPingPassthrough(); if (pingPassthrough != null) { pingInfo = pingPassthrough.getPingInformation(inetSocketAddress); @@ -321,25 +325,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.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.bedrock().primaryMotd(); // First line of the motd. + String subMotd = (motd.length > 1) ? motd[1] : config.bedrock().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.bedrock().primaryMotd()); + pong.subMotd(config.bedrock().secondaryMotd()); } // Placed here to prevent overriding values set in the ping event. - if (config.isPassthroughPlayerCounts() && pingInfo != null) { + if (config.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.maxPlayers()); } this.geyser.eventBus().fire(new GeyserBedrockPingEventImpl(pong, inetSocketAddress)); @@ -390,6 +394,35 @@ public final class GeyserServer { return pong; } + private List whitelistedIPsMatchers = null; + + /** + * @return Unmodifiable list of {@link CIDRMatcher}s from {@link GeyserConfig.BedrockConfig#proxyProtocolWhitelistedIps()} + */ + public List getWhitelistedIPsMatchers() { + // Effective Java, Third Edition; Item 83: Use lazy initialization judiciously + List matchers = this.whitelistedIPsMatchers; + if (matchers == null) { + synchronized (this) { + // Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line + List whitelistedCIDRs = new ArrayList<>(); + for (String ip: geyser.config().bedrock().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); + } + /** * @return the throwable from the given supplier, or the throwable caught while calling the supplier. */ diff --git a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java index 59651d139..03cdc60b2 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java +++ b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java @@ -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().addNonBedrockItems()) { 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(); } } diff --git a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java index ed08801d9..5ceeb1651 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -65,10 +65,10 @@ 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()) { + if (geyser.config().passthroughMotd() || geyser.config().passthroughPlayerCounts()) { GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser); // Ensure delay is not zero - int interval = (geyser.getConfig().getPingPassthroughInterval() == 0) ? 1 : geyser.getConfig().getPingPassthroughInterval(); + int interval = (geyser.config().pingPassthroughInterval() == 0) ? 1 : geyser.config().pingPassthroughInterval(); geyser.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s)."); geyser.getScheduledThread().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); return pingPassthrough; @@ -84,8 +84,8 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn @Override public void run() { 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); @@ -102,7 +102,7 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn byte[] buffer; try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream())) { - if (geyser.getConfig().getRemote().isUseProxyProtocol()) { + if (geyser.config().java().useProxyProtocol()) { // 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); diff --git a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java index 9a5b43816..283a8b869 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java @@ -70,7 +70,7 @@ public class PacketTranslatorRegistry extends AbstractMappedRegistry 25 ? packet.getClass().getSimpleName() : packet)); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java index a43df3f52..734f1ccb9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java @@ -90,7 +90,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().addNonBedrockItems()) { return; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java index 8494edc2b..e44550d71 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java @@ -55,7 +55,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().addNonBedrockItems()) { return; } @@ -65,7 +65,6 @@ public class CustomSkullRegistryPopulator { Path skullConfigPath = bootstrap.getConfigFolder().resolve("custom-skulls.yml"); File skullConfigFile = FileUtils.fileOrCopiedFromResource(skullConfigPath.toFile(), "custom-skulls.yml", Function.identity(), bootstrap); skullConfig = FileUtils.loadConfigNew(skullConfigFile, GeyserCustomSkullConfiguration.class); - System.out.println(skullConfig); } catch (IOException e) { GeyserImpl.getInstance().getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e); return; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 32d420efa..551770b8a 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -129,7 +129,7 @@ public class ItemRegistryPopulator { throw new AssertionError("Unable to load Bedrock item components", e); } - boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); + boolean customItemsAllowed = GeyserImpl.getInstance().config().addNonBedrockItems(); // 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 diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java index 395eb9576..6c9f8a695 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java @@ -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.scoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); + DEBUG_ENABLED = config.debugMode(); } private final GeyserImpl geyser = GeyserImpl.getInstance(); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 7f015a05e..2d5f2cbdc 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -46,17 +46,52 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.IntRange; -import org.cloudburstmc.math.vector.*; +import org.cloudburstmc.math.vector.Vector2f; +import org.cloudburstmc.math.vector.Vector2i; +import org.cloudburstmc.math.vector.Vector3d; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons; import org.cloudburstmc.protocol.bedrock.BedrockServerSession; -import org.cloudburstmc.protocol.bedrock.data.*; +import org.cloudburstmc.protocol.bedrock.data.Ability; +import org.cloudburstmc.protocol.bedrock.data.AbilityLayer; +import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode; +import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel; +import org.cloudburstmc.protocol.bedrock.data.ExperimentData; +import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting; +import org.cloudburstmc.protocol.bedrock.data.GameRuleData; +import org.cloudburstmc.protocol.bedrock.data.GameType; +import org.cloudburstmc.protocol.bedrock.data.PlayerPermission; +import org.cloudburstmc.protocol.bedrock.data.SoundEvent; +import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType; import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData; import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission; import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket; +import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket; +import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; +import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket; +import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket; +import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket; +import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet; +import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket; +import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket; +import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket; +import org.cloudburstmc.protocol.bedrock.packet.TextPacket; +import org.cloudburstmc.protocol.bedrock.packet.TransferPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAbilitiesPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAdventureSettingsPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateSoftEnumPacket; import org.cloudburstmc.protocol.common.DefinitionRegistry; import org.cloudburstmc.protocol.common.util.OptionalBoolean; import org.geysermc.api.util.BedrockPlatform; @@ -80,7 +115,6 @@ import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.GeyserEntityData; @@ -107,7 +141,22 @@ import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; -import org.geysermc.geyser.session.cache.*; +import org.geysermc.geyser.session.cache.AdvancementsCache; +import org.geysermc.geyser.session.cache.BookEditCache; +import org.geysermc.geyser.session.cache.ChunkCache; +import org.geysermc.geyser.session.cache.EntityCache; +import org.geysermc.geyser.session.cache.EntityEffectCache; +import org.geysermc.geyser.session.cache.FormCache; +import org.geysermc.geyser.session.cache.LodestoneCache; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.session.cache.PreferencesCache; +import org.geysermc.geyser.session.cache.RegistryCache; +import org.geysermc.geyser.session.cache.SkullCache; +import org.geysermc.geyser.session.cache.StructureBlockCache; +import org.geysermc.geyser.session.cache.TagCache; +import org.geysermc.geyser.session.cache.TeleportCache; +import org.geysermc.geyser.session.cache.WorldBorder; +import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; @@ -119,7 +168,11 @@ import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.mcprotocollib.network.BuiltinFlags; import org.geysermc.mcprotocollib.network.Session; -import org.geysermc.mcprotocollib.network.event.session.*; +import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent; +import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent; +import org.geysermc.mcprotocollib.network.event.session.PacketErrorEvent; +import org.geysermc.mcprotocollib.network.event.session.PacketSendingEvent; +import org.geysermc.mcprotocollib.network.event.session.SessionAdapter; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.network.tcp.TcpClientSession; import org.geysermc.mcprotocollib.network.tcp.TcpSession; @@ -154,7 +207,16 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ScheduledFuture; @@ -600,12 +662,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.emotes = new HashSet<>(); + geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes())); this.remoteServer = geyser.defaultRemoteServer(); } @@ -618,7 +676,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { sentSpawnPacket = true; syncEntityProperties(); - if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { + if (GeyserImpl.getInstance().config().addNonBedrockItems()) { ItemComponentPacket componentPacket = new ItemComponentPacket(); componentPacket.getItems().addAll(itemMappings.getComponentItemData()); upstream.sendPacket(componentPacket); @@ -870,11 +928,11 @@ 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().java().useProxyProtocol()) { downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); } - if (geyser.getConfig().isForwardPlayerPing()) { + if (geyser.config().forwardPlayerPing()) { // Let Geyser handle sending the keep alive downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); } @@ -943,7 +1001,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { ClientIntentionPacket intentionPacket = event.getPacket(); String address; - if (geyser.getConfig().getRemote().isForwardHost()) { + if (geyser.config().java().forwardHostname()) { address = clientData.getServerAddress().split(":")[0]; } else { address = intentionPacket.getHostname(); @@ -1035,7 +1093,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } else { GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause); } - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { cause.printStackTrace(); } } @@ -1058,7 +1116,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public void packetError(PacketErrorEvent event) { geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); - if (geyser.getConfig().isDebugMode()) + if (geyser.config().debugMode()) event.getCause().printStackTrace(); event.setSuppress(true); } @@ -1090,7 +1148,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() : ""; + String address = geyser.config().logPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : ""; geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, reason)); } @@ -1482,7 +1540,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().xboxAchievementsEnabled()); startGamePacket.setCurrentTick(-1); startGamePacket.setEduEditionOffers(0); startGamePacket.setEduFeaturesEnabled(false); @@ -1492,7 +1550,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().xboxAchievementsEnabled()); startGamePacket.setTexturePacksRequired(false); startGamePacket.setBonusChestEnabled(false); startGamePacket.setStartingWithMap(false); @@ -1510,7 +1568,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setEducationProductionId(""); startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty()); - String serverName = geyser.getConfig().getBedrock().serverName(); + String serverName = geyser.config().bedrock().serverName(); startGamePacket.setLevelId(serverName); startGamePacket.setLevelName(serverName); @@ -1634,7 +1692,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(); } @@ -1660,7 +1718,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; @@ -2051,7 +2109,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().commandSuggestions()) { return; } UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java index 07dd38491..8aad61ede 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java @@ -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 { + @Override + public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return json.getAsString().getBytes(StandardCharsets.UTF_8); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java index 2aeb83fa8..78a6b75fe 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java @@ -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().allowCustomSkulls(); + cooldownPreference = session.getGeyser().config().showCooldown(); } /** @@ -74,7 +75,7 @@ public class PreferencesCache { * {@link GeyserConfiguration#isShowCoordinates()} is disabled */ public void updateShowCoordinates() { - allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().getConfig().isShowCoordinates(); + allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().config().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().allowCustomSkulls(); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java index a40a1156d..050976c3b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -74,11 +74,11 @@ public class SkullCache { public SkullCache(GeyserSession session) { this.session = session; - this.maxVisibleSkulls = session.getGeyser().getConfig().getMaxVisibleCustomSkulls(); + this.maxVisibleSkulls = session.getGeyser().config().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().customSkullRenderDistance(), 64); this.skullRenderDistanceSquared = distance * distance; } @@ -98,7 +98,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(); } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java index 4e4f52914..415b6fa4b 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -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 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); @@ -195,20 +191,13 @@ public final class FloodgateSkinUploader { return; } - ObjectNode node = JACKSON.createObjectNode(); - ArrayNode chainDataNode = JACKSON.createArrayNode(); + JsonObject node = new JsonObject(); + JsonArray chainDataNode = new JsonArray(); chainData.forEach(chainDataNode::add); - node.set("chain_data", chainDataNode); - node.put("client_data", clientData); + node.add("chain_data", chainDataNode); + 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); @@ -241,4 +230,4 @@ public final class FloodgateSkinUploader { client.close(); } } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 88a9c6d01..ea99597f1 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -25,7 +25,8 @@ package org.geysermc.geyser.skin; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; @@ -43,6 +44,7 @@ import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.JsonUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -194,7 +196,7 @@ public class SkinManager { public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { GeyserImpl geyser = GeyserImpl.getInstance(); - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); } @@ -208,7 +210,7 @@ public class SkinManager { if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); - } else if (geyser.getConfig().isDebugMode()) { + } else if (geyser.config().debugMode()) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); geyser.getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); } @@ -246,7 +248,7 @@ public class SkinManager { return loadFromJson(skinDataValue); } catch (IOException e) { GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for tag " + tag); - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } return null; @@ -274,7 +276,7 @@ public class SkinManager { } else { GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername() + " with Value: " + texturesProperty); } - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { exception.printStackTrace(); } } @@ -282,30 +284,25 @@ public class SkinManager { } public static @Nullable GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException { - // TODO use GameProfile method. - JsonNode skinObject; + JsonObject skinObject; try { - skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); + skinObject = JsonUtils.parseJson(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); } catch (IllegalArgumentException e) { GeyserImpl.getInstance().getLogger().debug("Invalid base64 encoded skin entry: " + encodedJson); return null; } - JsonNode textures = skinObject.get("textures"); - - if (textures == null) { + if (!(skinObject.get("textures") instanceof JsonObject textures)) { return null; } - JsonNode skinTexture = textures.get("SKIN"); - if (skinTexture == null) { + if (!(textures.get("SKIN") instanceof JsonObject skinTexture)) { return null; } String skinUrl; - JsonNode skinUrlNode = skinTexture.get("url"); - if (skinUrlNode != null && skinUrlNode.isTextual()) { - skinUrl = skinUrlNode.asText().replace("http://", "https://"); + if (skinTexture.get("url") instanceof JsonPrimitive skinUrlNode && skinUrlNode.isString()) { + skinUrl = skinUrlNode.getAsString().replace("http://", "https://"); } else { return null; } @@ -322,11 +319,9 @@ public class SkinManager { boolean isAlex = skinTexture.has("metadata"); String capeUrl = null; - JsonNode capeTexture = textures.get("CAPE"); - if (capeTexture != null) { - JsonNode capeUrlNode = capeTexture.get("url"); - if (capeUrlNode != null && capeUrlNode.isTextual()) { - capeUrl = capeUrlNode.asText().replace("http://", "https://"); + if (textures.get("CAPE") instanceof JsonObject capeTexture) { + if (capeTexture.get("url") instanceof JsonPrimitive capeUrlNode && capeUrlNode.isString()) { + capeUrl = capeUrlNode.getAsString().replace("http://", "https://"); } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 82c4a75f7..6efa4ef6c 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -126,11 +126,6 @@ public class SkinProvider { WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull); String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim); - - GeyserImpl geyser = GeyserImpl.getInstance(); - if (geyser.getConfig().isAllowThirdPartyEars() || geyser.getConfig().isAllowThirdPartyCapes()) { - geyser.getLogger().warning("Third-party ears/capes have been removed from Geyser, if you still wish to have this functionality please use the extension: https://github.com/GeyserMC/ThirdPartyCosmetics"); - } } public static ExecutorService getExecutorService() { @@ -149,7 +144,7 @@ public class SkinProvider { public static void registerCacheImageTask(GeyserImpl geyser) { // Schedule Daily Image Expiry if we are caching them - if (geyser.getConfig().getCacheImages() > 0) { + if (geyser.config().cacheImages() > 0) { geyser.getScheduledThread().scheduleAtFixedRate(() -> { File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { @@ -157,7 +152,7 @@ public class SkinProvider { } int count = 0; - final long expireTime = ((long) GeyserImpl.getInstance().getConfig().getCacheImages()) * ((long)1000 * 60 * 60 * 24); + final long expireTime = ((long) GeyserImpl.getInstance().config().cacheImages()) * ((long)1000 * 60 * 60 * 24); for (File imageFile : Objects.requireNonNull(cacheFolder.listFiles())) { if (imageFile.lastModified() < System.currentTimeMillis() - expireTime) { //noinspection ResultOfMethodCallIgnored @@ -188,7 +183,7 @@ public class SkinProvider { Cape cape = null; SkinGeometry geometry = SkinGeometry.WIDE; - if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) { + if (GeyserImpl.getInstance().config().java().authType() != AuthType.ONLINE) { // Let's see if this player is a Bedrock player, and if so, let's pull their skin. GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); if (session != null) { @@ -427,7 +422,7 @@ public class SkinProvider { GeyserImpl.getInstance().getLogger().debug("Downloaded " + imageUrl); // Write to cache if we are allowed - if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) { + if (GeyserImpl.getInstance().config().cacheImages() > 0) { imageFile.getParentFile().mkdirs(); try { ImageIO.write(image, "png", imageFile); @@ -496,7 +491,7 @@ public class SkinProvider { return properties.get(0).getAsJsonObject().get("value").getAsString(); } catch (Exception e) { GeyserImpl.getInstance().getLogger().debug("Unable to request textures for " + uuid); - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } return null; @@ -515,7 +510,6 @@ public class SkinProvider { try { // Offline skin, or no present UUID JsonObject node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username); - System.out.println(node); JsonElement id = node.get("id"); if (id == null) { GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username); @@ -523,7 +517,7 @@ public class SkinProvider { } return id.getAsString(); } catch (Exception e) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } return null; diff --git a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java index 28fd6f9a4..ac0ea2789 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -80,8 +80,8 @@ public class GeyserLocale { * Finalize the default locale, now that we know what the default locale should be. */ public static void finalizeDefaultLocale(GeyserImpl geyser) { - String newDefaultLocale = geyser.getConfig().getDefaultLocale(); - if (newDefaultLocale == null) { + String newDefaultLocale = geyser.config().defaultLocale(); + if ("system".equals(newDefaultLocale)) { // We want to use the system locale which is already loaded return; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 4c426b410..df5925a27 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -207,7 +207,7 @@ public abstract class InventoryTranslator { case PLACE: { TransferItemStackRequestAction transferAction = (TransferItemStackRequestAction) action; if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { - if (session.getGeyser().getConfig().isDebugMode()) { + if (session.getGeyser().config().debugMode()) { session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.bedrockUsername()); dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); } @@ -312,7 +312,7 @@ public abstract class InventoryTranslator { ItemStackRequestSlotData destination = swapAction.getDestination(); if (!(checkNetId(session, inventory, source) && checkNetId(session, inventory, destination))) { - if (session.getGeyser().getConfig().isDebugMode()) { + if (session.getGeyser().config().debugMode()) { session.getGeyser().getLogger().error("DEBUG: About to reject SWAP request made by " + session.bedrockUsername()); dumpStackRequestDetails(session, inventory, source, destination); } @@ -807,7 +807,7 @@ public abstract class InventoryTranslator { * as bad (false). */ protected static ItemStackResponse rejectRequest(ItemStackRequest request, boolean throwError) { - if (throwError && GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (throwError && GeyserImpl.getInstance().config().debugMode()) { new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); } return new ItemStackResponse(ItemStackResponseStatus.ERROR, request.getRequestId(), Collections.emptyList()); @@ -952,4 +952,4 @@ public abstract class InventoryTranslator { TRANSFER, DONE } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index c2d457202..db3f85946 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -120,7 +120,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements return skull.getBlockDefinition(); } catch (InterruptedException | ExecutionException e) { session.getGeyser().getLogger().debug("Failed to acquire textures for custom skull: " + blockPosition + " " + javaNbt); - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java index b20f7a2dd..a7d1c3c14 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.translator.protocol.bedrock; import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket; -import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -36,10 +35,6 @@ public class BedrockEmoteListTranslator extends PacketTranslator yaw <= -135f || yaw > 135f; @@ -696,4 +696,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 0) { - if (session.getGeyser().getConfig().isForwardPlayerPing()) { + if (session.getGeyser().config().forwardPlayerPing()) { // use our cached value because // a) bedrock can be inaccurate with the value returned // b) playstation replies with a different magnitude than other platforms diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java index de2df0cb7..a738f2a3a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java @@ -45,7 +45,7 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat if (session.remoteServer().authType() == AuthType.ONLINE) { if (!session.isLoggedIn()) { - if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) { + if (session.getGeyser().config().savedUserLogins().contains(session.bedrockUsername())) { if (session.getGeyser().refreshTokenFor(session.bedrockUsername()) == null) { LoginEncryptionUtils.buildAndShowConsentWindow(session); } else { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java index 7a37aa72e..13683b2dc 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import org.cloudburstmc.protocol.bedrock.packet.EmotePacket; import org.geysermc.geyser.api.event.bedrock.ClientEmoteEvent; -import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; @@ -39,15 +38,6 @@ public class BedrockEmoteTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, EmotePacket packet) { - if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.DISABLED) { - // Activate the workaround - we should trigger the offhand now - session.requestOffhandSwap(); - - if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { - return; - } - } - // For the future: could have a method that exposes which players will see the emote ClientEmoteEvent event = new ClientEmoteEvent(session, packet.getEmoteId()); session.getGeyser().eventBus().fire(event); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index ecfb2d220..433e586da 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -113,7 +113,7 @@ public class JavaCommandsTranslator extends PacketTranslator { StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData(); if (stoneCuttingData.getIngredient().getOptions().length == 0) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { GeyserImpl.getInstance().getLogger().debug("Received broken stone cutter recipe: " + stoneCuttingData + " " + recipe.getIdentifier() + " " + Registries.JAVA_ITEMS.get().get(stoneCuttingData.getResult().getId()).javaIdentifier()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java index 2dc780c66..44ab214aa 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java @@ -45,7 +45,7 @@ public class JavaSetEntityDataTranslator extends PacketTranslator definition = entity.getDefinition(); for (EntityMetadata metadata : packet.getMetadata()) { if (metadata.getId() >= definition.translators().size()) { - if (session.getGeyser().getConfig().isDebugMode()) { + if (session.getGeyser().config().debugMode()) { // Minecraft client just ignores these session.getGeyser().getLogger().warning("Metadata ID " + metadata.getId() + " is out of bounds of known entity metadata size " + definition.translators().size() + " for entity type " + entity.getDefinition().entityType()); session.getGeyser().getLogger().debug(metadata.toString()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 413833acf..3bd069348 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -91,7 +91,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslatorViaRewind */ public class CooldownUtils { - private static CooldownType DEFAULT_SHOW_COOLDOWN; - - public static void setDefaultShowCooldown(String showCooldown) { - DEFAULT_SHOW_COOLDOWN = CooldownType.getByName(showCooldown); - } - - public static CooldownType getDefaultShowCooldown() { - return DEFAULT_SHOW_COOLDOWN; - } - /** * Starts sending the fake cooldown to the Bedrock client. If the cooldown is not disabled, the sent type is the cooldownPreference in {@link PreferencesCache} * @param session GeyserSession */ public static void sendCooldown(GeyserSession session) { - if (DEFAULT_SHOW_COOLDOWN == CooldownType.DISABLED) return; + if (session.getGeyser().config().showCooldown() == CooldownType.DISABLED) return; CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference(); if (sessionPreference == CooldownType.DISABLED) return; @@ -161,10 +151,6 @@ public class CooldownUtils { * @return The converted CooldownType */ public static CooldownType getByName(String name) { - if (name.equalsIgnoreCase("true")) { // Backwards config compatibility - return CooldownType.TITLE; - } - for (CooldownType type : VALUES) { if (type.name().equalsIgnoreCase(name)) { return type; diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 48ade52e2..eeb0a116d 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -179,7 +179,7 @@ public class InventoryUtils { } public static boolean canStack(GeyserItemStack item1, GeyserItemStack item2) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) + if (GeyserImpl.getInstance().config().debugMode()) canStackDebug(item1, item2); if (item1.isEmpty() || item2.isEmpty()) return false; @@ -231,7 +231,7 @@ public class InventoryUtils { private static ItemDefinition getUnusableSpaceBlockDefinition(int protocolVersion) { ItemMappings mappings = Registries.ITEMS.forVersion(protocolVersion); - String unusableSpaceBlock = GeyserImpl.getInstance().getConfig().getUnusableSpaceBlock(); + String unusableSpaceBlock = GeyserImpl.getInstance().config().unusableSpaceBlock(); ItemDefinition itemDefinition = mappings.getDefinition(unusableSpaceBlock); if (itemDefinition == null) { diff --git a/core/src/main/java/org/geysermc/geyser/util/JsonUtils.java b/core/src/main/java/org/geysermc/geyser/util/JsonUtils.java index a1f11e860..d1e4a9d99 100644 --- a/core/src/main/java/org/geysermc/geyser/util/JsonUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/JsonUtils.java @@ -48,6 +48,10 @@ public final class JsonUtils { return (JsonObject) new JsonParser().parse(new InputStreamReader(stream)); } + public static JsonObject parseJson(String s) { + return (JsonObject) new JsonParser().parse(s); + } + public static T fromJson(InputStream stream, Type type) { return GeyserImpl.GSON.fromJson(new InputStreamReader(stream), type); } diff --git a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java index 478a6ef96..c9d4e87bf 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.util; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.github.steveice10.mc.auth.service.MsaAuthenticationService; import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket; @@ -53,8 +50,6 @@ import java.util.List; import java.util.function.BiConsumer; public class LoginEncryptionUtils { - private static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - private static boolean HAS_SENT_ENCRYPTION_MESSAGE = false; public static void encryptPlayerConnection(GeyserSession session, LoginPacket loginPacket) { @@ -69,7 +64,7 @@ public class LoginEncryptionUtils { geyser.getLogger().debug(String.format("Is player data signed? %s", result.signed())); - if (!result.signed() && !session.getGeyser().getConfig().isEnableProxyConnections()) { + if (!result.signed() && !session.getGeyser().config().enableProxyConnections()) { session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.remote.invalid_xbox_account")); return; } @@ -85,8 +80,7 @@ public class LoginEncryptionUtils { throw new IllegalStateException("Client data isn't signed by the given chain data"); } - JsonNode clientDataJson = JSON_MAPPER.readTree(clientDataPayload); - BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); + BedrockClientData data = JsonUtils.fromJson(clientDataPayload, BedrockClientData.class); data.setOriginalString(clientData); session.setClientData(data); @@ -94,7 +88,7 @@ public class LoginEncryptionUtils { startEncryptionHandshake(session, identityPublicKey); } catch (Throwable e) { // An error can be thrown on older Java 8 versions about an invalid key - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { e.printStackTrace(); } @@ -213,7 +207,7 @@ public class LoginEncryptionUtils { .append("\n%xbox.signin.enterCode\n") .append(ChatColor.GREEN) .append(msCode.user_code); - int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout(); + int timeout = session.getGeyser().config().pendingAuthenticationTimeout(); if (timeout != 0) { message.append("\n\n") .append(ChatColor.RESET) diff --git a/core/src/main/java/org/geysermc/geyser/util/Metrics.java b/core/src/main/java/org/geysermc/geyser/util/Metrics.java deleted file mode 100644 index a4ef301e3..000000000 --- a/core/src/main/java/org/geysermc/geyser/util/Metrics.java +++ /dev/null @@ -1,447 +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.util; - -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 org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.GeyserImpl; - -import javax.net.ssl.HttpsURLConnection; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.GZIPOutputStream; - -/** - * bStats collects some data for plugin authors. - *

- * Check out bStats to learn more about bStats! - */ -public class Metrics { - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/server-implementation"; - - // Should failed requests be logged? - private static boolean logFailedRequests = false; - - // The logger for the failed requests - private static Logger logger = Logger.getLogger("bStats"); - - // The name of the server software - private final String name; - - // The uuid of the server - private final String serverUUID; - - // A list with all custom charts - private final List charts = new ArrayList<>(); - - private final static ObjectMapper mapper = new ObjectMapper(); - - private final GeyserImpl geyser; - - /** - * Class constructor. - * - * @param geyser The Geyser instance - * @param name The name of the server software. - * @param serverUUID The uuid of the server. - * @param logFailedRequests Whether failed requests should be logged or not. - * @param logger The logger for the failed requests. - */ - public Metrics(GeyserImpl geyser, String name, String serverUUID, boolean logFailedRequests, Logger logger) { - this.geyser = geyser; - this.name = name; - this.serverUUID = serverUUID; - Metrics.logFailedRequests = logFailedRequests; - Metrics.logger = logger; - - // Start submitting the data - startSubmitting(); - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - geyser.getScheduledThread().scheduleAtFixedRate(this::submitData, 1, 30, TimeUnit.MINUTES); - // Submit the data every 30 minutes, first time after 1 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. - * - * @return The plugin specific data. - */ - private ObjectNode getPluginData() { - ObjectNode data = mapper.createObjectNode(); - - data.put("pluginName", name); // Append the name of the server software - data.put("pluginVersion", GeyserImpl.VERSION); // Append the name of the server software - - ArrayNode customCharts = mapper.createArrayNode(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JsonNode chart = customChart.getRequestJsonNode(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.set("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - private ObjectNode getServerData() { - // 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(); - - ObjectNode data = mapper.createObjectNode(); - - data.put("serverUUID", serverUUID); - - data.put("osName", osName); - data.put("osArch", osArch); - data.put("osVersion", osVersion); - data.put("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - private void submitData() { - final ObjectNode data = getServerData(); - - ArrayNode pluginData = mapper.createArrayNode(); - pluginData.add(getPluginData()); - data.putPOJO("plugins", pluginData); - - new Thread(() -> { - try { - // We are still in the Thread of the timer, so nothing get blocked :) - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - logger.log(Level.WARNING, "Could not submit stats of " + name, e); - } - } - }).start(); - } - - /** - * Sends the data to the bStats server. - * - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(ObjectNode data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.write(compressedData); - outputStream.flush(); - outputStream.close(); - - connection.getInputStream().close(); // We don't care about the response - Just send our data :) - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte @NonNull [] compress(final @NonNull String str) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - gzip.close(); - return outputStream.toByteArray(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - final String chartId; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - private @Nullable ObjectNode getRequestJsonNode() { - ObjectNode chart = new ObjectMapper().createObjectNode(); - chart.put("chartId", chartId); - try { - ObjectNode data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.putPOJO("data", data); - } catch (Throwable t) { - if (logFailedRequests) { - logger.log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return chart; - } - - - - protected abstract ObjectNode getChartData() throws Exception; - - } - - /** - * Represents a custom simple pie. - */ - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected @Nullable ObjectNode getChartData() throws Exception { - ObjectNode data = mapper.createObjectNode(); - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - } - - /** - * Represents a custom advanced pie. - */ - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected @Nullable ObjectNode getChartData() throws Exception { - ObjectNode data = mapper.createObjectNode(); - ObjectNode values = mapper.createObjectNode(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.putPOJO("values", values); - return data; - } - } - - /** - * Represents a custom drilldown pie. - */ - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public @Nullable ObjectNode getChartData() throws Exception { - ObjectNode data = mapper.createObjectNode(); - ObjectNode values = mapper.createObjectNode(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - ObjectNode value = mapper.createObjectNode(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - value.put(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - values.putPOJO(entryValues.getKey(), value); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - data.putPOJO("values", values); - return data; - } - } - - /** - * Represents a custom single line chart. - */ - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected @Nullable ObjectNode getChartData() throws Exception { - ObjectNode data = mapper.createObjectNode(); - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - - } - -} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java index ed97408b9..c87eb0db0 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -51,8 +51,8 @@ public class SettingsUtils { // Let's store these to avoid issues boolean showCoordinates = session.getPreferencesCache().isAllowShowCoordinates(); - boolean cooldownShown = CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED; - boolean customSkulls = session.getGeyser().getConfig().isAllowCustomSkulls(); + boolean cooldownShown = session.getGeyser().config().showCooldown() != CooldownUtils.CooldownType.DISABLED; + boolean customSkulls = session.getGeyser().config().allowCustomSkulls(); // Only show the client title if any of the client settings are available boolean showClientSettings = showCoordinates || cooldownShown || customSkulls; diff --git a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java index 0e4c7154a..1c9df9c08 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -199,7 +199,7 @@ public class WebUtils { return ((String) attr.get(0)).split(" "); } } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { geyser.getLogger().debug("Exception while trying to find an SRV record for the remote host."); ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5ceb1605..d8695642f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] base-api = "1.0.0-SNAPSHOT" +bstats = "3.0.2" cumulus = "1.1.2" configurate = "4.2.0-GeyserMC-SNAPSHOT" erosion = "1.1-20240515.191456-1" @@ -130,6 +131,8 @@ protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-con math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" } +bstats = { group = "org.bstats", name = "bstats-base", version.ref = "bstats"} + # plugins indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" } shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" }