From 765128ce42ecf3c8418c4134c6e4e014579e884a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:55:12 -0500 Subject: [PATCH] Switch config system to Configurate (#5010) * Start implementing Configurate config system * More development * Start migrating to Gson only * Progress * Update usage of WebUtils * Most things now use Gson for JSON * Allow tests to succeed by using new Gson version * Use slightly cleaner version for Version deserializer * Work around older Gson versions without record support * GeyserCustomSkullConfiguration uses Configurate * Fix regression in properties get * New config used in core * The configuration is gone. Long live the config. * More changes and migrations away from Jackson * Improve node ordering when updating configs * typo * Better check for ignoring non-configurate configs for considering comment moving * Ensure metrics UUID is valid * Initial advanced config * Remove Jackson; finish config value placements * Remove duplicate relocate declarations * Let annotations work * Renaming to PluginSpecific * Use global bStats config where possible * Fix test * Re-introduce asterisk behavior in configs * Remove GeyserPluginBootstrap as it's no longer necessary * Remove old config.yml file * Update Xbox achievement comment * Apply suggestions from code review Co-authored-by: chris * No need to remove values anymore * Fix: disable bstats relocation on platforms where it is not needed * ensure it builds * Update custom unavailable slot comment Co-authored-by: chris * Update cooldown image * Logger message for direct-compression still being enabled * oops * More explicit RuntimeException message Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> * Constant for 'system' locale * Better config JSON encoding (something is broken with Cloudflare; we'll figure it out * Fix broadcast port default * Add this file too * Update configurate branch * fix build * Fix: Allow using custom config file on Standalone, add relocation comment * Move config loading to GeyserBootstrap interface * Add and rename some config options, add section notes (#5390) * Add and rename some config options, add section notes * adjust message * Update core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java Co-authored-by: Eclipse * Update core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java Co-authored-by: Eclipse * Update ConfigLoader.java * Update AdvancedConfig.java * clarify that we're talking about the HAProxy protocol * rename config option to use-haproxy-protocol * remove ominous warning sign on xbox auth warning * adjust wording --------- Co-authored-by: Eclipse * Back to one config file * Some minor touchups * Configurate: Sectionification (#5904) * Init: config sections * Start on adding tests, move migrations over to ConfigMigrations class * Get rid of auth section again, rename that one config option, fix mtu migration * Move custom skulls config options to the bottom of the gameplay settings * Add more tests * Rename and migrate proxy-protocol-whitelisted-ips to haproxy-protocol-whitelisted-ips * Add automatic downloading of the GeyserOptionalPack * Revert "Add automatic downloading of the GeyserOptionalPack" This reverts commit 65b96208fb3359765f668dc10205b70c0035c454. * Add more invalid config tests * Warn about emote-offhand-workaround removal * Add automatic loading of the GeyserOptionalPack (feature/configurate) (#5964) * Add automatic downloading of the GeyserOptionalPack * Warn about including the OptionalPack from extensions when Geyser is already including it instead of throwing. * Copy optional pack instead of downloading --------- Co-authored-by: onebeastchris * Remove unused variable * Start warning users not running Java 21 * Update tests, temporarily remove NumericRanges test * Remove duplicate advanced section from Geyser dump * Address some "reviews" * yeet md5 hash from geyser dump * Add info about number of resource packs / amount of mappings into Geyser dump * Re-enable invalid config loading test * Fix: allow-custom-skulls migration * Fix test * Add "enable-emotes" configuration option * Rename "emotes-enabled" to "show-emotes" * Only enable integrated pack when optional pack isn't present * Update integrated pack * Exclude jackson annotations, remove leftover debug print * Remove one-time config migration warnings as we don't have access to the logger at that stage * Throw more detailed descriptive error when loading resource packs from the "packs" folder, add another legacy config test * Fix NeoForge's fun module conflict * Re-add warning about moved functionality, fix Geyser-ViaProxy This reverts commit fbadfa574a2de60f426edda7237e92976b36a76f. * oops * Move GeyserLegacyPingPassthrough to separate thread to avoid Standalone command locking issues --------- Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> Co-authored-by: chris Co-authored-by: Eclipse Co-authored-by: Aurora --- bootstrap/bungeecord/build.gradle.kts | 5 +- .../platform/bungeecord/BungeeMetrics.java | 99 ++++ .../bungeecord/GeyserBungeeInjector.java | 4 +- .../GeyserBungeePingPassthrough.java | 2 +- .../bungeecord/GeyserBungeePlugin.java | 76 +-- .../GeyserBungeeUpdateListener.java | 2 +- bootstrap/mod/fabric/build.gradle.kts | 2 +- .../platform/fabric/GeyserFabricDumpInfo.java | 3 +- .../platform/fabric/GeyserFabricPlatform.java | 2 +- bootstrap/mod/neoforge/build.gradle.kts | 17 +- .../neoforge/GeyserNeoForgeDumpInfo.java | 3 +- .../neoforge/GeyserNeoForgePlatform.java | 2 +- .../platform/mod/GeyserModBootstrap.java | 68 ++- .../platform/mod/GeyserModInjector.java | 4 +- .../platform/mod/GeyserModUpdateListener.java | 2 +- .../mixin/client/IntegratedServerMixin.java | 5 +- bootstrap/spigot/build.gradle.kts | 12 +- .../spigot/GeyserSpigotConfiguration.java | 51 -- .../platform/spigot/GeyserSpigotDumpInfo.java | 3 +- .../platform/spigot/GeyserSpigotInjector.java | 18 +- .../platform/spigot/GeyserSpigotPlugin.java | 73 ++- .../spigot/GeyserSpigotUpdateListener.java | 2 +- .../geyser/platform/spigot/SpigotMetrics.java | 91 ++++ bootstrap/standalone/build.gradle.kts | 2 + .../standalone/GeyserStandaloneBootstrap.java | 193 ++----- .../standalone/GeyserStandaloneLogger.java | 8 +- bootstrap/velocity/build.gradle.kts | 5 +- .../velocity/GeyserVelocityDumpInfo.java | 3 +- .../velocity/GeyserVelocityInjector.java | 2 +- .../velocity/GeyserVelocityPlugin.java | 66 +-- .../GeyserVelocityUpdateListener.java | 2 +- ...onfiguration.java => VelocityMetrics.java} | 55 +- bootstrap/viaproxy/build.gradle.kts | 3 +- .../viaproxy/GeyserViaProxyConfiguration.java | 67 --- .../viaproxy/GeyserViaProxyDumpInfo.java | 3 +- .../viaproxy/GeyserViaProxyLogger.java | 8 + .../viaproxy/GeyserViaProxyPlugin.java | 86 ++-- core/build.gradle.kts | 21 +- .../java/org/geysermc/geyser/Constants.java | 4 + .../geysermc/geyser/FloodgateKeyLoader.java | 12 +- .../org/geysermc/geyser/GeyserBootstrap.java | 34 +- .../java/org/geysermc/geyser/GeyserImpl.java | 264 ++++++---- .../org/geysermc/geyser/GeyserLogger.java | 5 +- .../geyser/command/CommandRegistry.java | 6 +- .../defaults/ConnectionTestCommand.java | 53 +- .../geyser/command/defaults/DumpCommand.java | 44 +- .../command/defaults/VersionCommand.java | 8 +- .../standalone/PermissionConfiguration.java | 9 +- .../geyser/configuration/ConfigLoader.java | 217 ++++++++ .../configuration/ConfigMigrations.java | 198 ++++++++ .../ConfigurationCommentMover.java | 85 ++++ .../geyser/configuration/ExcludePlatform.java | 25 +- .../geyser/configuration/GeyserConfig.java | 475 ++++++++++++++++++ .../configuration/GeyserConfiguration.java | 191 ------- .../GeyserCustomSkullConfiguration.java | 20 +- .../GeyserJacksonConfiguration.java | 366 -------------- .../configuration/GeyserPluginConfig.java | 72 +++ .../configuration/GeyserRemoteConfig.java | 63 +++ .../LowercaseEnumSerializer.java | 55 ++ ...karoundOption.java => PluginSpecific.java} | 35 +- .../geyser/dump/BootstrapDumpInfo.java | 5 +- .../org/geysermc/geyser/dump/DumpInfo.java | 147 ++++-- .../geyser/entity/EntityDefinition.java | 2 +- .../geysermc/geyser/entity/type/Entity.java | 2 +- .../GeyserDefineResourcePacksEventImpl.java | 16 +- .../SessionLoadResourcePacksEventImpl.java | 22 +- .../geyser/inventory/click/ClickPlan.java | 2 +- .../geyser/network/UpstreamPacketHandler.java | 14 +- .../geyser/network/netty/GeyserInjector.java | 2 +- .../geyser/network/netty/GeyserServer.java | 82 ++- .../pack/GeyserResourcePackManifest.java | 30 +- .../geyser/pack/SkullResourcePackManager.java | 4 +- .../ping/GeyserLegacyPingPassthrough.java | 160 +++--- .../geysermc/geyser/ping/GeyserPingInfo.java | 28 +- .../registry/PacketTranslatorRegistry.java | 2 +- .../loader/BiomeIdentifierRegistryLoader.java | 14 +- .../registry/loader/EffectRegistryLoader.java | 29 +- .../loader/ParticleTypesRegistryLoader.java | 23 +- .../registry/loader/ResourcePackLoader.java | 26 +- .../loader/SoundEventsRegistryLoader.java | 37 +- .../registry/loader/SoundRegistryLoader.java | 37 +- .../mappings/MappingsConfigReader.java | 20 +- .../mappings/versions/MappingsReader.java | 35 +- .../mappings/versions/MappingsReader_v1.java | 285 +++++------ .../populator/BlockRegistryPopulator.java | 20 +- .../CreativeItemRegistryPopulator.java | 51 +- .../CustomBlockRegistryPopulator.java | 2 +- .../CustomSkullRegistryPopulator.java | 2 +- .../populator/ItemRegistryPopulator.java | 14 +- .../registry/type/GeyserMappingItem.java | 16 +- .../geyser/scoreboard/Scoreboard.java | 4 +- .../geyser/scoreboard/ScoreboardUpdater.java | 8 +- .../geyser/session/GeyserSession.java | 46 +- .../geyser/session/GeyserSessionAdapter.java | 10 +- .../session/SessionDisconnectListener.java | 2 +- .../session/auth/BedrockClientData.java | 89 ++-- .../geyser/session/cache/BundleCache.java | 2 +- .../session/cache/PreferencesCache.java | 15 +- .../geyser/session/cache/SkullCache.java | 6 +- .../geyser/skin/FloodgateSkinUploader.java | 65 +-- .../org/geysermc/geyser/skin/SkinManager.java | 38 +- .../geysermc/geyser/skin/SkinProvider.java | 47 +- .../geyser/text/AsteriskSerializer.java | 99 ++-- .../geysermc/geyser/text/GeyserLocale.java | 5 +- .../geysermc/geyser/text/MinecraftLocale.java | 13 +- .../inventory/InventoryTranslator.java | 6 +- .../entity/SkullBlockEntityTranslator.java | 4 +- .../BedrockCommandRequestTranslator.java | 2 - .../bedrock/BedrockEmoteListTranslator.java | 5 - ...BedrockInventoryTransactionTranslator.java | 2 +- .../BedrockNetworkStackLatencyTranslator.java | 2 +- ...SetLocalPlayerAsInitializedTranslator.java | 2 +- .../entity/player/BedrockEmoteTranslator.java | 15 +- .../protocol/java/JavaCommandsTranslator.java | 2 +- .../java/JavaKeepAliveTranslator.java | 2 +- .../entity/JavaSetEntityDataTranslator.java | 2 +- .../player/JavaPlayerPositionTranslator.java | 2 +- .../java/level/JavaBlockEventTranslator.java | 2 +- .../translator/text/MessageTranslator.java | 2 +- .../org/geysermc/geyser/util/AssetUtils.java | 102 ++-- .../geysermc/geyser/util/CooldownUtils.java | 16 +- .../org/geysermc/geyser/util/FileUtils.java | 43 +- .../geyser/util/GeyserIntegratedPackUtil.java | 157 ++++++ .../geysermc/geyser/util/InventoryUtils.java | 4 +- .../org/geysermc/geyser/util/JsonUtils.java | 76 +++ .../geyser/util/LoginEncryptionUtils.java | 14 +- .../org/geysermc/geyser/util/Metrics.java | 447 ---------------- .../geysermc/geyser/util/SettingsUtils.java | 4 +- .../geyser/util/VersionCheckUtils.java | 12 +- .../org/geysermc/geyser/util/WebUtils.java | 34 +- .../geyser/util/metrics/MetricsPlatform.java | 37 +- .../util/metrics/ProvidedMetricsPlatform.java | 40 +- .../resources/GeyserIntegratedPack.mcpack | Bin 0 -> 82681 bytes .../configuration/ConfigLoaderTest.java | 179 +++++++ .../network/util/GeyserMockContext.java | 28 +- .../configuration/all-changed/before.yml | 219 ++++++++ .../configuration/all-changed/plugin.yml | 239 +++++++++ .../configuration/all-changed/remote.yml | 238 +++++++++ .../allow-custom-skulls/before.yml | 219 ++++++++ .../allow-custom-skulls/plugin.yml | 237 +++++++++ .../allow-custom-skulls/remote.yml | 236 +++++++++ .../resources/configuration/chew/before.yml | 146 ++++++ .../resources/configuration/chew/plugin.yml | 237 +++++++++ .../resources/configuration/chew/remote.yml | 236 +++++++++ .../configuration/default/before.yml} | 2 +- .../configuration/default/plugin.yml | 237 +++++++++ .../configuration/default/remote.yml | 236 +++++++++ .../configuration/invalid/invalid_enum.yml | 219 ++++++++ .../configuration/invalid/invalid_remote.yml | 219 ++++++++ .../invalid/port_out_of_range.yml | 219 ++++++++ .../resources/configuration/legacy/before.yml | 219 ++++++++ .../resources/configuration/legacy/plugin.yml | 237 +++++++++ .../resources/configuration/legacy/remote.yml | 236 +++++++++ .../migrate-no-emotes/before.yml | 219 ++++++++ .../migrate-no-emotes/plugin.yml | 237 +++++++++ .../migrate-no-emotes/remote.yml | 236 +++++++++ gradle/libs.versions.toml | 21 +- 157 files changed, 8006 insertions(+), 2670 deletions(-) create mode 100644 bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeMetrics.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotMetrics.java rename bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/{GeyserVelocityConfiguration.java => VelocityMetrics.java} (50%) delete mode 100644 bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/ConfigurationCommentMover.java rename bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java => core/src/main/java/org/geysermc/geyser/configuration/ExcludePlatform.java (67%) create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java delete mode 100644 core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java delete mode 100644 core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java create mode 100644 core/src/main/java/org/geysermc/geyser/configuration/LowercaseEnumSerializer.java rename core/src/main/java/org/geysermc/geyser/configuration/{EmoteOffhandWorkaroundOption.java => PluginSpecific.java} (58%) create mode 100644 core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java create mode 100644 core/src/main/java/org/geysermc/geyser/util/JsonUtils.java delete mode 100644 core/src/main/java/org/geysermc/geyser/util/Metrics.java rename bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java => core/src/main/java/org/geysermc/geyser/util/metrics/MetricsPlatform.java (52%) rename bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java => core/src/main/java/org/geysermc/geyser/util/metrics/ProvidedMetricsPlatform.java (59%) create mode 100644 core/src/main/resources/GeyserIntegratedPack.mcpack create mode 100644 core/src/test/java/org/geysermc/geyser/configuration/ConfigLoaderTest.java create mode 100644 core/src/test/resources/configuration/all-changed/before.yml create mode 100644 core/src/test/resources/configuration/all-changed/plugin.yml create mode 100644 core/src/test/resources/configuration/all-changed/remote.yml create mode 100644 core/src/test/resources/configuration/allow-custom-skulls/before.yml create mode 100644 core/src/test/resources/configuration/allow-custom-skulls/plugin.yml create mode 100644 core/src/test/resources/configuration/allow-custom-skulls/remote.yml create mode 100644 core/src/test/resources/configuration/chew/before.yml create mode 100644 core/src/test/resources/configuration/chew/plugin.yml create mode 100644 core/src/test/resources/configuration/chew/remote.yml rename core/src/{main/resources/config.yml => test/resources/configuration/default/before.yml} (99%) create mode 100644 core/src/test/resources/configuration/default/plugin.yml create mode 100644 core/src/test/resources/configuration/default/remote.yml create mode 100644 core/src/test/resources/configuration/invalid/invalid_enum.yml create mode 100644 core/src/test/resources/configuration/invalid/invalid_remote.yml create mode 100644 core/src/test/resources/configuration/invalid/port_out_of_range.yml create mode 100644 core/src/test/resources/configuration/legacy/before.yml create mode 100644 core/src/test/resources/configuration/legacy/plugin.yml create mode 100644 core/src/test/resources/configuration/legacy/remote.yml create mode 100644 core/src/test/resources/configuration/migrate-no-emotes/before.yml create mode 100644 core/src/test/resources/configuration/migrate-no-emotes/plugin.yml create mode 100644 core/src/test/resources/configuration/migrate-no-emotes/remote.yml diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 0216a43dd..5f593975e 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -15,11 +15,12 @@ dependencies { } platformRelocate("net.md_5.bungee.jni") -platformRelocate("com.fasterxml.jackson") platformRelocate("net.kyori") platformRelocate("org.incendo") -platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated +platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated platformRelocate("org.yaml") // Broken as of 1.20 +platformRelocate("org.spongepowered") +platformRelocate("org.bstats") // These dependencies are already present on the platform provided(libs.bungeecord.proxy) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeMetrics.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeMetrics.java new file mode 100644 index 000000000..8fa3719f8 --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeMetrics.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.bungeecord; + +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; +import org.geysermc.geyser.util.metrics.MetricsPlatform; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.UUID; + +public final class BungeeMetrics implements MetricsPlatform { + private final Configuration configuration; + + public BungeeMetrics(Plugin plugin) throws IOException { + // https://github.com/Bastian/bstats-metrics/blob/master/bungeecord/src/main/java/org/bstats/bungeecord/Metrics.java + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + //noinspection ResultOfMethodCallIgnored + bStatsFolder.mkdirs(); + File configFile = new File(bStatsFolder, "config.yml"); + if (!configFile.exists()) { + writeFile(configFile, + "# bStats (https://bStats.org) collects some basic information for plugin authors, like how", + "# many people use their plugin and their total player count. It's recommended to keep bStats", + "# enabled, but if you're not comfortable with this, you can turn this setting off. There is no", + "# performance penalty associated with having metrics enabled, and data sent to bStats is fully", + "# anonymous.", + "enabled: true", + "serverUuid: \"" + UUID.randomUUID() + "\"", + "logFailedRequests: false", + "logSentData: false", + "logResponseStatusText: false"); + } + + this.configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); + } + + @Override + public boolean enabled() { + return configuration.getBoolean("enabled", true); + } + + @Override + public String serverUuid() { + return configuration.getString("serverUuid"); + } + + @Override + public boolean logFailedRequests() { + return configuration.getBoolean("logFailedRequests", false); + } + + @Override + public boolean logSentData() { + return configuration.getBoolean("logSentData", false); + } + + @Override + public boolean logResponseStatusText() { + return configuration.getBoolean("logResponseStatusText", false); + } + + private void writeFile(File file, String... lines) throws IOException { + try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) { + for (String line : lines) { + bufferedWriter.write(line); + bufferedWriter.newLine(); + } + } + } +} 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 3c28c1d18..424379636 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 @@ -157,7 +157,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener { listenerInfo.isPingPassthrough(), listenerInfo.getQueryPort(), listenerInfo.isQueryEnabled(), - bootstrap.getGeyserConfig().getRemote().isUseProxyProtocol() // If Geyser is expecting HAProxy, so should the Bungee end + bootstrap.config().advanced().java().useHaproxyProtocol() // If Geyser is expecting HAProxy, so should the Bungee end ); // The field that stores all listeners in BungeeCord @@ -191,7 +191,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener { } initChannel.invoke(channelInitializer, ch); - if (bootstrap.getGeyserConfig().isDisableCompression()) { + if (bootstrap.config().advanced().java().disableCompression()) { ch.pipeline().addAfter(PipelineUtils.PACKET_ENCODER, "geyser-compression-disabler", new GeyserBungeeCompressionDisabler()); } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java index e1f22c838..fb56d92c2 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -69,7 +69,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List try { event = future.get(100, TimeUnit.MILLISECONDS); } catch (Throwable cause) { - String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : ""; + String address = GeyserImpl.getInstance().config().logPlayerIpAddresses() ? inetSocketAddress.toString() : ""; GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause); return null; } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 421e5c893..12e201138 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -33,25 +33,25 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.protocol.ProtocolConstants; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.FloodgateKeyLoader; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserPluginConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.metrics.MetricsPlatform; import org.incendo.cloud.CommandManager; import org.incendo.cloud.bungee.BungeeCommandManager; import org.incendo.cloud.execution.ExecutionCoordinator; -import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; @@ -61,13 +61,12 @@ import java.nio.file.Paths; import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.TimeUnit; public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private CommandRegistry commandRegistry; - private GeyserBungeeConfiguration geyserConfig; + private GeyserPluginConfig geyserConfig; private GeyserBungeeInjector geyserInjector; private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger()); private IGeyserPingPassthrough geyserBungeePingPassthrough; @@ -102,12 +101,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { System.setProperty("Mcpl.io_uring", "true"); } - if (!this.loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); + this.geyser = GeyserImpl.load(this); this.geyserInjector = new GeyserBungeeInjector(this); // Registration of listeners occurs only once @@ -168,16 +166,15 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { public void onGeyserEnable() { if (GeyserImpl.getInstance().isReloading()) { - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); } // Force-disable query if enabled, or else Geyser won't enable for (ListenerInfo info : getProxy().getConfig().getListeners()) { - if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) { + if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.bedrock().port()) { try { Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled"); queryField.setAccessible(true); @@ -195,7 +192,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { GeyserImpl.start(); - if (geyserConfig.isLegacyPingPassthrough()) { + if (!geyserConfig.motd().integratedPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); @@ -232,8 +229,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } @Override - public GeyserBungeeConfiguration getGeyserConfig() { - return geyserConfig; + public @NonNull PlatformType platformType() { + return PlatformType.BUNGEECORD; + } + + @Override + public GeyserPluginConfig config() { + return this.geyserConfig; } @Override @@ -290,11 +292,29 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { @Override public boolean testFloodgatePluginPresent() { - if (getProxy().getPluginManager().getPlugin("floodgate") != null) { - geyserConfig.loadFloodgate(this); - return true; + return getProxy().getPluginManager().getPlugin("floodgate") != null; + } + + @Override + public Path getFloodgateKeyPath() { + Plugin floodgate = getProxy().getPluginManager().getPlugin("floodgate"); + Path geyserDataFolder = getDataFolder().toPath(); + Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; + + return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger); + } + + @Override + public MetricsPlatform createMetricsPlatform() { + try { + return new BungeeMetrics(this); + } catch (IOException e) { + this.geyserLogger.debug("Integrated bStats support failed to load."); + if (this.config().debugMode()) { + e.printStackTrace(); + } + return null; } - return false; } private Optional findCompatibleListener() { @@ -303,20 +323,4 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { .map(info -> (InetSocketAddress) info.getSocketAddress()) .findFirst(); } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean loadConfig() { - try { - if (!getDataFolder().exists()) //noinspection ResultOfMethodCallIgnored - getDataFolder().mkdir(); - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), - "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); - } catch (IOException ex) { - geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - return false; - } - return true; - } } 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 0a89b5421..9c8ca2a99 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(Permissions.CHECK_UPDATE)) { VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player)); diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts index 5b66e0f37..ddf492e93 100644 --- a/bootstrap/mod/fabric/build.gradle.kts +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { shadowBundle(projects.core) includeTransitive(projects.core) - // These are NOT transitively included, and instead shadowed + relocated. + // These are NOT transitively included, and instead shadowed (+ relocated, if not under the org.geyser namespace). // Avoids fabric complaining about non-SemVer versioning shadowBundle(libs.protocol.connection) shadowBundle(libs.protocol.common) diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java index 75da9125f..c4efdf9ff 100644 --- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.platform.fabric; +import com.google.gson.annotations.JsonAdapter; import lombok.AllArgsConstructor; import lombok.Getter; import net.fabricmc.api.EnvType; @@ -48,7 +49,7 @@ public class GeyserFabricDumpInfo extends BootstrapDumpInfo { private final String minecraftVersion; private final EnvType environmentType; - @AsteriskSerializer.Asterisk(isIp = true) + @JsonAdapter(value = AsteriskSerializer.class) private final String serverIP; private final int serverPort; private final boolean onlineMode; diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java index 4631ab493..345da4459 100644 --- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java @@ -73,7 +73,7 @@ public class GeyserFabricPlatform implements GeyserModPlatform { Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); if (floodgate.isPresent()) { Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate"); - bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder); + bootstrap.loadFloodgate(floodgateDataFolder); return true; } diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts index 310e8c3f6..0c7fa3765 100644 --- a/bootstrap/mod/neoforge/build.gradle.kts +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -13,9 +13,6 @@ architectury { provided("org.cloudburstmc.math", "api") provided("com.google.errorprone", "error_prone_annotations") -// Jackson shipped by Minecraft is too old, so we shade & relocate our newer version -relocate("com.fasterxml.jackson") - dependencies { // See https://github.com/google/guava/issues/6618 modules { @@ -30,15 +27,12 @@ dependencies { shadowBundle(project(path = ":mod", configuration = "transformProductionNeoForge")) shadowBundle(projects.core) - // Minecraft (1.21.2+) includes jackson. But an old version! - shadowBundle(libs.jackson.core) - shadowBundle(libs.jackson.databind) - shadowBundle(libs.jackson.dataformat.yaml) - shadowBundle(libs.jackson.annotations) - // Let's shade in our own api shadowBundle(projects.api) + // this one is particularly dumb + shadowBundle(libs.configurate.`interface`) + // cannot be shaded, since neoforge will complain if floodgate-neoforge tries to provide this include(projects.common) @@ -61,11 +55,6 @@ tasks { remapModrinthJar { archiveBaseName.set("geyser-neoforge") } - - shadowJar { - // Without this, jackson's service files are not relocated - mergeServiceFiles() - } } modrinth { diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java index 8348ca160..280de6a64 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.platform.neoforge; +import com.google.gson.annotations.JsonAdapter; import lombok.AllArgsConstructor; import lombok.Getter; import net.minecraft.server.MinecraftServer; @@ -47,7 +48,7 @@ public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo { private final String minecraftVersion; private final Dist dist; - @AsteriskSerializer.Asterisk(isIp = true) + @JsonAdapter(value = AsteriskSerializer.class) private final String serverIP; private final int serverPort; private final boolean onlineMode; diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java index c1e0b67e0..603f00f46 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java @@ -72,7 +72,7 @@ public class GeyserNeoForgePlatform implements GeyserModPlatform { public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) { if (ModList.get().isLoaded("floodgate")) { Path floodgateDataFolder = FMLPaths.CONFIGDIR.get().resolve("floodgate"); - bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder); + bootstrap.loadFloodgate(floodgateDataFolder); return true; } return false; diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java index 2e3c4fad5..c99c180ab 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java @@ -31,11 +31,13 @@ import lombok.Setter; import net.minecraft.server.MinecraftServer; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.FloodgateKeyLoader; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserPluginConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -43,14 +45,10 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.FileUtils; -import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.net.SocketAddress; import java.nio.file.Path; -import java.util.UUID; @RequiredArgsConstructor public abstract class GeyserModBootstrap implements GeyserBootstrap { @@ -69,7 +67,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { @Setter private CommandRegistry commandRegistry; - private GeyserModConfiguration geyserConfig; + private GeyserPluginConfig geyserConfig; private GeyserModInjector geyserInjector; private final GeyserModLogger geyserLogger = new GeyserModLogger(); private IGeyserPingPassthrough geyserPingPassthrough; @@ -80,12 +78,11 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { instance = this; dataFolder = this.platform.dataFolder(this.platform.configPath()); GeyserLocale.init(this); - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.load(this.platform.platformType(), this); + this.geyser = GeyserImpl.load(this); } public void onGeyserEnable() { @@ -95,16 +92,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { } if (GeyserImpl.getInstance().isReloading()) { - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); } GeyserImpl.start(); - if (geyserConfig.isLegacyPingPassthrough()) { + if (!geyserConfig.motd().integratedPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger); @@ -145,7 +141,12 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { } @Override - public GeyserModConfiguration getGeyserConfig() { + public @NonNull PlatformType platformType() { + return this.platform.platformType(); + } + + @Override + public GeyserPluginConfig config() { return geyserConfig; } @@ -199,12 +200,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { @Override public int getServerPort() { - if (isServer()) { - return ((GeyserServerPortGetter) server).geyser$getServerPort(); - } else { - // Set in the IntegratedServerMixin - return geyserConfig.getRemote().port(); - } + return ((GeyserServerPortGetter) server).geyser$getServerPort(); } public abstract boolean isServer(); @@ -214,31 +210,23 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { return this.platform.testFloodgatePluginPresent(this); } + private Path floodgateKeyPath; + + public void loadFloodgate(Path floodgateDataFolder) { + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, dataFolder, geyserLogger); + } + + @Override + public Path getFloodgateKeyPath() { + return floodgateKeyPath; + } + @Nullable @Override public InputStream getResourceOrNull(String resource) { return this.platform.resolveResource(resource); } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean loadConfig() { - try { - if (!dataFolder.toFile().exists()) { - //noinspection ResultOfMethodCallIgnored - dataFolder.toFile().mkdir(); - } - - File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserModConfiguration.class); - return true; - } catch (IOException ex) { - geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - return false; - } - } - @Override public @NonNull String getServerPlatform() { return server.getServerModName(); 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..c8ac21116 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().advanced().java().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/GeyserModUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java index 6948bd1e1..360619284 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java @@ -34,7 +34,7 @@ import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserModUpdateListener { public static void onPlayReady(ServerPlayer player) { // We could just not register the listener, but, this allows config reloading - if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { + if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) { // Should be creating this in the supplier, but we need it for the permission check. // Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it. ModCommandSource source = new ModCommandSource(player.createCommandSourceStack()); 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..8f218105f 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,14 +56,13 @@ public class IntegratedServerMixin implements GeyserServerPortGetter { // If the LAN is opened, starts Geyser. GeyserModBootstrap instance = GeyserModBootstrap.getInstance(); instance.setServer((MinecraftServer) (Object) this); - instance.getGeyserConfig().getRemote().setPort(port); instance.onGeyserEnable(); // Ensure player locale has been loaded, in case it's different from Java system language GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); // Give indication that Geyser is loaded Objects.requireNonNull(this.minecraft.player); - this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start", - this.minecraft.options.languageCode, "localhost", String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false); + this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start.ip_suppressed", + this.minecraft.options.languageCode, String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false); } } diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index f9252084b..7d27971e8 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -31,17 +31,25 @@ dependencies { compileOnly(libs.folia.api) compileOnlyApi(libs.viaversion) + + // For 1.16.5/1.17.1 + implementation(libs.gson.record.factory) { + isTransitive = false + } } platformRelocate("it.unimi.dsi.fastutil") -platformRelocate("com.fasterxml.jackson") // Relocate net.kyori but exclude the component logger platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("org.objectweb.asm") platformRelocate("me.lucko.commodore") platformRelocate("org.incendo") -platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated +platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated platformRelocate("org.yaml") // Broken as of 1.20 +platformRelocate("org.spongepowered") +platformRelocate("marcono1234.gson") +platformRelocate("org.bstats") + provided(libs.viaversion) tasks.withType { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java deleted file mode 100644 index 3320ffa65..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.platform.spigot; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.geysermc.geyser.FloodgateKeyLoader; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; - -import java.nio.file.Path; - -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { - @JsonIgnore - private Path floodgateKeyPath; - - public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); - Path geyserDataFolder = plugin.getDataFolder().toPath(); - Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java index 329663709..6e063f415 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.platform.spigot; +import com.google.gson.annotations.JsonAdapter; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; @@ -42,7 +43,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo { private final String platformAPIVersion; private final boolean onlineMode; - @AsteriskSerializer.Asterisk(isIp = true) + @JsonAdapter(value = AsteriskSerializer.class) private final String serverIP; private final int serverPort; private final List plugins; 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 3098ef0e0..152aa6afa 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 @@ -25,11 +25,13 @@ package org.geysermc.geyser.platform.spigot; -import org.geysermc.mcprotocollib.protocol.MinecraftConstants; -import org.geysermc.mcprotocollib.protocol.MinecraftProtocol; import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.local.LocalAddress; import io.netty.util.concurrent.DefaultThreadFactory; import org.bukkit.Bukkit; @@ -38,6 +40,8 @@ import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.network.netty.GeyserInjector; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; import org.geysermc.geyser.network.netty.LocalSession; +import org.geysermc.mcprotocollib.protocol.MinecraftConstants; +import org.geysermc.mcprotocollib.protocol.MinecraftProtocol; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -123,7 +127,7 @@ public class GeyserSpigotInjector extends GeyserInjector { int index = ch.pipeline().names().indexOf("encoder"); String baseName = index != -1 ? "encoder" : "outbound_config"; - if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) { + if (bootstrap.config().advanced().java().disableCompression() && GeyserSpigotCompressionDisabler.ENABLED) { ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler()); } } @@ -158,7 +162,7 @@ public class GeyserSpigotInjector extends GeyserInjector { } break; } catch (Exception e) { - if (bootstrap.getGeyserConfig().isDebugMode()) { + if (bootstrap.config().debugMode()) { bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!"); e.printStackTrace(); } @@ -178,8 +182,8 @@ public class GeyserSpigotInjector extends GeyserInjector { private void workAroundWeirdBug(GeyserBootstrap bootstrap) { MinecraftProtocol protocol = new MinecraftProtocol(); LocalSession session = new LocalSession(this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, Runnable::run); - session.setFlag(MinecraftConstants.CLIENT_HOST, bootstrap.getGeyserConfig().getRemote().address()); - session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.getGeyserConfig().getRemote().port()); + session.setFlag(MinecraftConstants.CLIENT_HOST, bootstrap.config().java().address()); + session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.config().java().port()); session.connect(); } 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 e3f52ee8e..ce1894538 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 @@ -38,9 +38,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.FloodgateKeyLoader; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.paper.PaperAdapters; @@ -50,7 +52,7 @@ import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserPluginConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; @@ -64,23 +66,20 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.metrics.MetricsPlatform; import org.incendo.cloud.bukkit.BukkitCommandManager; import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.paper.LegacyPaperCommandManager; -import java.io.File; -import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; import java.util.Objects; -import java.util.UUID; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private CommandRegistry commandRegistry; - private GeyserSpigotConfiguration geyserConfig; + private GeyserPluginConfig geyserConfig; private GeyserSpigotInjector geyserInjector; private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger()); @@ -161,16 +160,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // no-op } - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { + // We'll disable ourselves later return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); // Turn "(MC: 1.16.4)" into 1.16.4. this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; - this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this); + this.geyser = GeyserImpl.load(this); } @Override @@ -226,16 +225,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Configs are loaded once early - so we can create the logger, then load extensions and finally register // extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists if (GeyserImpl.getInstance().isReloading()) { - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { + Bukkit.getPluginManager().disablePlugin(this); return; } - this.geyserLogger.setDebug(this.geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); } GeyserImpl.start(); - if (geyserConfig.isLegacyPingPassthrough()) { + if (!geyserConfig.motd().integratedPingPassthrough()) { this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { if (ReflectedNames.checkPaperPingEvent()) { @@ -289,7 +288,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName()); } catch (Throwable e) { - if (geyserConfig.isDebugMode()) { + if (geyserConfig.debugMode()) { geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)"); e.printStackTrace(); } @@ -363,8 +362,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } @Override - public GeyserSpigotConfiguration getGeyserConfig() { - return geyserConfig; + public @NonNull PlatformType platformType() { + return PlatformType.SPIGOT; + } + + @Override + public GeyserPluginConfig config() { + return this.geyserConfig; } @Override @@ -455,32 +459,21 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public boolean testFloodgatePluginPresent() { - if (Bukkit.getPluginManager().getPlugin("floodgate") != null) { - geyserConfig.loadFloodgate(this); - return true; - } - return false; + return Bukkit.getPluginManager().getPlugin("floodgate") != null; } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean loadConfig() { - // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed - try { - if (!getDataFolder().exists()) { - //noinspection ResultOfMethodCallIgnored - getDataFolder().mkdir(); - } - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); - } catch (IOException ex) { - geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - Bukkit.getPluginManager().disablePlugin(this); - return false; - } + @Override + public Path getFloodgateKeyPath() { + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); + Path geyserDataFolder = getDataFolder().toPath(); + Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - return true; + return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger); + } + + @Override + public MetricsPlatform createMetricsPlatform() { + return new SpigotMetrics(this); } private void warnInvalidProxySetups(String platform) { 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 8a8a43460..a4eee8a20 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(Permissions.CHECK_UPDATE)) { VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player)); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotMetrics.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotMetrics.java new file mode 100644 index 000000000..796ae4b88 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotMetrics.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.geysermc.geyser.util.metrics.MetricsPlatform; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +public final class SpigotMetrics implements MetricsPlatform { + private final YamlConfiguration config; + + public SpigotMetrics(Plugin plugin) { + // https://github.com/Bastian/bstats-metrics/blob/master/bukkit/src/main/java/org/bstats/bukkit/Metrics.java + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + config = YamlConfiguration.loadConfiguration(configFile); + + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + + // Inform the server owners about bStats + config.options().header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous." + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { } + } + } + + @Override + public boolean enabled() { + return config.getBoolean("enabled", true); + } + + @Override + public String serverUuid() { + return config.getString("serverUuid"); + } + + @Override + public boolean logFailedRequests() { + return config.getBoolean("logFailedRequests", false); + } + + @Override + public boolean logSentData() { + return config.getBoolean("logSentData", false); + } + + @Override + public boolean logResponseStatusText() { + return config.getBoolean("logResponseStatusText", false); + } +} diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index 6ec437654..f7583436e 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -15,6 +15,8 @@ dependencies { implementation(libs.bundles.jline) implementation(libs.bundles.log4j) + + implementation(libs.gson.runtime) } application { 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 42a296c16..faf053df6 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 org.apache.logging.log4j.Level; @@ -41,35 +36,32 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; -import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.configuration.ConfigLoader; +import org.geysermc.geyser.configuration.GeyserConfig; +import org.geysermc.geyser.configuration.GeyserRemoteConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.LoopbackUtil; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.serialize.SerializationException; import java.io.File; -import java.io.IOException; import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.HashMap; -import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { private StandaloneCloudCommandManager cloud; private CommandRegistry commandRegistry; - private GeyserStandaloneConfiguration geyserConfig; + private GeyserConfig geyserConfig; private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger(); private IGeyserPingPassthrough geyserPingPassthrough; private GeyserStandaloneGUI gui; @@ -80,9 +72,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserImpl geyser; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private static final Map argsConfigKeys = new HashMap<>(); + private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { if (System.getProperty("io.netty.leakDetection.level") == null) { @@ -99,8 +89,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { GeyserLocale.init(bootstrap); - List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); - for (int i = 0; i < args.length; i++) { // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null // Optionally, you can force the use of a GUI or no GUI by specifying args @@ -132,34 +120,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)); @@ -189,19 +151,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { @Override public void onGeyserEnable() { - try { - File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); - - handleArgsConfigOptions(); - - if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { - geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug - geyserConfig.getRemote().setAddress("127.0.0.1"); - } - } catch (IOException ex) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + this.geyserConfig = loadConfig(GeyserRemoteConfig.class); + if (this.geyserConfig == null) { if (gui == null) { System.exit(1); } else { @@ -209,13 +160,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { return; } } - geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); // Allow libraries like Protocol to have their debug information passthrough - log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); + log4jLogger.get().setLevel(geyserConfig.debugMode() ? Level.DEBUG : Level.INFO); - geyser = GeyserImpl.load(PlatformType.STANDALONE, this); + geyser = GeyserImpl.load(this); boolean reloading = geyser.isReloading(); if (!reloading) { @@ -245,6 +194,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyserLogger.start(); } + @Override + public T loadConfig(Class configClass) { + return new ConfigLoader(this) + .configFile(new File(getConfigFolder().toFile(), configFilename)) + .transformer(this::handleArgsConfigOptions) + .load(configClass); + } + /** * Check using {@link java.awt.GraphicsEnvironment} that we are a headless client * @@ -273,8 +230,13 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } @Override - public GeyserConfiguration getGeyserConfig() { - return geyserConfig; + public @NonNull PlatformType platformType() { + return PlatformType.STANDALONE; + } + + @Override + public GeyserConfig config() { + return this.geyserConfig; } @Override @@ -330,100 +292,47 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { return false; } - /** - * Get the {@link BeanPropertyDefinition}s for the given class - * - * @param clazz The class to get the definitions for - * @return A list of {@link BeanPropertyDefinition} for the given class - */ - public static List 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.advanced().floodgateKeyFile()); } /** * Set a POJO property value on an object * - * @param property The {@link BeanPropertyDefinition} to set - * @param parentObject The object to alter * @param value The new value of the property */ - @SuppressWarnings({"unchecked", "rawtypes"}) // Required for enum usage - private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) { + private static void setConfigOption(CommentedConfigurationNode node, Object value) throws SerializationException { Object parsedValue = value; // Change the values type if needed - if (int.class.equals(property.getRawPrimaryType())) { + Class clazz = node.raw().getClass(); + if (Integer.class == clazz) { parsedValue = Integer.valueOf((String) parsedValue); - } else if (boolean.class.equals(property.getRawPrimaryType())) { + } else if (Boolean.class == clazz) { parsedValue = Boolean.valueOf((String) parsedValue); - } else if (Enum.class.isAssignableFrom(property.getRawPrimaryType())) { - parsedValue = Enum.valueOf((Class) 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/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index 8739add8a..e73e799ba 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -32,6 +32,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.io.IoBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.command.GeyserCommandSource; @@ -112,7 +113,12 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey @Override public void debug(String message) { - log.debug(ChatColor.GRAY + message); + log.debug(ChatColor.GRAY + "{}", message); + } + + @Override + public void debug(@Nullable Object object) { + log.debug("{}", object); } @Override diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index 70bffe5bc..dec2e0507 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -16,12 +16,13 @@ dependencies { api(libs.cloud.velocity) } -platformRelocate("com.fasterxml.jackson") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") platformRelocate("org.yaml") +platformRelocate("org.spongepowered") +platformRelocate("org.bstats") platformRelocate("org.incendo") -platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated +platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated // These dependencies are already present on the platform provided(libs.velocity.api) diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java index 45eb7abb9..6bc309f29 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.platform.velocity; +import com.google.gson.annotations.JsonAdapter; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; @@ -42,7 +43,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { private final String platformVendor; private final boolean onlineMode; - @AsteriskSerializer.Asterisk(isIp = true) + @JsonAdapter(value = AsteriskSerializer.class) private final String serverIP; private final int serverPort; private final List plugins; 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 a33a89f4b..02a440735 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 @@ -128,7 +128,7 @@ public class GeyserVelocityInjector extends GeyserInjector { protected void initChannel(@NonNull Channel ch) throws Exception { initChannel.invoke(channelInitializer, ch); - if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserVelocityCompressionDisabler.ENABLED) { + if (bootstrap.config().advanced().java().disableCompression() && GeyserVelocityCompressionDisabler.ENABLED) { ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler", new GeyserVelocityCompressionDisabler()); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 76483acdc..7ee573ee0 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -39,31 +39,31 @@ import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.FloodgateKeyLoader; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserPluginConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.metrics.MetricsPlatform; import org.incendo.cloud.CommandManager; import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.velocity.VelocityCommandManager; import org.slf4j.Logger; -import java.io.File; import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.UUID; +import java.util.Optional; @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") public class GeyserVelocityPlugin implements GeyserBootstrap { @@ -71,7 +71,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { private final ProxyServer proxyServer; private final PluginContainer container; private final GeyserVelocityLogger geyserLogger; - private GeyserVelocityConfiguration geyserConfig; + private GeyserPluginConfig geyserConfig; private GeyserVelocityInjector geyserInjector; private IGeyserPingPassthrough geyserPingPassthrough; private CommandRegistry commandRegistry; @@ -106,13 +106,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { System.setProperty("Mcpl.io_uring", "true"); } - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this); + this.geyser = GeyserImpl.load(this); this.geyserInjector = new GeyserVelocityInjector(proxyServer); // We need to register commands here, rather than in onGeyserEnable which is invoked during the appropriate ListenerBoundEvent. @@ -139,16 +138,15 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { return; } if (GeyserImpl.getInstance().isReloading()) { - if (!loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyserLogger.setDebug(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); } GeyserImpl.start(); - if (geyserConfig.isLegacyPingPassthrough()) { + if (!geyserConfig.motd().integratedPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); @@ -178,7 +176,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } @Override - public GeyserVelocityConfiguration getGeyserConfig() { + public @NonNull PlatformType platformType() { + return PlatformType.VELOCITY; + } + + @Override + public GeyserPluginConfig config() { return geyserConfig; } @@ -250,27 +253,26 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Override public boolean testFloodgatePluginPresent() { var floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); - if (floodgate.isPresent()) { - geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); - return true; - } - return false; + return floodgate.isPresent(); } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean loadConfig() { + @Override + public Path getFloodgateKeyPath() { + Optional floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); + Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null; + return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataPath, configFolder, geyserLogger); + } + + @Override + public MetricsPlatform createMetricsPlatform() { try { - if (!configFolder.toFile().exists()) - //noinspection ResultOfMethodCallIgnored - configFolder.toFile().mkdirs(); - File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), - "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); - } catch (IOException ex) { - geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - return false; + return new VelocityMetrics(this.configFolder); + } catch (IOException e) { + this.geyserLogger.debug("Integrated bStats support failed to load."); + if (this.config().debugMode()) { + e.printStackTrace(); + } + return null; } - return true; } } 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 c1c88b70d..de3d7fa8a 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(Permissions.CHECK_UPDATE)) { VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player)); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/VelocityMetrics.java similarity index 50% rename from bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityConfiguration.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/VelocityMetrics.java index 4c8ea53cc..5939c6678 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/VelocityMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,28 +25,45 @@ package org.geysermc.geyser.platform.velocity; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.proxy.ProxyServer; -import lombok.Getter; -import org.geysermc.geyser.FloodgateKeyLoader; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.bstats.config.MetricsConfig; +import org.geysermc.geyser.util.metrics.MetricsPlatform; import java.io.File; +import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration { - @JsonIgnore - private Path floodgateKeyPath; +public final class VelocityMetrics implements MetricsPlatform { + private final MetricsConfig config; - public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) { - Optional floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); - Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null; - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataPath, dataFolder.toPath(), plugin.getGeyserLogger()); + public VelocityMetrics(Path dataDirectory) throws IOException { + // https://github.com/Bastian/bstats-metrics/blob/master/velocity/src/main/java/org/bstats/velocity/Metrics.java + File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile(); + this.config = new MetricsConfig(configFile, true); + // No logger message is implemented as Velocity should print its own before we do. + } + + @Override + public boolean enabled() { + return config.isEnabled(); + } + + @Override + public String serverUuid() { + return config.getServerUUID(); + } + + @Override + public boolean logFailedRequests() { + return config.isLogErrorsEnabled(); + } + + @Override + public boolean logSentData() { + return config.isLogSentDataEnabled(); + } + + @Override + public boolean logResponseStatusText() { + return config.isLogResponseStatusTextEnabled(); } } diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts index 2b653aef8..d8623ebd3 100644 --- a/bootstrap/viaproxy/build.gradle.kts +++ b/bootstrap/viaproxy/build.gradle.kts @@ -12,8 +12,9 @@ platformRelocate("net.kyori") platformRelocate("org.yaml") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("org.cloudburstmc.netty") +platformRelocate("org.bstats") platformRelocate("org.incendo") -platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated +platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated // These dependencies are already present on the platform provided(libs.viaproxy) diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java deleted file mode 100644 index afc46fa6a..000000000 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ -package org.geysermc.geyser.platform.viaproxy; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import net.raphimc.vialegacy.api.LegacyProtocolVersion; -import net.raphimc.viaproxy.ViaProxy; -import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; - -import java.io.File; -import java.nio.file.Path; - -@JsonIgnoreProperties(ignoreUnknown = true) -@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final -public class GeyserViaProxyConfiguration extends GeyserJacksonConfiguration { - - private RemoteConfiguration remote = new RemoteConfiguration() { - @Override - public boolean isForwardHost() { - return super.isForwardHost() || !ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE); - } - }; - - @Override - public Path getFloodgateKeyPath() { - return new File(GeyserViaProxyPlugin.ROOT_FOLDER, this.getFloodgateKeyFile()).toPath(); - } - - @Override - public int getPingPassthroughInterval() { - int interval = super.getPingPassthroughInterval(); - if (interval < 15 && ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) { - // <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made - interval = 15; - } - return interval; - } - - @Override - public RemoteConfiguration getRemote() { - return this.remote; - } - -} diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java index 0bfc9d022..4478d11ef 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java @@ -24,6 +24,7 @@ */ package org.geysermc.geyser.platform.viaproxy; +import com.google.gson.annotations.JsonAdapter; import lombok.Getter; import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.plugins.ViaProxyPlugin; @@ -41,7 +42,7 @@ public class GeyserViaProxyDumpInfo extends BootstrapDumpInfo { private final String platformVersion; private final boolean onlineMode; - @AsteriskSerializer.Asterisk(isIp = true) + @JsonAdapter(value = AsteriskSerializer.class) private final String serverIP; private final int serverPort; private final List plugins; diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java index fdcfe2279..8ed1b5d65 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.viaproxy; import net.raphimc.viaproxy.cli.ConsoleFormatter; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.command.GeyserCommandSource; @@ -75,6 +76,13 @@ public class GeyserViaProxyLogger implements GeyserLogger, GeyserCommandSource { } } + @Override + public void debug(@Nullable Object object) { + if (this.debug) { + this.logger.debug(ConsoleFormatter.convert(String.valueOf(object))); + } + } + @Override public void debug(String message, Object... arguments) { if (this.debug) { diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java index 678db0e53..49ee32ce9 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java @@ -36,7 +36,9 @@ import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent; import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; +import net.raphimc.viaproxy.plugins.events.ViaProxyLoadedEvent; import net.raphimc.viaproxy.plugins.events.types.ITyped; +import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig; import org.apache.logging.log4j.LogManager; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; @@ -47,18 +49,19 @@ import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.ConfigLoader; +import org.geysermc.geyser.configuration.GeyserConfig; +import org.geysermc.geyser.configuration.GeyserPluginConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListener; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.LoopbackUtil; +import org.spongepowered.configurate.serialize.SerializationException; import java.io.File; -import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.file.Files; @@ -67,10 +70,10 @@ import java.util.UUID; public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap, EventRegistrar { - public static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser"); + private static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser"); private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser")); - private GeyserViaProxyConfiguration config; + private GeyserPluginConfig geyserConfig; private GeyserImpl geyser; private StandaloneCloudCommandManager cloud; private CommandRegistry commandRegistry; @@ -79,10 +82,6 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst @Override public void onEnable() { ROOT_FOLDER.mkdirs(); - - GeyserLocale.init(this); - this.onGeyserInitialize(); - ViaProxy.EVENT_MANAGER.register(this); } @@ -91,6 +90,12 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst this.onGeyserShutdown(); } + @EventHandler + private void onViaProxyLoaded(ViaProxyLoadedEvent event) { + GeyserLocale.init(this); + this.onGeyserInitialize(); + } + @EventHandler private void onConsoleCommand(final ConsoleCommandEvent event) { final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand(); @@ -119,6 +124,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst if (event.getType() != ITyped.Type.POST || event.isLegacyPassthrough()) { return; } + // TODO remove if (System.getProperty("geyser.viaproxy.disableIpPassthrough") != null) { // Temporary until Configurate branch is merged return; } @@ -147,11 +153,12 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst @Override public void onGeyserInitialize() { - if (!this.loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } - this.geyser = GeyserImpl.load(PlatformType.VIAPROXY, this); + this.geyser = GeyserImpl.load(this); this.geyser.eventBus().register(this, new GeyserServerTransferListener()); LoopbackUtil.checkAndApplyLoopback(this.logger); } @@ -164,7 +171,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst } boolean reloading = geyser.isReloading(); if (reloading) { - if (!this.loadConfig()) { + geyserConfig = loadConfig(GeyserPluginConfig.class); + if (geyserConfig == null) { return; } } else { @@ -185,7 +193,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst // Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser); } - if (this.config.getRemote().authType() == AuthType.FLOODGATE) { + if (this.geyserConfig.java().authType() == AuthType.FLOODGATE) { ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true); } } @@ -201,8 +209,13 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst } @Override - public GeyserConfiguration getGeyserConfig() { - return this.config; + public @NonNull PlatformType platformType() { + return PlatformType.VIAPROXY; + } + + @Override + public GeyserPluginConfig config() { + return this.geyserConfig; } @Override @@ -259,19 +272,36 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst return false; } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean loadConfig() { - try { - final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.config = FileUtils.loadConfig(configFile, GeyserViaProxyConfiguration.class); - } catch (IOException e) { - this.logger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e); - return false; - } - this.config.getRemote().setAuthType(Files.isRegularFile(this.config.getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE); - this.logger.setDebug(this.config.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(this.config, this.logger); - return true; + @Override + public Path getFloodgateKeyPath() { + return new File(ROOT_FOLDER, geyserConfig.advanced().floodgateKeyFile()).toPath(); } + @Override + public T loadConfig(Class configClass) { + T config = new ConfigLoader(this) + .transformer(node -> { + try { + if (!ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE)) { + node.node("java", "forward-host").set(true); + } + + var pingPassthroughInterval = node.node("ping-passthrough-interval"); + int interval = pingPassthroughInterval.getInt(); + if (interval < 15 && ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) { + // <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made + pingPassthroughInterval.set(15); + } + } catch (SerializationException e) { + throw new RuntimeException(e); + } + }) + .configFile(new File(ROOT_FOLDER, "config.yml")) + .load(configClass); + if (config != null) { + this.geyserConfig = (GeyserPluginConfig) config; + config.java().authType(Files.isRegularFile(getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE); + } + return config; + } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5c7f34499..bd535a30d 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -14,19 +14,29 @@ dependencies { api(projects.common) api(projects.api) - // Jackson JSON and YAML serialization - api(libs.bundles.jackson) + api(libs.yaml) // Used for extensions + annotationProcessor(libs.configurate.`interface`.ap) + api(libs.configurate.`interface`) + implementation(libs.configurate.yaml) api(libs.guava) + compileOnly(libs.gson.record.factory) { + isTransitive = false + } + // Fastutil Maps implementation(libs.bundles.fastutil) // Network libraries implementation(libs.websocket) - api(libs.bundles.protocol) + api(libs.bundles.protocol) { + exclude("com.fasterxml.jackson.core", "jackson-annotations") + } - api(libs.minecraftauth) + api(libs.minecraftauth) { + exclude("com.google.code.gson", "gson") + } api(libs.mcprotocollib) { exclude("io.netty", "netty-all") exclude("net.raphimc", "MinecraftAuth") @@ -60,6 +70,7 @@ dependencies { // Test testImplementation(libs.junit) + testImplementation(libs.gson.runtime) // Record support testImplementation(libs.mockito) // Annotation Processors @@ -68,6 +79,8 @@ dependencies { annotationProcessor(projects.ap) api(libs.events) + + api(libs.bstats) } tasks.processResources { diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 2b58e7e84..5c664b237 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -42,6 +42,10 @@ public final class Constants { public static final String MINECRAFT_SKIN_SERVER_URL = "https://textures.minecraft.net/texture/"; + public static final int CONFIG_VERSION = 5; + + public static final int BSTATS_ID = 5273; + static { URI wsUri = null; try { diff --git a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java index 761e67a67..cde2ff973 100644 --- a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java +++ b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java @@ -25,14 +25,14 @@ package org.geysermc.geyser; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.text.GeyserLocale; import java.nio.file.Files; import java.nio.file.Path; public class FloodgateKeyLoader { - public static Path getKeyPath(GeyserJacksonConfiguration config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + public static Path getKeyPath(GeyserConfig config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { // Always prioritize Floodgate's key, if it is installed. // This mostly prevents people from trying to copy the key and corrupting it in the process if (floodgateDataFolder != null) { @@ -45,13 +45,7 @@ public class FloodgateKeyLoader { } } - Path floodgateKey; - if (config.getFloodgateKeyFile().equals("public-key.pem")) { - logger.debug("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead"); - floodgateKey = geyserDataFolder.resolve("key.pem"); - } else { - floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); - } + Path floodgateKey = geyserDataFolder.resolve(config.advanced().floodgateKeyFile()); if (!Files.exists(floodgateKey)) { logger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed")); diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index c5b271593..aa0d651b9 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -27,12 +27,16 @@ package org.geysermc.geyser; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.ConfigLoader; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.util.metrics.MetricsPlatform; +import org.geysermc.geyser.util.metrics.ProvidedMetricsPlatform; import java.io.InputStream; import java.net.SocketAddress; @@ -68,11 +72,19 @@ public interface GeyserBootstrap { void onGeyserShutdown(); /** - * Returns the current GeyserConfiguration + * Returns the platform type this Geyser instance is running on. * - * @return The current GeyserConfiguration + * @return the PlatformType this Geyser instance is running on. */ - GeyserConfiguration getGeyserConfig(); + @NonNull + PlatformType platformType(); + + /** + * Returns the current GeyserConfig + * + * @return The current GeyserConfig + */ + GeyserConfig config(); /** * Returns the current GeyserLogger @@ -194,4 +206,18 @@ public interface GeyserBootstrap { * Tests if Floodgate is installed, loads the Floodgate key if so, and returns the result of Floodgate installed. */ boolean testFloodgatePluginPresent(); + + /** + * TEMPORARY - will be removed after The Merge:tm:. + */ + Path getFloodgateKeyPath(); + + @Nullable + default MetricsPlatform createMetricsPlatform() { + return new ProvidedMetricsPlatform(); + } + + default T loadConfig(Class configClass) { + return new ConfigLoader(this).createFolder().load(configClass); + } } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 0785cdc1f..ed9d6c90c 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -25,10 +25,8 @@ package org.geysermc.geyser; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import io.netty.channel.epoll.Epoll; import io.netty.util.NettyRuntime; import io.netty.util.concurrent.DefaultThreadFactory; @@ -40,6 +38,11 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.raphimc.minecraftauth.msa.data.MsaConstants; import net.raphimc.minecraftauth.msa.model.MsaApplicationConfig; +import org.bstats.MetricsBase; +import org.bstats.charts.AdvancedPie; +import org.bstats.charts.DrilldownPie; +import org.bstats.charts.SimplePie; +import org.bstats.charts.SingleLineChart; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -68,7 +71,8 @@ import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; +import org.geysermc.geyser.configuration.GeyserPluginConfig; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.erosion.UnixSocketClientListener; import org.geysermc.geyser.event.GeyserEventBus; @@ -79,6 +83,7 @@ import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.GeyserServer; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.loader.ResourcePackLoader; @@ -97,15 +102,17 @@ import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.AssetUtils; import org.geysermc.geyser.util.CodeOfConductManager; -import org.geysermc.geyser.util.CooldownUtils; -import org.geysermc.geyser.util.Metrics; +import org.geysermc.geyser.util.JsonUtils; import org.geysermc.geyser.util.NewsHandler; import org.geysermc.geyser.util.VersionCheckUtils; import org.geysermc.geyser.util.WebUtils; +import org.geysermc.geyser.util.metrics.MetricsPlatform; import java.io.File; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Type; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; @@ -129,12 +136,7 @@ import java.util.regex.Pattern; @Getter public class GeyserImpl implements GeyserApi, EventRegistrar { - public static final ObjectMapper JSON_MAPPER = new ObjectMapper() - .enable(JsonParser.Feature.IGNORE_UNDEFINED) - .enable(JsonParser.Feature.ALLOW_COMMENTS) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES) - .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); + public static final Gson GSON = JsonUtils.createGson(); public static final String NAME = "Geyser"; public static final String GIT_VERSION = BuildData.GIT_VERSION; @@ -167,13 +169,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { private ScheduledExecutorService scheduledThread; private GeyserServer geyserServer; - private final PlatformType platformType; private final GeyserBootstrap bootstrap; private final GeyserEventBus eventBus; private final GeyserExtensionManager extensionManager; - private Metrics metrics; + private MetricsBase metrics; private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; @Getter(AccessLevel.NONE) @@ -194,12 +195,11 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { @Setter private boolean isEnabled; - private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { + private GeyserImpl(GeyserBootstrap bootstrap) { instance = this; Geyser.set(this); - this.platformType = platformType; this.bootstrap = bootstrap; /* Initialize event bus */ @@ -218,10 +218,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { public void initialize() { // Setup encryption early so we don't start if we can't auth - try { - EncryptionUtils.getMojangPublicKey(); - } catch (Throwable e) { - throw new RuntimeException("Cannot setup authentication! Are you offline? ", e); + if (config().advanced().bedrock().validateBedrockLogin()) { + try { + EncryptionUtils.getMojangPublicKey(); + } catch (Throwable t) { + GeyserImpl.getInstance().getLogger().error("Unable to set up encryption! This can be caused by your internet connection or the Minecraft api being unreachable. ", t); + } } long startupTime = System.currentTimeMillis(); @@ -278,19 +280,19 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { startInstance(); - GeyserConfiguration config = bootstrap.getGeyserConfig(); + GeyserConfig config = bootstrap.config(); double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)); message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console"); logger.info(message); - if (platformType == PlatformType.STANDALONE) { - if (config.getRemote().authType() != AuthType.FLOODGATE) { + if (platformType() == PlatformType.STANDALONE) { + if (config.java().authType() != AuthType.FLOODGATE) { // If the auth-type is Floodgate, then this Geyser instance is probably owned by the Java server logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn")); } - } else if (config.getRemote().authType() == AuthType.FLOODGATE) { + } else if (config.java().authType() == AuthType.FLOODGATE) { VersionCheckUtils.checkForOutdatedFloodgate(logger); } @@ -308,7 +310,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } GeyserLogger logger = bootstrap.getGeyserLogger(); - GeyserConfiguration config = bootstrap.getGeyserConfig(); + GeyserConfig config = bootstrap.config(); ScoreboardUpdater.init(); @@ -316,6 +318,23 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { Registries.RESOURCE_PACKS.load(); + // Warnings to users who enable options that they might not need. + if (config.advanced().bedrock().useHaproxyProtocol()) { + logger.warning("Geyser is configured to expect HAProxy protocol for incoming Bedrock connections."); + logger.warning("If you do not know what this is, open the Geyser config, and set \"use-haproxy-protocol\" under the \"advanced/bedrock\" section to \"false\"."); + } + + if (config.advanced().java().useHaproxyProtocol()) { + logger.warning("Geyser is configured to use proxy protocol when connecting to the Java server."); + logger.warning("If you do not know what this is, open the Geyser config, and set \"use-haproxy-protocol\" under the \"advanced/java\" section to \"false\"."); + } + + if (!config.advanced().bedrock().validateBedrockLogin()) { + logger.error("XBOX AUTHENTICATION IS DISABLED ON THIS GEYSER INSTANCE!"); + logger.error("While this allows using Bedrock edition proxies, it also opens up the ability for hackers to connect with any username they choose."); + logger.error("To change this, set \"disable-xbox-auth\" to \"false\" in Geyser's config file."); + } + String geyserUdpPort = System.getProperty("geyserUdpPort", ""); String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort; if ("-1".equals(pluginUdpPort)) { @@ -324,33 +343,30 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { boolean portPropertyApplied = false; String pluginUdpAddress = System.getProperty("geyserUdpAddress", System.getProperty("pluginUdpAddress", "")); - if (platformType != PlatformType.STANDALONE) { + if (platformType() != PlatformType.STANDALONE) { int javaPort = bootstrap.getServerPort(); - if (config.getRemote().address().equals("auto")) { - config.setAutoconfiguredRemote(true); - String serverAddress = bootstrap.getServerBindAddress(); - if (!serverAddress.isEmpty() && !"0.0.0.0".equals(serverAddress)) { - config.getRemote().setAddress(serverAddress); - } 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.java().port(javaPort); } boolean forceMatchServerPort = "server".equals(pluginUdpPort); - if ((config.getBedrock().isCloneRemotePort() || forceMatchServerPort) && javaPort != -1) { - config.getBedrock().setPort(javaPort); + if ((config.bedrock().cloneRemotePort() || forceMatchServerPort) && javaPort != -1) { + config.bedrock().port(javaPort); if (forceMatchServerPort) { if (geyserUdpPort.isEmpty()) { logger.info("Port set from system generic property to match Java server."); @@ -364,15 +380,15 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { if ("server".equals(pluginUdpAddress)) { String address = bootstrap.getServerBindAddress(); if (!address.isEmpty()) { - config.getBedrock().setAddress(address); + config.bedrock().address(address); } } else if (!pluginUdpAddress.isEmpty()) { - config.getBedrock().setAddress(pluginUdpAddress); + config.bedrock().address(pluginUdpAddress); } if (!portPropertyApplied && !pluginUdpPort.isEmpty()) { int port = Integer.parseInt(pluginUdpPort); - config.getBedrock().setPort(port); + config.bedrock().port(port); if (geyserUdpPort.isEmpty()) { logger.info("Port set from generic system property: " + port); } else { @@ -380,16 +396,17 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } } - if (platformType != PlatformType.VIAPROXY) { + + if (platformType() != PlatformType.VIAPROXY) { boolean floodgatePresent = bootstrap.testFloodgatePluginPresent(); - if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) { + if (config.java().authType() == AuthType.FLOODGATE && !floodgatePresent) { logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; - } else if (config.isAutoconfiguredRemote() && floodgatePresent) { + } else if (floodgatePresent) { // Floodgate installed means that the user wants Floodgate authentication logger.debug("Auto-setting to Floodgate authentication."); - config.getRemote().setAuthType(AuthType.FLOODGATE); + config.java().authType(AuthType.FLOODGATE); } } } @@ -402,7 +419,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { if (parsedPort < 1 || parsedPort > 65535) { throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!"); } - config.getBedrock().setBroadcastPort(parsedPort); + config.advanced().bedrock().broadcastPort(parsedPort); logger.info("Broadcast port set from system property: " + parsedPort); } catch (NumberFormatException e) { logger.error(String.format("Invalid broadcast port from system property: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")")); @@ -410,23 +427,27 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } // It's set to 0 only if no system property or manual config value was set - if (config.getBedrock().broadcastPort() == 0) { - config.getBedrock().setBroadcastPort(config.getBedrock().port()); + if (config.advanced().bedrock().broadcastPort() == 0) { + config.advanced().bedrock().broadcastPort(config.bedrock().port()); } - String remoteAddress = config.getRemote().address(); - // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. - if (!IP_REGEX.matcher(remoteAddress).matches() && !remoteAddress.equalsIgnoreCase("localhost")) { - String[] record = WebUtils.findSrvRecord(this, remoteAddress); - if (record != null) { - int remotePort = Integer.parseInt(record[2]); - config.getRemote().setAddress(remoteAddress = record[3]); - config.getRemote().setPort(remotePort); - logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); + if (!(config instanceof GeyserPluginConfig)) { + String remoteAddress = config.java().address(); + // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. + if (!IP_REGEX.matcher(remoteAddress).matches() && !remoteAddress.equalsIgnoreCase("localhost")) { + String[] record = WebUtils.findSrvRecord(this, remoteAddress); + if (record != null) { + int remotePort = Integer.parseInt(record[2]); + config.java().address(remoteAddress = record[3]); + config.java().port(remotePort); + logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); + } } + } else if (!config.advanced().java().useDirectConnection()) { + logger.warning("The use-direct-connection config option is deprecated. Please reach out to us on Discord if there's a reason it needs to be disabled."); } - pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout()); + pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.pendingAuthenticationTimeout()); this.newsHandler = new NewsHandler(BRANCH, this.buildNumber()); @@ -438,20 +459,19 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { logger.debug("Epoll is not available; Erosion's Unix socket handling will not work."); } - CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); - BedrockDimension.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether + BedrockDimension.changeBedrockNetherId(config.gameplay().netherRoofWorkaround()); // Apply End dimension ID workaround to Nether - Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads"); - if (bedrockThreadCount == null) { + int bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads", -1); + if (bedrockThreadCount == -1) { // Copy the code from Netty's default thread count fallback bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); } this.geyserServer = new GeyserServer(this, bedrockThreadCount); - this.geyserServer.bind(new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port())) + this.geyserServer.bind(new InetSocketAddress(config.bedrock().address(), config.bedrock().port())) .whenComplete((avoid, throwable) -> { - String address = config.getBedrock().address(); - String port = String.valueOf(config.getBedrock().port()); // otherwise we get commas + String address = config.bedrock().address(); + String port = String.valueOf(config.bedrock().port()); // otherwise we get commas if (throwable == null) { if ("0.0.0.0".equals(address)) { @@ -469,9 +489,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } }).join(); - if (config.getRemote().authType() == AuthType.FLOODGATE) { + if (config.java().authType() == AuthType.FLOODGATE) { try { - Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); + Key key = new AesKeyProducer().produceFrom(bootstrap.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.debug("Loaded Floodgate key!"); @@ -483,28 +503,55 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } } - if (config.getMetrics().isEnabled()) { - metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); - metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size)); + MetricsPlatform metricsPlatform = bootstrap.createMetricsPlatform(); + if (metricsPlatform != null && metricsPlatform.enabled()) { + metrics = new MetricsBase( + "server-implementation", + metricsPlatform.serverUuid(), + Constants.BSTATS_ID, + true, // Already checked above. + builder -> { + // OS specific data + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + builder.appendField("osName", osName); + builder.appendField("osArch", osArch); + builder.appendField("osVersion", osVersion); + builder.appendField("coreCount", coreCount); + }, + builder -> {}, + null, + () -> true, + logger::error, + logger::info, + metricsPlatform.logFailedRequests(), + metricsPlatform.logSentData(), + metricsPlatform.logResponseStatusText(), + metricsPlatform.disableRelocateCheck() + ); + metrics.addCustomChart(new SingleLineChart("players", sessionManager::size)); // Prevent unwanted words best we can - metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT))); + metrics.addCustomChart(new SimplePie("authMode", () -> config.java().authType().toString().toLowerCase(Locale.ROOT))); Map> platformTypeMap = new HashMap<>(); Map serverPlatform = new HashMap<>(); serverPlatform.put(bootstrap.getServerPlatform(), 1); platformTypeMap.put(platformType().platformName(), serverPlatform); - metrics.addCustomChart(new Metrics.DrilldownPie("platform", () -> { + metrics.addCustomChart(new DrilldownPie("platform", () -> { // By the end, we should return, for example: // Geyser-Spigot => (Paper, 1) return platformTypeMap; })); - metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); - metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION)); - metrics.addCustomChart(new Metrics.SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.getRemote().isUseProxyProtocol()))); - metrics.addCustomChart(new Metrics.SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.getBedrock().isEnableProxyProtocol()))); - metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { + metrics.addCustomChart(new SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); + metrics.addCustomChart(new SimplePie("version", () -> GeyserImpl.VERSION)); + metrics.addCustomChart(new SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.advanced().java().useHaproxyProtocol()))); + metrics.addCustomChart(new SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.advanced().bedrock().useHaproxyProtocol()))); + metrics.addCustomChart(new AdvancedPie("playerPlatform", () -> { Map valueMap = new HashMap<>(); for (GeyserSession session : sessionManager.getAllSessions()) { if (session == null) continue; @@ -518,7 +565,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } return valueMap; })); - metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { + metrics.addCustomChart(new AdvancedPie("playerVersion", () -> { Map valueMap = new HashMap<>(); for (GeyserSession session : sessionManager.getAllSessions()) { if (session == null) continue; @@ -540,7 +587,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { platformMap.put(bootstrap.getServerPlatform(), 1); versionMap.put(minecraftVersion, platformMap); - metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> { + metrics.addCustomChart(new DrilldownPie("minecraftServerVersion", () -> { // By the end, we should return, for example: // 1.16.5 => (Spigot, 1) return versionMap; @@ -549,7 +596,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { // The following code can be attributed to the PaperMC project // https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614 - metrics.addCustomChart(new Metrics.DrilldownPie("javaVersion", () -> { + metrics.addCustomChart(new DrilldownPie("javaVersion", () -> { Map> map = new HashMap<>(); String javaVersion = System.getProperty("java.version"); Map entry = new HashMap<>(); @@ -584,22 +631,21 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { metrics = null; } - if (config.getRemote().authType() == AuthType.ONLINE) { + if (config.java().authType() == AuthType.ONLINE) { // May be written/read to on multiple threads from each GeyserSession as well as writing the config savedAuthChains = new ConcurrentHashMap<>(); + Type type = new TypeToken>() { }.getType(); File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile(); if (authChainsFile.exists()) { - TypeReference> type = new TypeReference<>() { }; - Map authChainFile = null; - try { - authChainFile = JSON_MAPPER.readValue(authChainsFile, type); + try (FileReader reader = new FileReader(authChainsFile)) { + authChainFile = GSON.fromJson(reader, type); } catch (IOException e) { logger.error("Cannot load saved user tokens!", e); } if (authChainFile != null) { - List validUsers = config.getSavedUserLogins(); + List validUsers = config.savedUserLogins(); boolean doWrite = false; for (Map.Entry entry : authChainFile.entrySet()) { String user = entry.getKey(); @@ -627,7 +673,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); } - if (config.isNotifyOnNewBedrockUpdate()) { + if (config.notifyOnNewBedrockUpdate()) { VersionCheckUtils.checkForGeyserUpdate(this::getLogger); } } @@ -703,6 +749,10 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { runIfNonNull(newsHandler, NewsHandler::shutdown); runIfNonNull(erosionUnixListener, UnixSocketClientListener::close); + if (bootstrap.getGeyserPingPassthrough() instanceof GeyserLegacyPingPassthrough legacyPingPassthrough) { + legacyPingPassthrough.interrupt(); + } + ResourcePackLoader.clear(); CodeOfConductManager.getInstance().save(); @@ -777,13 +827,13 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { @NonNull public RemoteServer defaultRemoteServer() { - return getConfig().getRemote(); + return config().java(); } @Override @NonNull public BedrockListener bedrockListener() { - return getConfig().getBedrock(); + return config().bedrock(); } @Override @@ -801,7 +851,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { @Override @NonNull public PlatformType platformType() { - return platformType; + return bootstrap.platformType(); } @Override @@ -828,9 +878,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { return Integer.parseInt(BUILD_NUMBER); } - public static GeyserImpl load(PlatformType platformType, GeyserBootstrap bootstrap) { + public static GeyserImpl load(GeyserBootstrap bootstrap) { if (instance == null) { - return new GeyserImpl(platformType, bootstrap); + return new GeyserImpl(bootstrap); } return instance; @@ -853,8 +903,8 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { return bootstrap.getGeyserLogger(); } - public GeyserConfiguration getConfig() { - return bootstrap.getGeyserConfig(); + public GeyserConfig config() { + return bootstrap.config(); } public WorldManager getWorldManager() { @@ -867,7 +917,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) { - if (!getConfig().getSavedUserLogins().contains(bedrockName)) { + if (!config().savedUserLogins().contains(bedrockName)) { // Do not save this login return; } @@ -889,11 +939,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { scheduledThread.execute(() -> { // Ensure all writes are handled on the same thread File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile(); - TypeReference> type = new TypeReference<>() { }; + Type type = new TypeToken>() { }.getType(); try (FileWriter writer = new FileWriter(savedAuthChains)) { - JSON_MAPPER.writerFor(type) - .withDefaultPrettyPrinter() - .writeValue(writer, this.savedAuthChains); + GSON.toJson(this.savedAuthChains, type, writer); } catch (IOException e) { getLogger().error("Unable to write saved refresh tokens!", e); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index 5452d6d29..3f042f8e2 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -101,7 +101,10 @@ public interface GeyserLogger extends GeyserCommandSource { * @param object the object to log */ default void debug(@Nullable Object object) { - debug(String.valueOf(object)); + if (isDebug()) { + // Don't create String object by default + info(String.valueOf(object)); + } } /** diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 75ea81ff9..70bd21bc5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -99,8 +99,8 @@ public class CommandRegistry implements EventRegistrar { private static final String GEYSER_ROOT_PERMISSION = "geyser.command"; - public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE || - GeyserImpl.getInstance().getPlatformType() == PlatformType.VIAPROXY; + public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().platformType() == PlatformType.STANDALONE || + GeyserImpl.getInstance().platformType() == PlatformType.VIAPROXY; protected final GeyserImpl geyser; private final CommandManager cloud; @@ -171,7 +171,7 @@ public class CommandRegistry implements EventRegistrar { registerBuiltInCommand(new CustomOptionsCommand("options", "geyser.commands.options.desc", "geyser.command.options")); registerBuiltInCommand(new QuickActionsCommand("quickactions", "geyser.commands.quickactions.desc", "geyser.command.quickactions")); - if (this.geyser.getPlatformType() == PlatformType.STANDALONE) { + if (this.geyser.platformType() == PlatformType.STANDALONE) { registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } 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 d2066dba1..b80650c3a 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 @@ -25,12 +25,13 @@ package org.geysermc.geyser.command.defaults; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.util.LoopbackUtil; import org.geysermc.geyser.util.WebUtils; import org.incendo.cloud.CommandManager; @@ -79,7 +80,7 @@ public class ConnectionTestCommand extends GeyserCommand { // Replace "<" and ">" symbols if they are present to avoid the common issue of people including them final String ip = ipArgument.replace("<", "").replace(">", ""); - final int port = portArgument != null ? portArgument : geyser.getConfig().getBedrock().broadcastPort(); // default bedrock port + final int port = portArgument != null ? portArgument : geyser.config().advanced().bedrock().broadcastPort(); // default bedrock port // Issue: people commonly checking placeholders if (ip.equals("ip")) { @@ -105,42 +106,42 @@ public class ConnectionTestCommand extends GeyserCommand { return; } - GeyserConfiguration config = geyser.getConfig(); + GeyserConfig config = geyser.config(); // Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing - if (config.getBedrock().broadcastPort() == config.getBedrock().port()) { - if (port != config.getBedrock().port()) { + if (config.advanced().bedrock().broadcastPort() == config.bedrock().port()) { + if (port != config.bedrock().port()) { if (portArgument != null) { source.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration (" - + config.getBedrock().port() + ")"); + + config.bedrock().port() + ")"); source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config."); - if (config.getBedrock().isCloneRemotePort()) { + if (config.bedrock().cloneRemotePort()) { source.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead."); } } else { source.sendMessage("You did not specify the port to check (add it with \":\"), " + "and the default port 19132 does not match the port in your Geyser configuration (" - + config.getBedrock().port() + ")!"); + + config.bedrock().port() + ")!"); source.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`."); } } } else { - if (config.getBedrock().broadcastPort() != port) { + if (config.advanced().bedrock().broadcastPort() != port) { source.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration (" - + config.getBedrock().broadcastPort() + "). "); + + config.advanced().bedrock().broadcastPort() + "). "); source.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on."); source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config."); } } // Issue: is the `bedrock` `address` in the config different? - if (!config.getBedrock().address().equals("0.0.0.0")) { + if (!config.bedrock().address().equals("0.0.0.0")) { source.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional."); } // Issue: did someone turn on enable-proxy-protocol, and they didn't mean it? - if (config.getBedrock().isEnableProxyProtocol()) { - source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + + if (config.advanced().bedrock().useHaproxyProtocol()) { + source.sendMessage("You have the `use-haproxy-protocol` setting enabled. " + "Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled."); } @@ -171,7 +172,7 @@ public class ConnectionTestCommand extends GeyserCommand { CONNECTION_TEST_MOTD = connectionTestMotd; source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait..."); - JsonNode output; + JsonObject output; try { String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8); output = WebUtils.getJson("https://checker.geysermc.org/ping?hostname=" + hostname + "&port=" + port); @@ -179,18 +180,18 @@ public class ConnectionTestCommand extends GeyserCommand { CONNECTION_TEST_MOTD = null; } - if (output.get("success").asBoolean()) { - JsonNode cache = output.get("cache"); + if (output.get("success").getAsBoolean()) { + JsonObject cache = output.getAsJsonObject("cache"); String when; - if (cache.get("fromCache").asBoolean()) { - when = cache.get("secondsSince").asInt() + " seconds ago"; + if (cache.get("fromCache").isJsonPrimitive()) { + when = cache.get("secondsSince").getAsBoolean() + " seconds ago"; } else { when = "now"; } - JsonNode ping = output.get("ping"); - JsonNode pong = ping.get("pong"); - String remoteMotd = pong.get("motd").asText(); + JsonObject ping = output.getAsJsonObject("ping"); + JsonObject pong = ping.getAsJsonObject("pong"); + String remoteMotd = pong.get("motd").getAsString(); if (!connectionTestMotd.equals(remoteMotd)) { source.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " + "Did you supply the correct IP and port of your server?"); @@ -198,7 +199,7 @@ public class ConnectionTestCommand extends GeyserCommand { return; } - if (ping.get("tcpFirst").asBoolean()) { + if (ping.get("tcpFirst").getAsBoolean()) { source.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information."); sendLinks(source); return; @@ -210,9 +211,9 @@ public class ConnectionTestCommand extends GeyserCommand { } source.sendMessage("Your server is likely unreachable from outside the network!"); - JsonNode message = output.get("message"); - if (message != null && !message.asText().isEmpty()) { - source.sendMessage("Got the error message: " + message.asText()); + JsonElement message = output.get("message"); + if (message != null && !message.getAsString().isEmpty()) { + source.sendMessage("Got the error message: " + message.getAsString()); } sendLinks(source); } catch (Exception e) { 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 ff879b8a8..10eb5578c 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,11 +25,14 @@ package org.geysermc.geyser.command.defaults; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.util.DefaultIndenter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; @@ -38,16 +41,12 @@ import org.geysermc.geyser.dump.DumpInfo; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.JsonUtils; import org.geysermc.geyser.util.WebUtils; import org.incendo.cloud.CommandManager; import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.suggestion.SuggestionProvider; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser; public class DumpCommand extends GeyserCommand { @@ -56,7 +55,6 @@ public class DumpCommand extends GeyserCommand { private static final Iterable SUGGESTIONS = List.of("full", "offline", "logs"); private final GeyserImpl geyser; - private static final ObjectMapper MAPPER = new ObjectMapper(); private static final String DUMP_URL = "https://dump.geysermc.org/"; public DumpCommand(GeyserImpl geyser, String name, String description, String permission) { @@ -111,20 +109,14 @@ public class DumpCommand extends GeyserCommand { AsteriskSerializer.showSensitive = showSensitive; + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale())); String dumpData; try { DumpInfo dump = new DumpInfo(geyser, addLog); - - if (offlineDump) { - DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); - // Make arrays easier to read - prettyPrinter.indentArraysWith(new DefaultIndenter(" ", "\n")); - dumpData = MAPPER.writer(prettyPrinter).writeValueAsString(dump); - } else { - dumpData = MAPPER.writeValueAsString(dump); - } - } catch (IOException e) { + dumpData = gson.toJson(dump); + } catch (Exception e) { source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; @@ -150,11 +142,11 @@ public class DumpCommand extends GeyserCommand { source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale())); String response = null; - JsonNode responseNode; + JsonObject responseNode; try { response = WebUtils.post(DUMP_URL + "documents", dumpData); - responseNode = MAPPER.readTree(response); - } catch (IOException e) { + responseNode = JsonUtils.parseJson(response); + } catch (Throwable e) { source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); if (e instanceof JsonParseException && response != null) { @@ -164,11 +156,11 @@ public class DumpCommand extends GeyserCommand { } if (!responseNode.has("key")) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").getAsString() : response)); return; } - uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); + uploadedDumpUrl = DUMP_URL + responseNode.get("key").getAsString(); } source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index ef5535cb8..92f67a767 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.command.defaults; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonObject; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.PlatformType; @@ -77,7 +77,7 @@ public class VersionCommand extends GeyserCommand { GeyserImpl.NAME, GeyserImpl.VERSION, SUPPORTED_JAVA_RANGE, SUPPORTED_BEDROCK_RANGE)); // Disable update checking in dev mode and for players in Geyser Standalone - if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { + if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.platformType() == PlatformType.STANDALONE)) { return; } @@ -89,8 +89,8 @@ public class VersionCommand extends GeyserCommand { source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale())); try { int buildNumber = this.geyser.buildNumber(); - JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest"); - int latestBuildNumber = response.get("build").asInt(); + JsonObject response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest"); + int latestBuildNumber = response.get("build").getAsInt(); if (latestBuildNumber == buildNumber) { source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale())); diff --git a/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java b/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java index 59e8db211..b77a6bc0c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java @@ -25,21 +25,18 @@ package org.geysermc.geyser.command.standalone; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; import java.util.Collections; import java.util.Set; @Getter -@JsonIgnoreProperties(ignoreUnknown = true) -@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final +@ConfigSerializable +@SuppressWarnings("FieldMayBeFinal") public class PermissionConfiguration { - @JsonProperty("default-permissions") private Set defaultPermissions = Collections.emptySet(); - @JsonProperty("default-denied-permissions") private Set defaultDeniedPermissions = Collections.emptySet(); } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java b/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java new file mode 100644 index 000000000..f2ea41ad8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/ConfigLoader.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import com.google.common.annotations.VisibleForTesting; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.returnsreceiver.qual.This; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.text.GeyserLocale; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions; +import org.spongepowered.configurate.objectmapping.meta.Processor; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.yaml.NodeStyle; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Consumer; + +public final class ConfigLoader { + private static final String HEADER = """ + -------------------------------- + Geyser Configuration File + + A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. + + GitHub: https://github.com/GeyserMC/Geyser + Discord: https://discord.gg/geysermc + Wiki: https://geysermc.org/wiki + + NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. + In most cases, especially with server hosting providers, further hosting-specific configuration is required. + --------------------------------"""; + + /** + * Only nullable for testing. + */ + private final @Nullable GeyserBootstrap bootstrap; + private PlatformType platformType; + private @Nullable Consumer transformer; + private File configFile; + + /** + * Only set during testing. + */ + @VisibleForTesting + CommentedConfigurationNode configurationNode; + + public ConfigLoader(GeyserBootstrap bootstrap) { + this.bootstrap = bootstrap; + this.platformType = bootstrap.platformType(); + configFile = new File(bootstrap.getConfigFolder().toFile(), "config.yml"); + } + + @VisibleForTesting + ConfigLoader(File file) { + this.bootstrap = null; + configFile = file; + } + + /** + * Creates the directory as indicated by {@link GeyserBootstrap#getConfigFolder()} + */ + @This + public ConfigLoader createFolder() { + Path dataFolder = this.bootstrap.getConfigFolder(); + if (!dataFolder.toFile().exists()) { + if (!dataFolder.toFile().mkdir()) { + GeyserImpl.getInstance().getLogger().warning("Failed to create config folder: " + dataFolder); + } + } + return this; + } + + @This + public ConfigLoader transformer(Consumer transformer) { + this.transformer = transformer; + return this; + } + + @This + public ConfigLoader configFile(File configFile) { + this.configFile = configFile; + return this; + } + + /** + * @return null if the config failed to load. + */ + @Nullable + public T load(Class configClass) { + try { + return load0(configClass); + } catch (IOException ex) { + bootstrap.getGeyserLogger().error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + return null; + } + } + + private T load0(Class configClass) throws ConfigurateException { + var loader = createLoader(configFile); + + CommentedConfigurationNode node = loader.load(); + boolean originallyEmpty = !configFile.exists() || node.isNull(); + + ConfigurationTransformation.Versioned migrations = ConfigMigrations.TRANSFORMER.apply(configClass, bootstrap); + int currentVersion = migrations.version(node); + migrations.apply(node); + int newVersion = migrations.version(node); + + T config = node.get(configClass); + + // Serialize the instance to ensure strict field ordering. Additionally, if we serialized back + // to the old node, existing nodes would only have their value changed, keeping their position + // at the top of the ordered map, forcing all new nodes to the bottom (regardless of field order). + // For that reason, we must also create a new node. + CommentedConfigurationNode newRoot = CommentedConfigurationNode.root(loader.defaultOptions()); + newRoot.set(config); + + if (originallyEmpty || currentVersion != newVersion) { + if (!originallyEmpty && currentVersion > 4) { + // Only copy comments over if the file already existed, and we are going to replace it + + // Second case: Version 4 is pre-configurate where there were commented out nodes. + // These get treated as comments on lower nodes, which produces very undesirable results. + ConfigurationCommentMover.moveComments(node, newRoot); + } + + loader.save(newRoot); + } + + if (transformer != null) { + // We transform AFTER saving so that these specific transformations aren't applied to file. + transformer.accept(newRoot); + config = newRoot.get(configClass); + } + + if (this.bootstrap != null) { // Null for testing only. + this.bootstrap.getGeyserLogger().setDebug(config.debugMode()); + } else { + this.configurationNode = newRoot; + } + + return config; + } + + @VisibleForTesting + CommentedConfigurationNode loadConfigurationNode(Class configClass, PlatformType platformType) throws ConfigurateException { + this.platformType = platformType; + load0(configClass); + return configurationNode.copy(); + } + + private YamlConfigurationLoader createLoader(File file) { + return YamlConfigurationLoader.builder() + .file(file) + .indent(2) + .nodeStyle(NodeStyle.BLOCK) + .defaultOptions(options -> InterfaceDefaultOptions.addTo(options, builder -> { + builder.addProcessor(ExcludePlatform.class, excludePlatform(platformType.platformName())) + .addProcessor(PluginSpecific.class, integrationSpecific(platformType != PlatformType.STANDALONE)); + }) + .shouldCopyDefaults(false) // If we use ConfigurationNode#get(type, default), do not write the default back to the node. + .header(ConfigLoader.HEADER) + .serializers(builder -> builder.register(new LowercaseEnumSerializer()))) + .build(); + } + + private static Processor.Factory excludePlatform(String thisPlatform) { + return (data, fieldType) -> (value, destination) -> { + for (String platform : data.platforms()) { + if (thisPlatform.equals(platform)) { + //noinspection DataFlowIssue + destination.parent().removeChild(destination.key()); + break; + } + } + }; + } + + private static Processor.Factory integrationSpecific(boolean thisConfigPlugin) { + return (data, fieldType) -> (value, destination) -> { + if (data.forPlugin() != thisConfigPlugin) { + //noinspection DataFlowIssue + destination.parent().removeChild(destination.key()); + } + }; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java b/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java new file mode 100644 index 000000000..dbb9bd3b4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import org.geysermc.geyser.GeyserBootstrap; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import java.util.function.BiFunction; + +import static org.spongepowered.configurate.NodePath.path; +import static org.spongepowered.configurate.transformation.TransformAction.remove; +import static org.spongepowered.configurate.transformation.TransformAction.rename; + +public class ConfigMigrations { + + public static final BiFunction, GeyserBootstrap, ConfigurationTransformation.Versioned> TRANSFORMER = (configClass, bootstrap) -> + ConfigurationTransformation.versionedBuilder() + .versionKey("config-version") + .addVersion(5, ConfigurationTransformation.builder() + // Java section + .addAction(path("remote"), rename("java")) + .addAction(path("remote", "address"), (path, value) -> { + if ("auto".equals(value.getString())) { + // Auto-convert back to localhost + value.set("127.0.0.1"); + } + return null; + }) + + // Motd section + .addAction(path("bedrock", "motd1"), renameAndMove("motd", "primary-motd")) + .addAction(path("bedrock", "motd2"), renameAndMove("motd", "secondary-motd")) + .addAction(path("passthrough-motd"), moveTo("motd")) + .addAction(path("passthrough-player-counts"), moveTo("motd")) + .addAction(path("ping-passthrough-interval"), moveTo("motd")) + .addAction(path("max-players"), moveTo("motd")) + .addAction(path("legacy-ping-passthrough"), configClass == GeyserRemoteConfig.class ? remove() : (path, value) -> { + // Invert value + value.set(!value.getBoolean()); + return new Object[]{ "motd", "integrated-ping-passthrough" }; + }) + + // gameplay + .addAction(path("command-suggestions"), moveTo("gameplay")) + .addAction(path("forward-player-ping"), moveTo("gameplay")) + .addAction(path("show-cooldown"), (path, value) -> { + String s = value.getString(); + if (s != null) { + switch (s) { + case "true" -> value.set("title"); + case "false" -> value.set("disabled"); + } + } + return new Object[]{ "gameplay", "show-cooldown" }; + }) + .addAction(path("bedrock", "server-name"), moveTo("gameplay")) + .addAction(path("show-coordinates"), moveTo("gameplay")) + .addAction(path("disable-bedrock-scaffolding"), moveTo("gameplay")) + .addAction(path("custom-skull-render-distance"), moveTo("gameplay")) + .addAction(path("force-resource-packs"), moveTo("gameplay")) + .addAction(path("xbox-achievements-enabled"), moveTo("gameplay")) + .addAction(path("unusable-space-block"), moveTo("gameplay")) + .addAction(path("unusable-space-block"), moveTo("gameplay")) + .addAction(path("add-non-bedrock-items"), renameAndMove("gameplay", "enable-custom-content")) + .addAction(path("above-bedrock-nether-building"), renameAndMove("gameplay", "nether-roof-workaround")) + .addAction(path("xbox-achievements-enabled"), moveTo("gameplay")) + // NOTE: We're not explicitly removing the allow-custom-skulls option, it will already be removed since it + // won't be written back. If we remove it, we can't query the value of it! + .addAction(path("max-visible-custom-skulls"), (path, value) -> { + ConfigurationNode parent = value.parent(); + if (parent != null && parent.isMap()) { + ConfigurationNode allowCustomSkulls = parent.childrenMap().get("allow-custom-skulls"); + if (allowCustomSkulls != null && !allowCustomSkulls.getBoolean()) { + value.set(0); + } + } + return new Object[]{ "gameplay", "max-visible-custom-skulls" }; + }) + .addAction(path("emote-offhand-workaround"), (path, value) -> { + String previous = value.getString(); + if (!Objects.equals(previous, "disabled") && bootstrap != null) { + bootstrap.getGeyserLogger().warning("The emote-offhand-workaround option has been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/EmoteOffhandExtension/"); + } + if (Objects.equals(previous, "no-emotes")) { + value.set(false); + return new Object[]{ "gameplay", "show-emotes" }; + } + return null; + }) + + // For the warning! + .addAction(path("allow-third-party-capes"), (node, value) -> { + if (bootstrap != null) { + bootstrap.getGeyserLogger().warning("Third-party ears/capes have been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/ThirdPartyCosmetics"); + } + return null; + }) + .addAction(path("allow-third-party-ears"), (node, value) -> { + if (bootstrap != null) { + bootstrap.getGeyserLogger().warning("Third-party ears/capes have been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/ThirdPartyCosmetics"); + } + return null; + }) + + // Advanced section + .addAction(path("cache-images"), moveTo("advanced")) + .addAction(path("scoreboard-packet-threshold"), moveTo("advanced")) + .addAction(path("add-team-suggestions"), moveTo("advanced")) + .addAction(path("floodgate-key-file"), (path, value) -> { + // Elimate any legacy config values + if ("public-key.pem".equals(value.getString())) { + value.set("key.pem"); + } + return new Object[]{ "advanced", "floodgate-key-file" }; + }) + + // Bedrock + .addAction(path("bedrock", "broadcast-port"), moveTo("advanced", "bedrock")) + .addAction(path("bedrock", "compression-level"), moveTo("advanced", "bedrock")) + .addAction(path("bedrock", "enable-proxy-protocol"), renameAndMove("advanced", "bedrock", "use-haproxy-protocol")) + .addAction(path("bedrock", "proxy-protocol-whitelisted-ips"), renameAndMove("advanced", "bedrock", "haproxy-protocol-whitelisted-ips")) + .addAction(path("mtu"), moveTo("advanced", "bedrock")) + + // Java + .addAction(path("remote", "use-proxy-protocol"), renameAndMove("advanced", "java", "use-haproxy-protocol")) + .addAction(path("disable-compression"), moveTo("advanced", "java")) + .addAction(path("use-direct-connection"), moveTo("advanced", "java")) + + // Other + .addAction(path("default-locale"), (path, value) -> { + if (value.getString() == null) { + value.set("system"); + } + return null; + }) + .addAction(path("metrics", "uuid"), (path, value) -> { + if ("generateduuid".equals(value.getString())) { + // Manually copied config without Metrics UUID creation? + value.set(UUID.randomUUID()); + } + return new Object[]{ "metrics-uuid" }; + }) + .addAction(path("metrics", "enabled"), (path, value) -> { + // Move to the root, not in the Metrics class. + return new Object[]{ "enable-metrics" }; + }) + + .build()) + .build(); + + static TransformAction renameAndMove(String... newPath) { + return ((path, value) -> Arrays.stream(newPath).toArray()); + } + + static TransformAction moveTo(String... newPath) { + return (path, value) -> { + Object[] arr = path.array(); + if (arr.length == 0) { + throw new ConfigurateException(value, "The root node cannot be renamed!"); + } else { + // create a new array with space for newPath segments + the original last segment + Object[] result = new Object[newPath.length + 1]; + System.arraycopy(newPath, 0, result, 0, newPath.length); + result[newPath.length] = arr[arr.length - 1]; + return result; + } + }; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/ConfigurationCommentMover.java b/core/src/main/java/org/geysermc/geyser/configuration/ConfigurationCommentMover.java new file mode 100644 index 000000000..4aed64da7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/ConfigurationCommentMover.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationVisitor; + +/** + * Moves comments from a different node and puts them on this node + */ +public final class ConfigurationCommentMover implements ConfigurationVisitor.Stateless { + + private final CommentedConfigurationNode otherRoot; + + private ConfigurationCommentMover(@NonNull CommentedConfigurationNode otherNode) { + this.otherRoot = otherNode; + } + + @Override + public void enterNode(final ConfigurationNode node) { + if (!(node instanceof CommentedConfigurationNode destination)) { + // Should not occur because all nodes in a tree are the same type, + // and our static method below ensures this visitor is only used on CommentedConfigurationNodes + throw new IllegalStateException(node.path() + " is not a CommentedConfigurationNode"); + } + // Node with the same path + CommentedConfigurationNode source = otherRoot.node(node.path()); + + moveSingle(source, destination); + } + + private static void moveSingle(@NonNull CommentedConfigurationNode source, @NonNull CommentedConfigurationNode destination) { + // Only transfer the comment, overriding if necessary + String comment = source.comment(); + if (comment != null) { + destination.comment(comment); + } + } + + /** + * Moves comments from a source node and its children to a destination node and its children (of a different tree), overriding if necessary. + * Comments are only moved to the destination node and its children which exist. + * Comments are only moved to and from nodes with the exact same path. + * + * @param source the source of the comments, which must be the topmost parent of a tree + * @param destination the destination of the comments, any node in a different tree + */ + public static void moveComments(@NonNull CommentedConfigurationNode source, @NonNull CommentedConfigurationNode destination) { + if (source.parent() != null) { + throw new IllegalArgumentException("source is not the base of the tree it is within: " + source.path()); + } + + if (source.isNull()) { + // It has no value(s), but may still have a comment on it. Don't both traversing the whole destination tree. + moveSingle(source, destination); + } else { + destination.visit(new ConfigurationCommentMover(source)); + } + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/ExcludePlatform.java similarity index 67% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java rename to core/src/main/java/org/geysermc/geyser/configuration/ExcludePlatform.java index 1102ed0a9..1308872c3 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/ExcludePlatform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.standalone; +package org.geysermc.geyser.configuration; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -import java.nio.file.Path; -import java.nio.file.Paths; - -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration { - @Override - public Path getFloodgateKeyPath() { - return Paths.get(getFloodgateKeyFile()); - } +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcludePlatform { + String[] platforms(); } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java new file mode 100644 index 000000000..c9fef20c9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.network.BedrockListener; +import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.CooldownUtils; +import org.spongepowered.configurate.interfaces.meta.Exclude; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; +import org.spongepowered.configurate.interfaces.meta.range.NumericRange; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@ConfigSerializable +public interface GeyserConfig { + @Comment("Network settings for the Bedrock listener") + BedrockConfig bedrock(); + + @Comment("Network settings for the Java server connection") + JavaConfig java(); + + @Comment("MOTD settings") + MotdConfig motd(); + + @Comment("Gameplay options that affect Bedrock players") + GameplayConfig gameplay(); + + @Comment("The default locale if we don't have the one the client requested. If set to \"system\", the system's language will be used.") + @DefaultString(GeyserLocale.SYSTEM_LOCALE) + @NonNull + String defaultLocale(); + + @Comment("Whether player IP addresses will be logged by the server.") + @DefaultBoolean(true) + boolean logPlayerIpAddresses(); + + @Comment(""" + For online mode authentication type only. + Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. + This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, + but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. + Removing a name from this list will delete its cached login information on the next Geyser startup. + The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json".""") + default List savedUserLogins() { + return List.of("ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername", + "ThisOtherExampleUsernameShouldAlsoBeLongEnough"); + } + + @Comment(""" + For online mode authentication type only. + Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. + User is allowed to disconnect from the server during this period.""") + @DefaultNumeric(120) + int pendingAuthenticationTimeout(); + + @Comment(""" + Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version + that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms + auto-update.""") + @DefaultBoolean(true) + boolean notifyOnNewBedrockUpdate(); + + @Comment("Advanced configuration options. These usually do not need modifications.") + AdvancedConfig advanced(); + + @Comment(""" + bStats is a stat tracker that is entirely anonymous and tracks only basic information + about Geyser, such as how many people are online, how many servers are using Geyser, + what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. + https://bstats.org/plugin/server-implementation/GeyserMC""") + @DefaultBoolean(true) + @ExcludePlatform(platforms = {"BungeeCord", "Spigot", "Velocity"}) // bStats platform versions used + boolean enableMetrics(); + + @Comment("The bstats metrics uuid. Do not touch!") + @ExcludePlatform(platforms = {"BungeeCord", "Spigot", "Velocity"}) // bStats platform versions used + default UUID metricsUuid() { + return UUID.randomUUID(); + } + + @Comment("If debug messages should be sent through console") + boolean debugMode(); + + @Comment("Do not change!") + @SuppressWarnings("unused") + default int configVersion() { + return Constants.CONFIG_VERSION; + } + + @ConfigSerializable + interface BedrockConfig extends BedrockListener { + @Comment(""" + The IP address that Geyser will bind on to listen for incoming Bedrock connections. + Generally, you should only change this if you want to limit what IPs can connect to your server.""") + @NonNull + @Override + @DefaultString("0.0.0.0") + @AsteriskSerializer.Asterisk + String address(); + + @Comment(""" + The port that will Geyser will listen on for incoming Bedrock connections. + Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic.""") + @Override + @DefaultNumeric(19132) + @NumericRange(from = 0, to = 65535) + int port(); + + @Comment(""" + Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + This option makes the Bedrock port the same as the Java port every time you start the server.""") + @DefaultBoolean + @PluginSpecific + boolean cloneRemotePort(); + + void address(String address); + void port(int port); + + @Exclude + @Override + default int broadcastPort() { + return GeyserImpl.getInstance().config().advanced().bedrock().broadcastPort(); + } + + @Exclude + @Override + default String primaryMotd() { + return GeyserImpl.getInstance().config().motd().primaryMotd(); + } + + @Exclude + @Override + default String secondaryMotd() { + return GeyserImpl.getInstance().config().motd().secondaryMotd(); + } + + @Exclude + @Override + default String serverName() { + return GeyserImpl.getInstance().config().gameplay().serverName(); + } + } + + @ConfigSerializable + interface JavaConfig extends RemoteServer { + void address(String address); + void port(int port); + + @Comment(""" + What type of authentication Bedrock players will be checked against when logging into the Java server. + Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline".""") + @NonNull + @Override + default AuthType authType() { + return AuthType.ONLINE; + } + + void authType(AuthType authType); + boolean forwardHostname(); + + @Override + @Exclude + default String minecraftVersion() { + return GameProtocol.getJavaMinecraftVersion(); + } + + @Override + @Exclude + default int protocolVersion() { + return GameProtocol.getJavaProtocolVersion(); + } + + @Override + @Exclude + default boolean resolveSrv() { + return false; + } + } + + @ConfigSerializable + interface MotdConfig { + @Comment(""" + The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true. + If either of these are empty, the respective string will default to "Geyser\"""") + @DefaultString("Geyser") + String primaryMotd(); + @DefaultString("Another Geyser server.") + String secondaryMotd(); + + @Comment("Whether Geyser should relay the MOTD from the Java server to Bedrock players.") + @DefaultBoolean(true) + boolean passthroughMotd(); + + @Comment(""" + Maximum amount of players that can connect. + This is only visual, and is only applied if passthrough-motd is disabled.""") + @DefaultNumeric(100) + int maxPlayers(); + + @Comment("Whether to relay the player count and max players from the Java server to Bedrock players.") + @DefaultBoolean(true) + boolean passthroughPlayerCounts(); + + @Comment(""" + Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + There is no need to disable this unless your MOTD or player count does not appear properly.""") + @DefaultBoolean(true) + @PluginSpecific + boolean integratedPingPassthrough(); + + @Comment("How often to ping the Java server to refresh MOTD and player count, in seconds.") + @DefaultNumeric(3) + int pingPassthroughInterval(); + } + + @ConfigSerializable + interface GameplayConfig { + + @Comment("The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.") + @DefaultString("Geyser") + String serverName(); + + @Comment(""" + Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + https://geysermc.org/img/external/cooldown_indicator.png + This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + This setting can be set to "title", "actionbar" or "false\"""") + default CooldownUtils.CooldownType showCooldown() { + return CooldownUtils.CooldownType.TITLE; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + @Comment(""" + Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.""") + @DefaultBoolean(true) + boolean commandSuggestions(); + + @Comment("Controls if coordinates are shown to players.") + @DefaultBoolean(true) + boolean showCoordinates(); + + @Comment("Whether Bedrock players are blocked from performing their scaffolding-style bridging.") + boolean disableBedrockScaffolding(); + + @Comment(""" + Bedrock prevents building and displaying blocks above Y127 in the Nether. + This config option works around that by changing the Nether dimension ID to the End ID. + The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome.""") + boolean netherRoofWorkaround(); + + @Comment(""" + Whether to show Bedrock Edition emotes to other Bedrock Edition players. + """) + @DefaultBoolean(true) + boolean emotesEnabled(); + + @Comment(""" + Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + """) + @DefaultString("minecraft:barrier") + String unusableSpaceBlock(); + + @Comment(""" + Whether to add any items and blocks which normally does not exist in Bedrock Edition. + This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + If this is disabled, furnace minecart items will be mapped to hopper minecart items. + Geyser's block, item, and skull mappings systems will also be disabled. + This option requires a restart of Geyser in order to change its setting.""") + @DefaultBoolean(true) + boolean enableCustomContent(); + + @Comment(""" + Force clients to load all resource packs if there are any. + If set to false, it allows the user to connect to the server even if they don't + want to download the resource packs.""") + @DefaultBoolean(true) + boolean forceResourcePacks(); + + @Comment(""" + Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + If enabled, force-resource-packs will be enabled.""") + @DefaultBoolean(true) + boolean enableIntegratedPack(); + + @Comment(""" + Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + ping, it may also cause players to time out more easily.""") + boolean forwardPlayerPing(); + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + @Comment(""" + Allows Xbox achievements to be unlocked. + If a player types in an unknown command, they will receive a message that states cheats are disabled. + Otherwise, commands work as expected.""") + boolean xboxAchievementsEnabled(); + + @Comment(""" + The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + A value of 0 will disable all custom skulls. + Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number.""") + @DefaultNumeric(128) + int maxVisibleCustomSkulls(); + + @Comment("The radius in blocks around the player in which custom skulls are displayed.") + @DefaultNumeric(32) + int customSkullRenderDistance(); + } + + @ConfigSerializable + interface AdvancedBedrockConfig { + @Comment(""" + The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + A value of 0 will broadcast the port specified above. + DO NOT change this unless Geyser runs on a different port than the one that is used to connect.""") + @DefaultNumeric(0) + @NumericRange(from = 0, to = 65535) + int broadcastPort(); + + void broadcastPort(int port); + + @Comment(""" + How much to compress network traffic to the Bedrock client. The higher the number, the more CPU usage used, but + the smaller the bandwidth used. Does not have any effect below -1 or above 9. Set to -1 to disable.""") + @DefaultNumeric(6) + @NumericRange(from = -1, to = 9) + int compressionLevel(); + + @Comment(""" + Whether to expect HAPROXY protocol for connecting Bedrock clients. + This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!""") + @DefaultBoolean + boolean useHaproxyProtocol(); + + @Comment(""" + A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + Keeping this list empty means there is no IP address whitelist. + IP addresses, subnets, and links to plain text files are supported.""") + default List haproxyProtocolWhitelistedIps() { + return Collections.emptyList(); + } + + @Comment(""" + The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + 1400 is the default.""") + @DefaultNumeric(1400) + int mtu(); + + @Comment(""" + This option disables the auth step Geyser performs for connecting Bedrock players. + It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + assistance - and use UDP instead of TCP. + Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled.""") + @DefaultBoolean(true) + boolean validateBedrockLogin(); + } + + @ConfigSerializable + interface AdvancedJavaConfig { + @Comment(""" + Whether to enable HAPROXY protocol when connecting to the Java server. + This is useful only when: + 1) Your Java server supports HAPROXY protocol (it probably doesn't) + 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!""") + boolean useHaproxyProtocol(); + + @Comment(""" + Whether to connect directly into the Java server without creating a TCP connection. + This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + If enabled, the remote address and port sections are ignored. + If disabled, expect performance decrease and latency increase. + """) + @DefaultBoolean(true) + @PluginSpecific + boolean useDirectConnection(); + + @Comment(""" + Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + This requires use-direct-connection to be true. + """) + @DefaultBoolean(true) + @PluginSpecific + boolean disableCompression(); + } + + @ConfigSerializable + interface AdvancedConfig { + @Comment(""" + Specify how many days player skin images will be cached to disk to save downloading them from the internet. + A value of 0 is disabled. (Default: 0)""") + int cacheImages(); + + @Comment(""" + Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + a lot of scoreboard packets per second, this can cause serious lag. + This option allows you to specify after how many Scoreboard packets per seconds + the Scoreboard updates will be limited to four updates per second.""") + @DefaultNumeric(20) + int scoreboardPacketThreshold(); + + @Comment(""" + Whether Geyser should send team names in command suggestions. + Disable this if you have a lot of teams used that you don't need as suggestions.""") + @DefaultBoolean(true) + boolean addTeamSuggestions(); + + @Comment(""" + A list of remote resource pack urls to send to the Bedrock client for downloading. + The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + """) + default List resourcePackUrls() { + return Collections.emptyList(); + } + + // Cannot be type File yet because we may want to hide it in plugin instances. + @Comment(""" + Floodgate uses encryption to ensure use from authorized sources. + This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity) + You can ignore this when not using Floodgate. + If you're using a plugin version of Floodgate on the same server, the key will automatically be picked up from Floodgate.""") + @DefaultString("key.pem") + String floodgateKeyFile(); + + @Comment("Advanced networking options for the Geyser to Java server connection") + AdvancedJavaConfig java(); + + @Comment("Advanced networking options for Geyser's Bedrock listener") + AdvancedBedrockConfig bedrock(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java deleted file mode 100644 index 88bb98171..000000000 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.configuration; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.api.network.AuthType; -import org.geysermc.geyser.api.network.BedrockListener; -import org.geysermc.geyser.api.network.RemoteServer; -import org.geysermc.geyser.network.CIDRMatcher; -import org.geysermc.geyser.network.GameProtocol; -import org.geysermc.geyser.text.GeyserLocale; - -import java.nio.file.Path; -import java.util.List; - -public interface GeyserConfiguration { - /** - * If the config was originally 'auto' before the values changed - */ - void setAutoconfiguredRemote(boolean autoconfiguredRemote); - - // Modify this when you introduce breaking changes into the config - int CURRENT_CONFIG_VERSION = 4; - - IBedrockConfiguration getBedrock(); - - IRemoteConfiguration getRemote(); - - List getSavedUserLogins(); - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean isCommandSuggestions(); - - @JsonIgnore - boolean isPassthroughMotd(); - - @JsonIgnore - boolean isPassthroughPlayerCounts(); - - @JsonIgnore - boolean isLegacyPingPassthrough(); - - int getPingPassthroughInterval(); - - boolean isForwardPlayerPing(); - - int getMaxPlayers(); - - boolean isDebugMode(); - - @Deprecated - boolean isAllowThirdPartyCapes(); - - @Deprecated - boolean isAllowThirdPartyEars(); - - String getShowCooldown(); - - boolean isShowCoordinates(); - - boolean isDisableBedrockScaffolding(); - - EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround(); - - String getDefaultLocale(); - - Path getFloodgateKeyPath(); - - boolean isAddNonBedrockItems(); - - boolean isAboveBedrockNetherBuilding(); - - boolean isForceResourcePacks(); - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean isXboxAchievementsEnabled(); - - int getCacheImages(); - - boolean isAllowCustomSkulls(); - - int getMaxVisibleCustomSkulls(); - - int getCustomSkullRenderDistance(); - - boolean isLogPlayerIpAddresses(); - - boolean isNotifyOnNewBedrockUpdate(); - - String getUnusableSpaceBlock(); - - IMetricsInfo getMetrics(); - - int getPendingAuthenticationTimeout(); - - boolean isAutoconfiguredRemote(); - - interface IBedrockConfiguration extends BedrockListener { - void setAddress(String address); - - void setPort(int port); - - void setBroadcastPort(int broadcastPort); - - boolean isCloneRemotePort(); - - int getCompressionLevel(); - - boolean isEnableProxyProtocol(); - - List getProxyProtocolWhitelistedIPs(); - - /** - * @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()} - */ - List getWhitelistedIPsMatchers(); - } - - interface IRemoteConfiguration extends RemoteServer { - - void setAddress(String address); - - void setPort(int port); - - boolean isUseProxyProtocol(); - - boolean isForwardHost(); - - default String minecraftVersion() { - return GameProtocol.getJavaMinecraftVersion(); - } - - default int protocolVersion() { - return GameProtocol.getJavaProtocolVersion(); - } - - void setAuthType(AuthType authType); - } - - interface IMetricsInfo { - - boolean isEnabled(); - - String getUniqueId(); - } - - int getScoreboardPacketThreshold(); - - // if u have offline mode enabled pls be safe - boolean isEnableProxyConnections(); - - int getMtu(); - - boolean isUseDirectConnection(); - - boolean isDisableCompression(); - - int getConfigVersion(); - - static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) { - if (geyserConfig.getConfigVersion() < CURRENT_CONFIG_VERSION) { - geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.outdated")); - } else if (geyserConfig.getConfigVersion() > CURRENT_CONFIG_VERSION) { - geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.too_new")); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java index 1af3578a3..91c019817 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java @@ -25,26 +25,20 @@ package org.geysermc.geyser.configuration; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; import java.util.Collections; import java.util.List; import java.util.Objects; -@JsonIgnoreProperties(ignoreUnknown = true) -@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final +@ConfigSerializable public class GeyserCustomSkullConfiguration { - @JsonProperty("player-usernames") private List playerUsernames; - @JsonProperty("player-uuids") private List playerUUIDs; - @JsonProperty("player-profiles") private List playerProfiles; - @JsonProperty("skin-hashes") private List skinHashes; public List getPlayerUsernames() { @@ -62,4 +56,14 @@ public class GeyserCustomSkullConfiguration { public List getPlayerSkinHashes() { return Objects.requireNonNullElse(skinHashes, Collections.emptyList()); } + + @Override + public String toString() { + return "GeyserCustomSkullConfiguration{" + + "playerUsernames=" + playerUsernames + + ", playerUUIDs=" + playerUUIDs + + ", playerProfiles=" + playerProfiles + + ", skinHashes=" + skinHashes + + '}'; + } } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java deleted file mode 100644 index 81ac824e4..000000000 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.configuration; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import lombok.Getter; -import lombok.Setter; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.network.AuthType; -import org.geysermc.geyser.network.CIDRMatcher; -import org.geysermc.geyser.text.AsteriskSerializer; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.WebUtils; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final -public abstract class GeyserJacksonConfiguration implements GeyserConfiguration { - - @Setter - private boolean autoconfiguredRemote = false; - - private BedrockConfiguration bedrock = new BedrockConfiguration(); - private RemoteConfiguration remote = new RemoteConfiguration(); - - @JsonProperty("saved-user-logins") - private List savedUserLogins = Collections.emptyList(); - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile = "key.pem"; - - public abstract Path getFloodgateKeyPath(); - - @JsonProperty("command-suggestions") - private boolean commandSuggestions = true; - - @JsonProperty("passthrough-motd") - private boolean isPassthroughMotd = false; - - @JsonProperty("passthrough-player-counts") - private boolean isPassthroughPlayerCounts = false; - - @JsonProperty("legacy-ping-passthrough") - private boolean isLegacyPingPassthrough = false; - - @JsonProperty("ping-passthrough-interval") - private int pingPassthroughInterval = 3; - - @JsonProperty("forward-player-ping") - private boolean forwardPlayerPing = false; - - @JsonProperty("max-players") - private int maxPlayers = 100; - - @JsonProperty("debug-mode") - private boolean debugMode = false; - - @JsonProperty("allow-third-party-capes") - private boolean allowThirdPartyCapes = false; - - @JsonProperty("show-cooldown") - private String showCooldown = "title"; - - @JsonProperty("show-coordinates") - private boolean showCoordinates = true; - - @JsonProperty("disable-bedrock-scaffolding") - private boolean disableBedrockScaffolding = false; - - @JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class) - @JsonProperty("emote-offhand-workaround") - private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; - - @JsonProperty("allow-third-party-ears") - private boolean allowThirdPartyEars = false; - - @JsonProperty("default-locale") - private String defaultLocale = null; // is null by default so system language takes priority - - @JsonProperty("cache-images") - private int cacheImages = 0; - - @JsonProperty("allow-custom-skulls") - private boolean allowCustomSkulls = true; - - @JsonProperty("max-visible-custom-skulls") - private int maxVisibleCustomSkulls = 128; - - @JsonProperty("custom-skull-render-distance") - private int customSkullRenderDistance = 32; - - @JsonProperty("add-non-bedrock-items") - private boolean addNonBedrockItems = true; - - @JsonProperty("above-bedrock-nether-building") - private boolean aboveBedrockNetherBuilding = false; - - @JsonProperty("force-resource-packs") - private boolean forceResourcePacks = true; - - @JsonProperty("xbox-achievements-enabled") - private boolean xboxAchievementsEnabled = false; - - @JsonProperty("log-player-ip-addresses") - private boolean logPlayerIpAddresses = true; - - @JsonProperty("notify-on-new-bedrock-update") - private boolean notifyOnNewBedrockUpdate = true; - - @JsonProperty("unusable-space-block") - private String unusableSpaceBlock = "minecraft:barrier"; - - private MetricsInfo metrics = new MetricsInfo(); - - @JsonProperty("pending-authentication-timeout") - private int pendingAuthenticationTimeout = 120; - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class BedrockConfiguration implements IBedrockConfiguration { - @AsteriskSerializer.Asterisk(isIp = true) - @JsonProperty("address") - @Setter - private String address = "0.0.0.0"; - - @Override - public @NonNull String address() { - return address; - } - - @Setter - @JsonProperty("port") - private int port = 19132; - - @Override - public int port() { - return port; - } - - @Setter - @JsonProperty("broadcast-port") - private int broadcastPort = 0; - - @Override - public int broadcastPort() { - return broadcastPort; - } - - @Getter - @JsonProperty("clone-remote-port") - private boolean cloneRemotePort = false; - - @JsonProperty("motd1") - private String motd1 = "GeyserMC"; - - @Override - public String primaryMotd() { - return motd1; - } - - @JsonProperty("motd2") - private String motd2 = "Geyser"; - - @Override - public String secondaryMotd() { - return motd2; - } - - @JsonProperty("server-name") - private String serverName = GeyserImpl.NAME; - - @Override - public String serverName() { - return serverName; - } - - @JsonProperty("compression-level") - private int compressionLevel = 6; - - public int getCompressionLevel() { - return Math.max(-1, Math.min(compressionLevel, 9)); - } - - @Getter - @JsonProperty("enable-proxy-protocol") - private boolean enableProxyProtocol = false; - - @Getter - @JsonProperty("proxy-protocol-whitelisted-ips") - private List proxyProtocolWhitelistedIPs = Collections.emptyList(); - - @JsonIgnore - private List whitelistedIPsMatchers = null; - - @Override - 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: proxyProtocolWhitelistedIPs) { - if (!ip.startsWith("http")) { - whitelistedCIDRs.add(ip); - continue; - } - - WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add); - } - - this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream() - .map(CIDRMatcher::new) - .collect(Collectors.toList()); - } - } - return Collections.unmodifiableList(matchers); - } - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class RemoteConfiguration implements IRemoteConfiguration { - @Setter - @AsteriskSerializer.Asterisk(isIp = true) - @JsonProperty("address") - private String address = "auto"; - - @Override - public String address() { - return address; - } - - @JsonDeserialize(using = PortDeserializer.class) - @Setter - @JsonProperty("port") - private int port = 25565; - - @Override - public int port() { - return port; - } - - @Setter - @JsonDeserialize(using = AuthTypeDeserializer.class) - @JsonProperty("auth-type") - private AuthType authType = AuthType.ONLINE; - - @Override - public @NonNull AuthType authType() { - return authType; - } - - @Override - public boolean resolveSrv() { - return false; - } - - @Getter - @JsonProperty("use-proxy-protocol") - private boolean useProxyProtocol = false; - - @Getter - @JsonProperty("forward-hostname") - private boolean forwardHost = false; - } - - @Getter - @JsonIgnoreProperties(ignoreUnknown = true) - public static class MetricsInfo implements IMetricsInfo { - private boolean enabled = true; - - @JsonDeserialize(using = MetricsIdDeserializer.class) - @JsonProperty("uuid") - private String uniqueId = UUID.randomUUID().toString(); - - private static class MetricsIdDeserializer extends JsonDeserializer { - @Override - public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - String uuid = p.getValueAsString(); - if ("generateduuid".equals(uuid)) { - // Compensate for configs not copied from the jar - return UUID.randomUUID().toString(); - } - return uuid; - } - } - } - - @JsonProperty("scoreboard-packet-threshold") - private int scoreboardPacketThreshold = 10; - - @JsonProperty("enable-proxy-connections") - private boolean enableProxyConnections = false; - - @JsonProperty("mtu") - private int mtu = 1400; - - @JsonProperty("use-direct-connection") - private boolean useDirectConnection = true; - - @JsonProperty("disable-compression") - private boolean isDisableCompression = true; - - @JsonProperty("config-version") - private int configVersion = 0; - - /** - * Ensure that the port deserializes in the config as a number no matter what. - */ - protected static class PortDeserializer extends JsonDeserializer { - @Override - public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - String value = p.getValueAsString(); - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.invalid_port")); - return 25565; - } - } - } - - public static class AuthTypeDeserializer extends JsonDeserializer { - @Override - public AuthType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return AuthType.getByName(p.getValueAsString()); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java new file mode 100644 index 000000000..7800a2c16 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserPluginConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import org.spongepowered.configurate.interfaces.meta.Exclude; +import org.spongepowered.configurate.interfaces.meta.Field; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; + +@ConfigSerializable +public interface GeyserPluginConfig extends GeyserConfig { + @Override + IntegratedJavaConfig java(); + + @Override + PluginMotdConfig motd(); + + @ConfigSerializable + interface PluginMotdConfig extends MotdConfig { + @Comment(""" + How often to ping the Java server to refresh MOTD and player count, in seconds. + Only relevant if integrated-ping-passthrough is disabled.""") + @Override + int pingPassthroughInterval(); + } + + @ConfigSerializable + interface IntegratedJavaConfig extends JavaConfig { + @Override + @Field + String address(); + + @Override + void address(String address); + + @Override + @Field + int port(); + + @Override + void port(int port); + + @Override + @Exclude + default boolean forwardHostname() { + return true; // No need to worry about suspicious behavior flagging the server. + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java new file mode 100644 index 000000000..602bca3e8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserRemoteConfig.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import org.geysermc.geyser.text.AsteriskSerializer; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric; +import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString; +import org.spongepowered.configurate.interfaces.meta.range.NumericRange; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; + +/** + * Used for any instance where the Java server is detached from Geyser. + */ +@ConfigSerializable +public interface GeyserRemoteConfig extends GeyserConfig { + @Override + RemoteConfig java(); + + @ConfigSerializable + interface RemoteConfig extends JavaConfig { + @Override + @Comment("The IP address of the Java Edition server.") + @DefaultString("127.0.0.1") + @AsteriskSerializer.Asterisk + String address(); + + @Override + @Comment("The port of the Java Edition server.") + @DefaultNumeric(25565) + @NumericRange(from = 0, to = 65535) + int port(); + + @Override + @Comment(""" + Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + This is designed to be used for forced hosts on proxies.""") + boolean forwardHostname(); + } +} 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/configuration/EmoteOffhandWorkaroundOption.java b/core/src/main/java/org/geysermc/geyser/configuration/PluginSpecific.java similarity index 58% rename from core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java rename to core/src/main/java/org/geysermc/geyser/configuration/PluginSpecific.java index fd44d3903..91871687a 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/PluginSpecific.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,26 +25,17 @@ package org.geysermc.geyser.configuration; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -import java.io.IOException; - -public enum EmoteOffhandWorkaroundOption { - NO_EMOTES, - EMOTES_AND_OFFHAND, - DISABLED; - - public static class Deserializer extends JsonDeserializer { - @Override - public EmoteOffhandWorkaroundOption deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - String value = p.getValueAsString(); - return switch (value) { - case "no-emotes" -> NO_EMOTES; - case "emotes-and-offhand" -> EMOTES_AND_OFFHAND; - default -> DISABLED; - }; - } - } +/** + * Add to a config value to indicate this field is only for plugin versions of Geyser, + * or vice-versa. + */ +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PluginSpecific { + boolean forPlugin() default true; } diff --git a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java index 7851fadfd..e373309a4 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.dump; +import com.google.gson.annotations.JsonAdapter; import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; @@ -38,7 +39,7 @@ public class BootstrapDumpInfo { private final PlatformType platform; public BootstrapDumpInfo() { - this.platform = GeyserImpl.getInstance().getPlatformType(); + this.platform = GeyserImpl.getInstance().platformType(); } @Getter @@ -55,7 +56,7 @@ public class BootstrapDumpInfo { @AllArgsConstructor public static class ListenerInfo { - @AsteriskSerializer.Asterisk(isIp = true) + @JsonAdapter(value = AsteriskSerializer.class) public String ip; public int port; } 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 aeff8767b..8f33d3c67 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -25,28 +25,39 @@ package org.geysermc.geyser.dump; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.hash.Hashing; import com.google.common.io.ByteSource; import com.google.common.io.Files; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.annotations.SerializedName; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Getter; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.util.DeviceOs; -import org.geysermc.floodgate.util.FloodgateInfoHolder; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.MinecraftVersion; -import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.pack.ResourcePackHolder; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.util.CpuUtils; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.WebUtils; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions; +import org.spongepowered.configurate.serialize.SerializationException; import java.io.File; import java.io.IOException; @@ -57,12 +68,15 @@ import java.net.Socket; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.stream.Collectors; @Getter public class DumpInfo { - @JsonIgnore private static final long MEGABYTE = 1024L * 1024L; private final DumpInfo.VersionInfo versionInfo; @@ -71,16 +85,17 @@ public class DumpInfo { private final Locale systemLocale; private final String systemEncoding; private final GitInfo gitInfo; - private final GeyserConfiguration config; - private final Floodgate floodgate; + private Object config; private final Object2IntMap userPlatforms; private final int connectionAttempts; - private final HashInfo hashInfo; + private final String hash; private final RamInfo ramInfo; private LogsInfo logsInfo; private final BootstrapDumpInfo bootstrapInfo; private final FlagsInfo flagsInfo; private final List extensionInfo; + private final List packInfo; + private final MappingInfo mappingInfo; public DumpInfo(GeyserImpl geyser, boolean addLog) { this.versionInfo = new VersionInfo(); @@ -92,27 +107,35 @@ public class DumpInfo { this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY); - this.config = geyser.getConfig(); - this.floodgate = new Floodgate(); - - String md5Hash = "unknown"; - String sha256Hash = "unknown"; try { - // https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file - // https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java - File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI()); - ByteSource byteSource = Files.asByteSource(file); - // Jenkins uses MD5 for its hash - TODO remove - //noinspection UnstableApiUsage,deprecation - md5Hash = byteSource.hash(Hashing.md5()).toString(); - //noinspection UnstableApiUsage - sha256Hash = byteSource.hash(Hashing.sha256()).toString(); - } catch (Exception e) { - if (this.config.isDebugMode()) { + // Workaround for JsonAdapter not being allowed on methods + ConfigurationOptions options = InterfaceDefaultOptions.addTo(ConfigurationOptions.defaults(), builder -> + builder.addProcessor(AsteriskSerializer.Asterisk.class, String.class, AsteriskSerializer.CONFIGURATE_SERIALIZER)) + .shouldCopyDefaults(false); + + ConfigurationNode configNode = CommentedConfigurationNode.root(options); + configNode.set(geyser.config()); + this.config = toGson(configNode); + } catch (SerializationException e) { + e.printStackTrace(); + if (geyser.config().debugMode()) { e.printStackTrace(); } } - this.hashInfo = new HashInfo(md5Hash, sha256Hash); + + String sha256Hash = "unknown"; + try { + // https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file + File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + ByteSource byteSource = Files.asByteSource(file); + //noinspection UnstableApiUsage + sha256Hash = byteSource.hash(Hashing.sha256()).toString(); + } catch (Exception e) { + if (geyser.config().debugMode()) { + e.printStackTrace(); + } + } + this.hash = sha256Hash; this.ramInfo = new RamInfo(); @@ -140,6 +163,44 @@ public class DumpInfo { for (Extension extension : GeyserApi.api().extensionManager().extensions()) { this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().apiVersion(), extension.description().main(), extension.description().authors())); } + + this.packInfo = Registries.RESOURCE_PACKS.get().values().stream() + .map(PackInfo::new) + .toList(); + this.mappingInfo = new MappingInfo(BlockRegistries.CUSTOM_BLOCKS.get().length, + BlockRegistries.CUSTOM_SKULLS.get().size(), + Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_PROTOCOL).getCustomIdMappings().size() + ); + } + + private JsonElement toGson(ConfigurationNode node) { + if (node.isMap()) { + JsonObject object = new JsonObject(); + node.childrenMap().forEach((key, value) -> { + JsonElement json = toGson(value); + object.add(key.toString(), json); + }); + return object; + } else if (node.isList()) { + JsonArray array = new JsonArray(); + node.childrenList().forEach(childNode -> array.add(toGson(childNode))); + return array; + } else { + return convertRawScalar(node); + } + } + + private JsonElement convertRawScalar(ConfigurationNode node) { + final @Nullable Object value = node.rawScalar(); + if (value == null) { + return JsonNull.INSTANCE; + } else if (value instanceof Number n) { + return new JsonPrimitive(n); + } else if (value instanceof Boolean b) { + return new JsonPrimitive(b); + } else { + return new JsonPrimitive(value.toString()); + } } @Getter @@ -233,17 +294,6 @@ public class DumpInfo { } } - @Getter - public static class Floodgate { - private final Properties gitInfo; - private final Object config; - - Floodgate() { - this.gitInfo = FloodgateInfoHolder.getGitProperties(); - this.config = FloodgateInfoHolder.getConfig(); - } - } - @Getter public static class LogsInfo { private String link; @@ -253,16 +303,13 @@ public class DumpInfo { Map fields = new HashMap<>(); fields.put("content", FileUtils.readAllLines(geyser.getBootstrap().getLogsPath()).collect(Collectors.joining("\n"))); - JsonNode logData = GeyserImpl.JSON_MAPPER.readTree(WebUtils.postForm("https://api.mclo.gs/1/log", fields)); + JsonObject logData = new JsonParser().parse(WebUtils.postForm("https://api.mclo.gs/1/log", fields)).getAsJsonObject(); - this.link = logData.get("url").textValue(); + this.link = logData.get("url").getAsString(); } catch (IOException ignored) { } } } - public record HashInfo(String md5Hash, String sha256Hash) { - } - public record RamInfo(long free, long total, long max) { public RamInfo() { this(Runtime.getRuntime().freeMemory() / MEGABYTE, @@ -283,7 +330,17 @@ public class DumpInfo { public record ExtensionInfo(boolean enabled, String name, String version, String apiVersion, String main, List authors) { } - public record GitInfo(String buildNumber, @JsonProperty("git.commit.id.abbrev") String commitHashAbbrev, @JsonProperty("git.commit.id") String commitHash, - @JsonProperty("git.branch") String branchName, @JsonProperty("git.remote.origin.url") String originUrl) { + public record GitInfo(String buildNumber, @SerializedName("git.commit.id.abbrev") String commitHashAbbrev, @SerializedName("git.commit.id") String commitHash, + @SerializedName("git.branch") String branchName, @SerializedName("git.remote.origin.url") String originUrl) { + } + + public record PackInfo(String name, String type) { + + public PackInfo(ResourcePackHolder holder) { + this(holder.pack().manifest().header().name(), holder.codec().getClass().getSimpleName()); + } + } + + public record MappingInfo(int customBlocks, int customSkulls, int customItems) { } } 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 f720eefd3..284ac4425 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java @@ -72,7 +72,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 b64446bea..7f9bb5061 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 @@ -220,7 +220,7 @@ public class Entity implements GeyserEntity { flagsDirty = false; - if (session.getGeyser().getConfig().isDebugMode() && PRINT_ENTITY_SPAWN_DEBUG) { + if (session.getGeyser().config().debugMode() && PRINT_ENTITY_SPAWN_DEBUG) { EntityType type = definition.entityType(); String name = type != null ? type.name() : getClass().getSimpleName(); session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java index 2e62a4482..57d8c977e 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java @@ -34,6 +34,7 @@ import org.geysermc.geyser.api.pack.exception.ResourcePackException; import org.geysermc.geyser.api.pack.option.ResourcePackOption; import org.geysermc.geyser.pack.GeyserResourcePack; import org.geysermc.geyser.pack.ResourcePackHolder; +import org.geysermc.geyser.util.GeyserIntegratedPackUtil; import java.util.Collection; import java.util.List; @@ -42,11 +43,12 @@ import java.util.Objects; import java.util.UUID; @Getter -public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent { +public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent implements GeyserIntegratedPackUtil { private final Map packs; public GeyserDefineResourcePacksEventImpl(Map packMap) { this.packs = packMap; + registerGeyserPack(this); } @Override @@ -61,6 +63,8 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION); } + preProcessPack(pack); + UUID uuid = resourcePack.uuid(); if (packs.containsKey(uuid)) { throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE); @@ -120,4 +124,14 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack holder.optionHolder().validateAndAdd(holder.pack(), options); } + + @Override + public void unregisterIntegratedPack() { + unregister(INTEGRATED_PACK_UUID); + } + + @Override + public boolean integratedPackRegistered() { + return packs.containsKey(INTEGRATED_PACK_UUID); + } } diff --git a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java index 241f5d170..6ec158a0d 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java @@ -45,6 +45,7 @@ import org.geysermc.geyser.pack.ResourcePackHolder; import org.geysermc.geyser.pack.option.OptionHolder; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.GeyserIntegratedPackUtil; import java.util.AbstractMap; import java.util.ArrayList; @@ -55,7 +56,7 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; -public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent { +public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent implements GeyserIntegratedPackUtil { /** * The packs for this Session. A {@link ResourcePackHolder} may contain resource pack options registered @@ -103,6 +104,8 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION); } + preProcessPack(pack); + UUID uuid = resourcePack.uuid(); if (packs.containsKey(uuid)) { throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE); @@ -178,6 +181,16 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE holder.validateAndAdd(pack, options); } + @Override + public void unregisterIntegratedPack() { + unregister(INTEGRATED_PACK_UUID); + } + + @Override + public boolean integratedPackRegistered() { + return packs.containsKey(INTEGRATED_PACK_UUID); + } + // Methods used internally for e.g. ordered packs, or resource pack entries public List orderedPacks() { @@ -201,7 +214,14 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE public List infoPacketEntries() { List entries = new ArrayList<>(); + boolean anyCdn = packs.values().stream().anyMatch(holder -> holder.codec() instanceof UrlPackCodec); + boolean warned = false; + for (ResourcePackHolder holder : packs.values()) { + if (!warned && anyCdn && !(holder.codec() instanceof UrlPackCodec)) { + GeyserImpl.getInstance().getLogger().warning("Mixing pack codecs will result in all UrlPackCodec delivered packs to fall back to non-cdn delivery!"); + warned = true; + } GeyserResourcePack pack = holder.pack(); ResourcePackManifest.Header header = pack.manifest().header(); entries.add(new ResourcePacksInfoPacket.Entry( 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 68d1445d8..110da3b0d 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 @@ -460,7 +460,7 @@ public final class ClickPlan { } else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { stateIdIncrements = 1; } else { - if (session.getGeyser().getConfig().isDebugMode()) { + if (session.getGeyser().config().debugMode()) { session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan); } stateIdIncrements = 1; 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 e8c6745c8..76388ef59 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -103,7 +103,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { super(geyser, session); ZlibCompression compression = new ZlibCompression(Zlib.RAW); - compression.setLevel(this.geyser.getConfig().getBedrock().getCompressionLevel()); + compression.setLevel(this.geyser.config().advanced().bedrock().compressionLevel()); this.compressionStrategy = new SimpleCompressionStrategy(compression); } @@ -236,14 +236,17 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { // Can happen if an error occurs in the resource pack event; that'll disconnect the player return PacketSignal.HANDLED; } + session.integratedPackActive(resourcePackLoadEvent.isIntegratedPackActive()); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries()); resourcePacksInfo.setVibrantVisualsForceDisabled(!session.isAllowVibrantVisuals()); - resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); + resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().config().gameplay().forceResourcePacks() || + resourcePackLoadEvent.isIntegratedPackActive()); resourcePacksInfo.setWorldTemplateId(UUID.randomUUID()); resourcePacksInfo.setWorldTemplateVersion("*"); + session.sendUpstreamPacket(resourcePacksInfo); GeyserLocale.loadGeyserLocale(session.locale()); @@ -264,7 +267,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { switch (packet.getStatus()) { case COMPLETED -> { finishedResourcePackSending = true; - if (geyser.getConfig().getRemote().authType() != AuthType.ONLINE) { + if (geyser.config().java().authType() != AuthType.ONLINE) { session.authenticate(session.getAuthData().name()); } else if (!couldLoginUserByName(session.getAuthData().name())) { // We must spawn the white world @@ -296,6 +299,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.sendUpstreamPacket(stackPacket); } + case REFUSED -> session.disconnect("disconnectionScreen.resourcePack"); default -> { GeyserImpl.getInstance().getLogger().debug("received unknown status packet: " + packet); session.disconnect("disconnectionScreen.resourcePack"); @@ -315,7 +319,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean couldLoginUserByName(String bedrockUsername) { - if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) { + if (geyser.config().savedUserLogins().contains(bedrockUsername)) { String authChain = geyser.authChainFor(bedrockUsername); if (authChain != null) { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); @@ -425,7 +429,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { // Also flushes packets // Avoids bursting slower / delayed clients session.sendUpstreamPacketImmediately(data); - GeyserImpl.getInstance().getScheduledThread().schedule(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS); + session.scheduleInEventLoop(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS); } else { session.sendUpstreamPacket(data); } 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..99aba7bc5 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 @@ -50,7 +50,7 @@ public abstract class GeyserInjector { * @param bootstrap the bootstrap of the Geyser instance. */ public void initializeLocalChannel(GeyserBootstrap bootstrap) { - if (!bootstrap.getGeyserConfig().isUseDirectConnection()) { + if (!bootstrap.config().advanced().java().useDirectConnection()) { bootstrap.getGeyserLogger().debug("Disabling direct injection!"); return; } 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 0b5e644d8..4fe0b3802 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 @@ -46,7 +46,7 @@ import org.cloudburstmc.protocol.bedrock.BedrockPong; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.connection.ConnectionRequestEvent; import org.geysermc.geyser.command.defaults.ConnectionTestCommand; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.event.type.GeyserBedrockPingEventImpl; import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.network.GameProtocol; @@ -60,10 +60,13 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.WebUtils; import org.geysermc.mcprotocollib.network.helper.TransportHelper; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -131,7 +134,7 @@ public final class GeyserServer { this.listenCount = 1; } - if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + if (this.geyser.config().advanced().bedrock().useHaproxyProtocol()) { this.proxiedAddresses = ExpiringMap.builder() .expiration(30 + 1, TimeUnit.MINUTES) .expirationPolicy(ExpirationPolicy.ACCESSED).build(); @@ -139,7 +142,7 @@ public final class GeyserServer { this.proxiedAddresses = null; } - this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort(); + this.broadcastPort = geyser.config().advanced().bedrock().broadcastPort(); } public CompletableFuture bind(InetSocketAddress address) { @@ -161,12 +164,12 @@ public final class GeyserServer { .addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this)); // Add proxy handler - boolean isProxyProtocol = this.geyser.getConfig().getBedrock().isEnableProxyProtocol(); + boolean isProxyProtocol = this.geyser.config().advanced().bedrock().useHaproxyProtocol(); if (isProxyProtocol) { channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler()); } - boolean isWhitelistedProxyProtocol = isProxyProtocol && !this.geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs().isEmpty(); + boolean isWhitelistedProxyProtocol = isProxyProtocol && !this.geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps().isEmpty(); if (Boolean.parseBoolean(System.getProperty("Geyser.RakRateLimitingDisabled", "false")) || isWhitelistedProxyProtocol) { // We would already block any non-whitelisted IP addresses in onConnectionRequest so we can remove the rate limiter channel.pipeline().remove(RakServerRateLimiter.NAME); @@ -200,7 +203,7 @@ public final class GeyserServer { @SuppressWarnings("Convert2MethodRef") private ServerBootstrap createBootstrap() { - if (this.geyser.getConfig().isDebugMode()) { + if (this.geyser.config().debugMode()) { this.geyser.getLogger().debug("Transport type: " + TRANSPORT.method().name()); if (TRANSPORT.datagramChannelClass() == NioDatagramChannel.class) { if (System.getProperties().contains("disableNativeEventLoop")) { @@ -216,7 +219,7 @@ public final class GeyserServer { GeyserServerInitializer serverInitializer = new GeyserServerInitializer(this.geyser); playerGroup = serverInitializer.getEventLoopGroup(); - this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu()); + this.geyser.getLogger().debug("Setting MTU to " + this.geyser.config().advanced().bedrock().mtu()); int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT); this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit); @@ -231,7 +234,7 @@ public final class GeyserServer { .channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannelClass())) .group(group, childGroup) .option(RakChannelOption.RAK_HANDLE_PING, true) - .option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu()) + .option(RakChannelOption.RAK_MAX_MTU, this.geyser.config().advanced().bedrock().mtu()) .option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit) .option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit) .option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie) @@ -239,10 +242,10 @@ public final class GeyserServer { } public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { - List allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs(); - if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) { + List allowedProxyIPs = geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps(); + if (geyser.config().advanced().bedrock().useHaproxyProtocol() && !allowedProxyIPs.isEmpty()) { boolean isWhitelistedIP = false; - for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) { + for (CIDRMatcher matcher : getWhitelistedIPsMatchers()) { if (matcher.matches(inetSocketAddress.getAddress())) { isWhitelistedIP = true; break; @@ -256,8 +259,8 @@ public final class GeyserServer { } String ip; - if (geyser.getConfig().isLogPlayerIpAddresses()) { - if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + if (geyser.config().logPlayerIpAddresses()) { + if (geyser.config().advanced().bedrock().useHaproxyProtocol()) { ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString(); } else { ip = inetSocketAddress.toString(); @@ -283,10 +286,10 @@ public final class GeyserServer { } public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) { - if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { + if (geyser.config().debugMode() && PRINT_DEBUG_PINGS) { String ip; - if (geyser.getConfig().isLogPlayerIpAddresses()) { - if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + if (geyser.config().logPlayerIpAddresses()) { + if (geyser.config().advanced().bedrock().useHaproxyProtocol()) { ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString(); } else { ip = inetSocketAddress.toString(); @@ -297,10 +300,10 @@ public final class GeyserServer { geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip)); } - GeyserConfiguration config = geyser.getConfig(); + GeyserConfig config = geyser.config(); GeyserPingInfo pingInfo = null; - if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) { + if (config.motd().passthroughMotd() || config.motd().passthroughPlayerCounts()) { IGeyserPingPassthrough pingPassthrough = geyser.getBootstrap().getGeyserPingPassthrough(); if (pingPassthrough != null) { pingInfo = pingPassthrough.getPingInformation(inetSocketAddress); @@ -317,25 +320,25 @@ public final class GeyserServer { .ipv6Port(this.broadcastPort) .serverId(channel.config().getOption(RakChannelOption.RAK_GUID)); - if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { + if (config.motd().passthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); - String mainMotd = (motd.length > 0) ? motd[0] : config.getBedrock().primaryMotd(); // First line of the motd. - String subMotd = (motd.length > 1) ? motd[1] : config.getBedrock().secondaryMotd(); // Second line of the motd if present, otherwise default. + String mainMotd = (motd.length > 0) ? motd[0] : config.motd().primaryMotd(); // First line of the motd. + String subMotd = (motd.length > 1) ? motd[1] : config.motd().secondaryMotd(); // Second line of the motd if present, otherwise default. pong.motd(mainMotd.trim()); pong.subMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit. } else { - pong.motd(config.getBedrock().primaryMotd()); - pong.subMotd(config.getBedrock().secondaryMotd()); + pong.motd(config.motd().primaryMotd()); + pong.subMotd(config.motd().secondaryMotd()); } // Placed here to prevent overriding values set in the ping event. - if (config.isPassthroughPlayerCounts() && pingInfo != null) { + if (config.motd().passthroughPlayerCounts() && pingInfo != null) { pong.playerCount(pingInfo.getPlayers().getOnline()); pong.maximumPlayerCount(pingInfo.getPlayers().getMax()); } else { pong.playerCount(geyser.getSessionManager().getSessions().size()); - pong.maximumPlayerCount(config.getMaxPlayers()); + pong.maximumPlayerCount(config.motd().maxPlayers()); } this.geyser.eventBus().fire(new GeyserBedrockPingEventImpl(pong, inetSocketAddress)); @@ -386,6 +389,35 @@ public final class GeyserServer { return pong; } + private List whitelistedIPsMatchers = null; + + /** + * @return Unmodifiable list of {@link CIDRMatcher}s from {@link GeyserConfig.AdvancedBedrockConfig#haproxyProtocolWhitelistedIps()} + */ + 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().advanced().bedrock().haproxyProtocolWhitelistedIps()) { + if (!ip.startsWith("http")) { + whitelistedCIDRs.add(ip); + continue; + } + + WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add); + } + + this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream() + .map(CIDRMatcher::new) + .toList(); + } + } + return Collections.unmodifiableList(matchers); + } + /** * @return the throwable from the given supplier, or the throwable caught while calling the supplier. */ diff --git a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java index 6b309e910..52beba2ce 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java +++ b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java @@ -25,21 +25,23 @@ package org.geysermc.geyser.pack; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.pack.ResourcePackManifest; -import java.io.IOException; +import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.UUID; public record GeyserResourcePackManifest( - @JsonProperty("format_version") int formatVersion, + @SerializedName("format_version") int formatVersion, Header header, Collection modules, Collection dependencies, @@ -60,13 +62,13 @@ public record GeyserResourcePackManifest( this.settings = ensureNonNull(settings); } - public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { } + public record Header(UUID uuid, Version version, String name, String description, @SerializedName("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { } public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { } public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { } - public record Subpack(@JsonProperty("folder_name") String folderName, String name, @JsonProperty("memory_tier") Float memoryTier) implements ResourcePackManifest.Subpack { } + public record Subpack(@SerializedName("folder_name") String folderName, String name, @SerializedName("memory_tier") Float memoryTier) implements ResourcePackManifest.Subpack { } public record Setting(String type, String text) implements ResourcePackManifest.Setting { } @@ -75,7 +77,7 @@ public record GeyserResourcePackManifest( return Collections.unmodifiableCollection(collection); } - @JsonDeserialize(using = Version.VersionDeserializer.class) + @JsonAdapter(value = Version.VersionDeserializer.class) public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version { @Override @@ -83,11 +85,11 @@ public record GeyserResourcePackManifest( return major + "." + minor + "." + patch; } - public static class VersionDeserializer extends JsonDeserializer { + public static class VersionDeserializer implements JsonDeserializer { @Override - public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - int[] version = ctxt.readValue(p, int[].class); - return new Version(version[0], version[1], version[2]); + public Version deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonArray array = json.getAsJsonArray(); + return new Version(array.get(0).getAsInt(), array.get(1).getAsInt(), array.get(2).getAsInt()); } } } 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..936f41899 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().gameplay().enableCustomContent()) { packFile.delete(); // No need to keep resource pack return null; } @@ -161,7 +161,7 @@ public class SkullResourcePackManager { } } catch (IOException e) { GeyserImpl.getInstance().getLogger().debug("Unable to clean up skull skin cache."); - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } } 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 27b405348..aeae1d901 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,8 +25,7 @@ package org.geysermc.geyser.ping; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; +import com.google.gson.JsonSyntaxException; import io.netty.handler.codec.haproxy.HAProxyCommand; import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; import io.netty.util.NetUtil; @@ -34,18 +33,29 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.util.VarInts; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.util.JsonUtils; -import java.io.*; -import java.net.*; -import java.util.concurrent.TimeUnit; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.ConnectException; +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; -public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable { +public class GeyserLegacyPingPassthrough extends Thread implements IGeyserPingPassthrough, Runnable { private static final byte[] HAPROXY_BINARY_PREFIX = new byte[]{13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10}; private final GeyserImpl geyser; + private final long interval; - public GeyserLegacyPingPassthrough(GeyserImpl geyser) { + public GeyserLegacyPingPassthrough(GeyserImpl geyser, int interval) { this.geyser = geyser; + this.interval = interval * 1000L; } private GeyserPingInfo pingInfo; @@ -56,12 +66,14 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn * @return GeyserPingPassthrough, or null if not initialized */ public static @Nullable IGeyserPingPassthrough init(GeyserImpl geyser) { - if (geyser.getConfig().isPassthroughMotd() || geyser.getConfig().isPassthroughPlayerCounts()) { - GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser); + if (geyser.config().motd().passthroughMotd() || geyser.config().motd().passthroughPlayerCounts()) { // Ensure delay is not zero - int interval = (geyser.getConfig().getPingPassthroughInterval() == 0) ? 1 : geyser.getConfig().getPingPassthroughInterval(); + int interval = (geyser.config().motd().pingPassthroughInterval() == 0) ? 1 : geyser.config().motd().pingPassthroughInterval(); geyser.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s)."); - geyser.getScheduledThread().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); + GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser, interval); + pingPassthrough.setName("Geyser LegacyPingPassthrough Thread"); + pingPassthrough.setDaemon(true); + pingPassthrough.start(); return pingPassthrough; } return null; @@ -74,76 +86,84 @@ 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(); - InetSocketAddress endpoint = new InetSocketAddress(address, port); - socket.connect(endpoint, 5000); + while (!geyser.isShuttingDown() && !geyser.isReloading()) { + try (Socket socket = new Socket()) { + String address = geyser.config().java().address(); + int port = geyser.config().java().port(); + InetSocketAddress endpoint = new InetSocketAddress(address, port); + socket.connect(endpoint, 5000); - ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); - try (DataOutputStream handshake = new DataOutputStream(byteArrayStream)) { - handshake.write(0x0); - VarInts.writeUnsignedInt(handshake, GameProtocol.getJavaProtocolVersion()); - VarInts.writeUnsignedInt(handshake, address.length()); - handshake.writeBytes(address); - handshake.writeShort(port); - VarInts.writeUnsignedInt(handshake, 1); - } + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + try (DataOutputStream handshake = new DataOutputStream(byteArrayStream)) { + handshake.write(0x0); + VarInts.writeUnsignedInt(handshake, GameProtocol.getJavaProtocolVersion()); + VarInts.writeUnsignedInt(handshake, address.length()); + handshake.writeBytes(address); + handshake.writeShort(port); + VarInts.writeUnsignedInt(handshake, 1); + } - byte[] buffer; + byte[] buffer; - try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream())) { - if (geyser.getConfig().getRemote().isUseProxyProtocol()) { - // 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); - dataOutputStream.writeByte((0x02 << 4) | HAProxyCommand.PROXY.byteValue()); - dataOutputStream.writeByte(socket.getLocalAddress() instanceof Inet4Address ? + try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream())) { + if (geyser.config().advanced().java().useHaproxyProtocol()) { + // HAProxy support + // Based on https://github.com/netty/netty/blob/d8ad931488f6b942dabe28ecd6c399b4438da0a8/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessageEncoder.java#L78 + dataOutputStream.write(HAPROXY_BINARY_PREFIX); + dataOutputStream.writeByte((0x02 << 4) | HAProxyCommand.PROXY.byteValue()); + dataOutputStream.writeByte(socket.getLocalAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4.byteValue() : HAProxyProxiedProtocol.TCP6.byteValue()); - byte[] srcAddrBytes = NetUtil.createByteArrayFromIpAddressString( + byte[] srcAddrBytes = NetUtil.createByteArrayFromIpAddressString( ((InetSocketAddress) socket.getLocalSocketAddress()).getAddress().getHostAddress()); - byte[] dstAddrBytes = NetUtil.createByteArrayFromIpAddressString( + byte[] dstAddrBytes = NetUtil.createByteArrayFromIpAddressString( endpoint.getAddress().getHostAddress()); - dataOutputStream.writeShort(srcAddrBytes.length + dstAddrBytes.length + 4); - dataOutputStream.write(srcAddrBytes); - dataOutputStream.write(dstAddrBytes); - dataOutputStream.writeShort(((InetSocketAddress) socket.getLocalSocketAddress()).getPort()); - dataOutputStream.writeShort(port); - } + dataOutputStream.writeShort(srcAddrBytes.length + dstAddrBytes.length + 4); + dataOutputStream.write(srcAddrBytes); + dataOutputStream.write(dstAddrBytes); + dataOutputStream.writeShort(((InetSocketAddress) socket.getLocalSocketAddress()).getPort()); + dataOutputStream.writeShort(port); + } - VarInts.writeUnsignedInt(dataOutputStream, byteArrayStream.size()); - dataOutputStream.write(byteArrayStream.toByteArray()); - dataOutputStream.writeByte(0x01); - dataOutputStream.writeByte(0x00); - - try (DataInputStream dataInputStream = new DataInputStream(socket.getInputStream())) { - VarInts.readUnsignedInt(dataInputStream); - VarInts.readUnsignedInt(dataInputStream); - int length = VarInts.readUnsignedInt(dataInputStream); - buffer = new byte[length]; - dataInputStream.readFully(buffer); - dataOutputStream.writeByte(0x09); + VarInts.writeUnsignedInt(dataOutputStream, byteArrayStream.size()); + dataOutputStream.write(byteArrayStream.toByteArray()); dataOutputStream.writeByte(0x01); - dataOutputStream.writeLong(System.currentTimeMillis()); + dataOutputStream.writeByte(0x00); - VarInts.readUnsignedInt(dataInputStream); + try (DataInputStream dataInputStream = new DataInputStream(socket.getInputStream())) { + VarInts.readUnsignedInt(dataInputStream); + VarInts.readUnsignedInt(dataInputStream); + int length = VarInts.readUnsignedInt(dataInputStream); + buffer = new byte[length]; + dataInputStream.readFully(buffer); + dataOutputStream.writeByte(0x09); + dataOutputStream.writeByte(0x01); + dataOutputStream.writeLong(System.currentTimeMillis()); + + VarInts.readUnsignedInt(dataInputStream); + } } + + this.pingInfo = JsonUtils.fromJson(buffer, GeyserPingInfo.class); + } catch (SocketTimeoutException | ConnectException ex) { + this.pingInfo = null; + this.geyser.getLogger().debug("Connection timeout for ping passthrough."); + } catch (JsonSyntaxException ex) { + this.geyser.getLogger().error("Failed to parse json when pinging server!", ex); + } catch (EOFException e) { + this.pingInfo = null; + this.geyser.getLogger().warning("Failed to ping the remote Java server! Is it online and configured in Geyser's config?"); + } catch (UnknownHostException ex) { + // Don't reset pingInfo, as we want to keep the last known value + this.geyser.getLogger().warning("Unable to resolve remote host! Is the remote server down or invalid?"); + } catch (IOException e) { + this.geyser.getLogger().error("IO error while trying to use legacy ping passthrough", e); } - this.pingInfo = GeyserImpl.JSON_MAPPER.readValue(buffer, GeyserPingInfo.class); - } catch (SocketTimeoutException | ConnectException ex) { - this.pingInfo = null; - this.geyser.getLogger().debug("Connection timeout for ping passthrough."); - } catch (JsonParseException | JsonMappingException ex) { - this.geyser.getLogger().error("Failed to parse json when pinging server!", ex); - } catch (EOFException e) { - this.pingInfo = null; - this.geyser.getLogger().warning("Failed to ping the remote Java server! Is it online and configured in Geyser's config?"); - } catch (UnknownHostException ex) { - // Don't reset pingInfo, as we want to keep the last known value - this.geyser.getLogger().warning("Unable to resolve remote host! Is the remote server down or invalid?"); - } catch (IOException e) { - this.geyser.getLogger().error("IO error while trying to use legacy ping passthrough", e); + try { + Thread.sleep(interval); + } catch (InterruptedException ex) { + // no-op + } } } } diff --git a/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java b/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java index 9d8da114d..853fa6187 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java @@ -25,21 +25,25 @@ package org.geysermc.geyser.ping; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.annotations.JsonAdapter; import lombok.Data; import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.reflect.Type; + /** * The structure of this class and its nested classes are specifically * designed for the format received by {@link GeyserLegacyPingPassthrough}. */ @Data -@JsonIgnoreProperties(ignoreUnknown = true) public class GeyserPingInfo { @Nullable + @JsonAdapter(DescriptionDeserializer.class) private String description; private Players players; @@ -58,13 +62,7 @@ public class GeyserPingInfo { this.players = new Players(maxPlayers, onlinePlayers); } - @JsonSetter("description") - void setDescription(JsonNode description) { - this.description = description.toString(); - } - @Data - @JsonIgnoreProperties(ignoreUnknown = true) public static class Players { private int max; @@ -79,4 +77,14 @@ public class GeyserPingInfo { this.online = online; } } + + /** + * So GSON does not complain how we are treating Description - it will be converted to a proper Component later. + */ + private static final class DescriptionDeserializer implements JsonDeserializer { + @Override + public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return json.toString(); + } + } } 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 3fd79b8a6..ce713baa2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java @@ -73,7 +73,7 @@ public class PacketTranslatorRegistry extends AbstractMappedRegistry 25 ? packet.getClass().getSimpleName() : packet)); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java index 3205004e4..ce7c9f218 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java @@ -25,14 +25,16 @@ package org.geysermc.geyser.registry.loader; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.type.TypeReference; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.util.JsonUtils; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Type; import java.util.Map; public class BiomeIdentifierRegistryLoader implements RegistryLoader> { @@ -43,12 +45,12 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader> biomeEntriesType = new TypeReference<>() { }; + // Reference variable for Gson to read off of + Type biomeEntriesType = new TypeToken>() { }.getType(); Map biomeEntries; try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/biomes.json")) { - biomeEntries = GeyserImpl.JSON_MAPPER.readValue(stream, biomeEntriesType); + biomeEntries = JsonUtils.fromJson(stream, biomeEntriesType); } catch (IOException e) { throw new AssertionError("Unable to load Bedrock runtime biomes", e); } @@ -66,7 +68,7 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader the value */ public abstract class EffectRegistryLoader implements RegistryLoader { - private static final Map loadedFiles = new WeakHashMap<>(); - public void loadFile(String input) { - if (!loadedFiles.containsKey(input)) { - JsonNode effects; - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) { - effects = GeyserImpl.JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError("Unable to load registrations for " + input, e); - } - loadedFiles.put(input, effects); + public JsonObject loadFile(String input) { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input); + InputStreamReader reader = new InputStreamReader(stream)) { + //noinspection deprecation + return new JsonParser().parse(reader).getAsJsonObject(); + } catch (Exception e) { + throw new AssertionError("Unable to load registrations for " + input, e); } } - - public JsonNode get(String input) { - return loadedFiles.get(input); - } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java index a09d6d6d3..42e34a2d5 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java @@ -25,15 +25,15 @@ package org.geysermc.geyser.registry.loader; -import com.fasterxml.jackson.databind.JsonNode; -import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.data.LevelEventType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.registry.type.ParticleMapping; +import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; -import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -44,16 +44,13 @@ public class ParticleTypesRegistryLoader extends EffectRegistryLoader load(String input) { - this.loadFile(input); - - Iterator> particlesIterator = this.get(input).fields(); + JsonObject particlesJson = this.loadFile(input); Map particles = new Object2ObjectOpenHashMap<>(); try { - while (particlesIterator.hasNext()) { - Map.Entry entry = particlesIterator.next(); + for (Map.Entry entry : particlesJson.entrySet()) { String key = entry.getKey().toUpperCase(Locale.ROOT); - JsonNode bedrockId = entry.getValue().get("bedrockId"); - JsonNode eventType = entry.getValue().get("eventType"); + JsonElement bedrockId = entry.getValue().getAsJsonObject().get("bedrockId"); + JsonElement eventType = entry.getValue().getAsJsonObject().get("eventType"); if (eventType == null && bedrockId == null) { GeyserImpl.getInstance().getLogger().debug("Skipping particle mapping " + key + " because no Bedrock equivalent exists."); continue; @@ -63,16 +60,16 @@ public class ParticleTypesRegistryLoader extends EffectRegistryLoader loadRemotePacks() { + private void loadRemotePacks(GeyserDefineResourcePacksEventImpl event) { GeyserImpl instance = GeyserImpl.getInstance(); // Unable to make this a static variable, as the test would fail final Path cachedDirectory = instance.getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs"); @@ -235,19 +234,14 @@ public class ResourcePackLoader implements RegistryLoader(); + return; } } - //List remotePackUrls = instance.getConfig().getResourcePackUrls(); - List remotePackUrls = List.of(); - Map packMap = new Object2ObjectOpenHashMap<>(); - + List remotePackUrls = instance.config().advanced().resourcePackUrls(); for (String url : remotePackUrls) { try { - GeyserUrlPackCodec codec = new GeyserUrlPackCodec(url); - GeyserResourcePack pack = codec.create(); - packMap.put(pack.uuid(), ResourcePackHolder.of(pack)); + event.register(new GeyserUrlPackCodec(url).create()); } catch (Throwable e) { instance.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url)); instance.getLogger().error(e.getMessage()); @@ -256,8 +250,6 @@ public class ResourcePackLoader implements RegistryLoader load(String input) { this.loadFile(input); - Iterator> effectsIterator = this.get(input).fields(); + JsonObject effectsJson = this.loadFile(input); Map soundEffects = new Object2ObjectOpenHashMap<>(); - while (effectsIterator.hasNext()) { - Map.Entry entry = effectsIterator.next(); - JsonNode node = entry.getValue(); + for (Map.Entry entry : effectsJson.entrySet()) { + JsonObject node = entry.getValue().getAsJsonObject(); try { - String type = node.get("type").asText(); + String type = node.get("type").getAsString(); LevelEvent javaEffect = null; LevelEventTranslator transformer = null; switch (type) { case "soundLevel" -> { javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey()); - LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").asText()); - int data = node.has("data") ? node.get("data").intValue() : 0; + LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").getAsString()); + int data = node.has("data") ? node.get("data").getAsInt() : 0; transformer = new SoundLevelEventTranslator(levelEventType, data); } case "soundEvent" -> { javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey()); - org.cloudburstmc.protocol.bedrock.data.SoundEvent soundEvent = org.cloudburstmc.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").asText()); - String identifier = node.has("identifier") ? node.get("identifier").asText() : ""; - int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1; + org.cloudburstmc.protocol.bedrock.data.SoundEvent soundEvent = org.cloudburstmc.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").getAsString()); + String identifier = node.has("identifier") ? node.get("identifier").getAsString() : ""; + int extraData = node.has("extraData") ? node.get("extraData").getAsInt() : -1; transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData); } case "playSound" -> { javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey()); - String name = node.get("name").asText(); - float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f; - boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue(); - float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").floatValue() : 1.0f; - float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").floatValue() : 0.0f; - boolean relative = !node.has("relative") || node.get("relative").booleanValue(); + String name = node.get("name").getAsString(); + float volume = node.has("volume") ? node.get("volume").getAsFloat() : 1.0f; + boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").getAsBoolean(); + float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").getAsFloat() : 1.0f; + float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").getAsFloat() : 0.0f; + boolean relative = !node.has("relative") || node.get("relative").getAsBoolean(); transformer = new PlaySoundEventTranslator(name, volume, pitchSub, pitchMul, pitchAdd, relative); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java index 7033740c1..af615cd0f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java @@ -25,15 +25,16 @@ package org.geysermc.geyser.registry.loader; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.registry.type.SoundMapping; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; /** @@ -42,30 +43,30 @@ import java.util.Map; public class SoundRegistryLoader implements RegistryLoader> { @Override public Map load(String input) { - JsonNode soundsTree; - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) { - soundsTree = GeyserImpl.JSON_MAPPER.readTree(stream); + JsonObject soundsJson; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input); + InputStreamReader isr = new InputStreamReader(stream)) { + //noinspection deprecation + soundsJson = new JsonParser().parse(isr).getAsJsonObject(); } catch (IOException e) { throw new AssertionError("Unable to load sound mappings", e); } Map soundMappings = new HashMap<>(); - Iterator> soundsIterator = soundsTree.fields(); - while (soundsIterator.hasNext()) { - Map.Entry next = soundsIterator.next(); - JsonNode brMap = next.getValue(); - String javaSound = next.getKey(); + for (Map.Entry entry : soundsJson.entrySet()) { + JsonObject brMap = entry.getValue().getAsJsonObject(); + String javaSound = entry.getKey(); soundMappings.put(javaSound, new SoundMapping( javaSound, - brMap.has("bedrock_mapping") && brMap.get("bedrock_mapping").isTextual() ? brMap.get("bedrock_mapping").asText() : null, - brMap.has("playsound_mapping") && brMap.get("playsound_mapping").isTextual() ? brMap.get("playsound_mapping").asText() : null, - brMap.has("extra_data") && brMap.get("extra_data").isInt() ? brMap.get("extra_data").asInt() : -1, - brMap.has("identifier") && brMap.get("identifier").isTextual() ? brMap.get("identifier").asText() : null, - brMap.has("level_event") && brMap.get("level_event").isBoolean() && brMap.get("level_event").asBoolean(), - brMap.has("pitch_adjust") && brMap.get("pitch_adjust").isNumber() ? brMap.get("pitch_adjust").floatValue() : 1.0f + brMap.has("bedrock_mapping") ? brMap.get("bedrock_mapping").getAsString() : null, + brMap.has("playsound_mapping") ? brMap.get("playsound_mapping").getAsString() : null, + brMap.has("extra_data") ? brMap.get("extra_data").getAsInt() : -1, + brMap.has("identifier") ? brMap.get("identifier").getAsString() : null, + brMap.has("level_event") && brMap.get("level_event").getAsBoolean(), + brMap.has("pitch_adjust") ? brMap.get("pitch_adjust").getAsFloat() : 1.0f ) ); } return soundMappings; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java index d09e0b5a1..6fd23c738 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java @@ -25,7 +25,8 @@ package org.geysermc.geyser.registry.mappings; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.checkerframework.checker.nullness.qual.Nullable; @@ -35,6 +36,7 @@ import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; import org.geysermc.geyser.registry.mappings.versions.MappingsReader; import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1; +import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -95,10 +97,10 @@ public class MappingsConfigReader { } } - public @Nullable JsonNode getMappingsRoot(Path file) { - JsonNode mappingsRoot; - try { - mappingsRoot = GeyserImpl.JSON_MAPPER.readTree(file.toFile()); + public @Nullable JsonObject getMappingsRoot(Path file) { + JsonObject mappingsRoot; + try (FileReader reader = new FileReader(file.toFile())) { + mappingsRoot = (JsonObject) new JsonParser().parse(reader); } catch (IOException e) { GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e); return null; @@ -112,8 +114,8 @@ public class MappingsConfigReader { return mappingsRoot; } - public int getFormatVersion(JsonNode mappingsRoot, Path file) { - int formatVersion = mappingsRoot.get("format_version").asInt(); + public int getFormatVersion(JsonObject mappingsRoot, Path file) { + int formatVersion = mappingsRoot.get("format_version").getAsInt(); if (!this.mappingReaders.containsKey(formatVersion)) { GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion); return -1; @@ -122,7 +124,7 @@ public class MappingsConfigReader { } public void readItemMappingsFromJson(Path file, BiConsumer consumer) { - JsonNode mappingsRoot = getMappingsRoot(file); + JsonObject mappingsRoot = getMappingsRoot(file); if (mappingsRoot == null) { return; @@ -138,7 +140,7 @@ public class MappingsConfigReader { } public void readBlockMappingsFromJson(Path file, BiConsumer consumer) { - JsonNode mappingsRoot = getMappingsRoot(file); + JsonObject mappingsRoot = getMappingsRoot(file); if (mappingsRoot == null) { return; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java index b2bdd5a01..43d9a23d9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.versions; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonObject; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; @@ -36,14 +36,14 @@ import java.nio.file.Path; import java.util.function.BiConsumer; public abstract class MappingsReader { - public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); - public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); + public abstract void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer); + public abstract void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer); - public abstract CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException; - public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; + public abstract CustomItemData readItemMappingEntry(JsonObject node) throws InvalidCustomMappingsFileException; + public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonObject node) throws InvalidCustomMappingsFileException; - protected @Nullable CustomRenderOffsets fromJsonNode(JsonNode node) { - if (node == null || !node.isObject()) { + protected @Nullable CustomRenderOffsets fromJsonObject(JsonObject node) { + if (node == null) { return null; } @@ -53,9 +53,8 @@ public abstract class MappingsReader { ); } - protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonNode node, String hand) { - JsonNode tmpNode = node.get(hand); - if (tmpNode == null || !tmpNode.isObject()) { + protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonObject node, String hand) { + if (!(node.get(hand) instanceof JsonObject tmpNode)) { return null; } @@ -65,9 +64,8 @@ public abstract class MappingsReader { ); } - protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonNode node, String perspective) { - JsonNode tmpNode = node.get(perspective); - if (tmpNode == null || !tmpNode.isObject()) { + protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonObject node, String perspective) { + if (!(node.get(perspective) instanceof JsonObject tmpNode)) { return null; } @@ -78,9 +76,8 @@ public abstract class MappingsReader { ); } - protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) { - JsonNode tmpNode = node.get(offsetType); - if (tmpNode == null || !tmpNode.isObject()) { + protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonObject node, String offsetType) { + if (!(node.get(offsetType) instanceof JsonObject tmpNode)) { return null; } @@ -89,9 +86,9 @@ public abstract class MappingsReader { } return new CustomRenderOffsets.OffsetXYZ( - tmpNode.get("x").floatValue(), - tmpNode.get("y").floatValue(), - tmpNode.get("z").floatValue() + tmpNode.get("x").getAsFloat(), + tmpNode.get("y").getAsFloat(), + tmpNode.get("z").getAsFloat() ); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index c09399980..4126403c8 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -25,8 +25,10 @@ package org.geysermc.geyser.registry.mappings.versions; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,9 +36,14 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockPermutation; import org.geysermc.geyser.api.block.custom.CustomBlockState; -import org.geysermc.geyser.api.block.custom.component.*; +import org.geysermc.geyser.api.block.custom.component.BoxComponent; +import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; +import org.geysermc.geyser.api.block.custom.component.GeometryComponent; +import org.geysermc.geyser.api.block.custom.component.MaterialInstance; +import org.geysermc.geyser.api.block.custom.component.PlacementConditions; import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType; import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face; +import org.geysermc.geyser.api.block.custom.component.TransformationComponent; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.util.CreativeCategory; @@ -57,7 +64,13 @@ import org.geysermc.geyser.util.MathUtils; import org.geysermc.geyser.util.MinecraftKey; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; @@ -68,7 +81,7 @@ import java.util.stream.Collectors; */ public class MappingsReader_v1 extends MappingsReader { @Override - public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer) { this.readItemMappingsV1(file, mappingsRoot, consumer); } @@ -76,24 +89,24 @@ public class MappingsReader_v1 extends MappingsReader { * Read item block from a JSON node * * @param file The path to the file - * @param mappingsRoot The {@link JsonNode} containing the mappings + * @param mappingsRoot The {@link JsonObject} containing the mappings * @param consumer The consumer to accept the mappings - * @see #readBlockMappingsV1(Path, JsonNode, BiConsumer) + * @see #readBlockMappingsV1(Path, JsonObject, BiConsumer) */ @Override - public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer) { this.readBlockMappingsV1(file, mappingsRoot, consumer); } - public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - JsonNode itemsNode = mappingsRoot.get("items"); + public void readItemMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer consumer) { + JsonObject itemsNode = mappingsRoot.getAsJsonObject("items"); - if (itemsNode != null && itemsNode.isObject()) { - itemsNode.fields().forEachRemaining(entry -> { - if (entry.getValue().isArray()) { - entry.getValue().forEach(data -> { + if (itemsNode != null) { + itemsNode.entrySet().forEach(entry -> { + if (entry.getValue() instanceof JsonArray array) { + array.forEach(data -> { try { - CustomItemData customItemData = this.readItemMappingEntry(data); + CustomItemData customItemData = this.readItemMappingEntry((JsonObject) data); consumer.accept(entry.getKey(), customItemData); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); @@ -108,19 +121,17 @@ public class MappingsReader_v1 extends MappingsReader { * Read block mappings from a JSON node * * @param file The path to the file - * @param mappingsRoot The {@link JsonNode} containing the mappings + * @param mappingsRoot The {@link JsonObject} containing the mappings * @param consumer The consumer to accept the mappings - * @see #readBlockMappings(Path, JsonNode, BiConsumer) + * @see #readBlockMappings(Path, JsonObject, BiConsumer) */ - public void readBlockMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - JsonNode blocksNode = mappingsRoot.get("blocks"); - - if (blocksNode != null && blocksNode.isObject()) { - blocksNode.fields().forEachRemaining(entry -> { - if (entry.getValue().isObject()) { + public void readBlockMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer consumer) { + if (mappingsRoot.get("blocks") instanceof JsonObject blocksNode) { + blocksNode.entrySet().forEach(entry -> { + if (entry.getValue() instanceof JsonObject jsonObject) { try { String identifier = MinecraftKey.key(entry.getKey()).asString(); - CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue()); + CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, jsonObject); consumer.accept(identifier, customBlockMapping); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error("Error in registering blocks for custom mapping file: " + file.toString()); @@ -131,85 +142,85 @@ public class MappingsReader_v1 extends MappingsReader { } } - private CustomItemOptions readItemCustomItemOptions(JsonNode node) { + private CustomItemOptions readItemCustomItemOptions(JsonObject node) { CustomItemOptions.Builder customItemOptions = CustomItemOptions.builder(); - JsonNode customModelData = node.get("custom_model_data"); - if (customModelData != null && customModelData.isInt()) { - customItemOptions.customModelData(customModelData.asInt()); + JsonElement customModelData = node.get("custom_model_data"); + if (customModelData != null && customModelData.isJsonPrimitive()) { + customItemOptions.customModelData(customModelData.getAsInt()); } - JsonNode damagePredicate = node.get("damage_predicate"); - if (damagePredicate != null && damagePredicate.isInt()) { - customItemOptions.damagePredicate(damagePredicate.asInt()); + JsonElement damagePredicate = node.get("damage_predicate"); + if (damagePredicate != null && damagePredicate.isJsonPrimitive()) { + customItemOptions.damagePredicate(damagePredicate.getAsInt()); } - JsonNode unbreakable = node.get("unbreakable"); - if (unbreakable != null && unbreakable.isBoolean()) { - customItemOptions.unbreakable(unbreakable.asBoolean()); + JsonElement unbreakable = node.get("unbreakable"); + if (unbreakable != null && unbreakable.isJsonPrimitive()) { + customItemOptions.unbreakable(unbreakable.getAsBoolean()); } - JsonNode defaultItem = node.get("default"); - if (defaultItem != null && defaultItem.isBoolean()) { - customItemOptions.defaultItem(defaultItem.asBoolean()); + JsonElement defaultItem = node.get("default"); + if (defaultItem != null && defaultItem.isJsonPrimitive()) { + customItemOptions.defaultItem(defaultItem.getAsBoolean()); } return customItemOptions.build(); } @Override - public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { - if (node == null || !node.isObject()) { + public CustomItemData readItemMappingEntry(JsonObject node) throws InvalidCustomMappingsFileException { + if (node == null) { throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); } - JsonNode name = node.get("name"); - if (name == null || !name.isTextual() || name.asText().isEmpty()) { + JsonElement name = node.get("name"); + if (name == null || !name.isJsonPrimitive() || name.getAsString().isEmpty()) { throw new InvalidCustomMappingsFileException("An item entry has no name"); } CustomItemData.Builder customItemData = CustomItemData.builder() - .name(name.asText()) + .name(name.getAsString()) .customItemOptions(this.readItemCustomItemOptions(node)); //The next entries are optional if (node.has("display_name")) { - customItemData.displayName(node.get("display_name").asText()); + customItemData.displayName(node.get("display_name").getAsString()); } if (node.has("icon")) { - customItemData.icon(node.get("icon").asText()); + customItemData.icon(node.get("icon").getAsString()); } if (node.has("creative_category")) { - customItemData.creativeCategory(node.get("creative_category").asInt()); + customItemData.creativeCategory(node.get("creative_category").getAsInt()); } if (node.has("creative_group")) { - customItemData.creativeGroup(node.get("creative_group").asText()); + customItemData.creativeGroup(node.get("creative_group").getAsString()); } if (node.has("allow_offhand")) { - customItemData.allowOffhand(node.get("allow_offhand").asBoolean()); + customItemData.allowOffhand(node.get("allow_offhand").getAsBoolean()); } if (node.has("display_handheld")) { - customItemData.displayHandheld(node.get("display_handheld").asBoolean()); + customItemData.displayHandheld(node.get("display_handheld").getAsBoolean()); } if (node.has("texture_size")) { - customItemData.textureSize(node.get("texture_size").asInt()); + customItemData.textureSize(node.get("texture_size").getAsInt()); } if (node.has("render_offsets")) { - JsonNode tmpNode = node.get("render_offsets"); + JsonObject tmpNode = node.getAsJsonObject("render_offsets"); - customItemData.renderOffsets(fromJsonNode(tmpNode)); + customItemData.renderOffsets(fromJsonObject(tmpNode)); } - if (node.get("tags") instanceof ArrayNode tags) { + if (node.get("tags") instanceof JsonArray tags) { Set tagsSet = new ObjectOpenHashSet<>(); - tags.forEach(tag -> tagsSet.add(tag.asText())); + tags.forEach(tag -> tagsSet.add(tag.getAsString())); customItemData.tags(tagsSet); } @@ -220,26 +231,26 @@ public class MappingsReader_v1 extends MappingsReader { * Read a block mapping entry from a JSON node and Java identifier * * @param identifier The Java identifier of the block - * @param node The {@link JsonNode} containing the block mapping entry + * @param node The {@link JsonObject} containing the block mapping entry * @return The {@link CustomBlockMapping} record to be read by {@link org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator} * @throws InvalidCustomMappingsFileException If the JSON node is invalid */ @Override - public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { - if (node == null || !node.isObject()) { + public CustomBlockMapping readBlockMappingEntry(String identifier, JsonObject node) throws InvalidCustomMappingsFileException { + if (node == null) { throw new InvalidCustomMappingsFileException("Invalid block mappings entry:" + node); } - String name = node.get("name").asText(); + String name = node.get("name").getAsString(); if (name == null || name.isEmpty()) { throw new InvalidCustomMappingsFileException("A block entry has no name"); } - boolean includedInCreativeInventory = node.has("included_in_creative_inventory") && node.get("included_in_creative_inventory").asBoolean(); + boolean includedInCreativeInventory = node.has("included_in_creative_inventory") && node.get("included_in_creative_inventory").getAsBoolean(); CreativeCategory creativeCategory = CreativeCategory.NONE; if (node.has("creative_category")) { - String categoryName = node.get("creative_category").asText(); + String categoryName = node.get("creative_category").getAsString(); try { creativeCategory = CreativeCategory.valueOf(categoryName.toUpperCase()); } catch (IllegalArgumentException e) { @@ -249,11 +260,11 @@ public class MappingsReader_v1 extends MappingsReader { String creativeGroup = ""; if (node.has("creative_group")) { - creativeGroup = node.get("creative_group").asText(); + creativeGroup = node.get("creative_group").getAsString(); } // If this is true, we will only register the states the user has specified rather than all the possible block states - boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean(); + boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").getAsBoolean(); // Create the data for the overall block CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder() @@ -273,12 +284,9 @@ public class MappingsReader_v1 extends MappingsReader { Map componentsMap = new LinkedHashMap<>(); - JsonNode stateOverrides = node.get("state_overrides"); - if (stateOverrides != null && stateOverrides.isObject()) { + if (node.get("state_overrides") instanceof JsonObject stateOverrides) { // Load components for specific Java block states - Iterator> fields = stateOverrides.fields(); - while (fields.hasNext()) { - Map.Entry overrideEntry = fields.next(); + for (Map.Entry overrideEntry : stateOverrides.entrySet()) { String state = identifier + "[" + overrideEntry.getKey() + "]"; if (!BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().containsKey(state)) { throw new InvalidCustomMappingsFileException("Unknown Java block state: " + state + " for state_overrides."); @@ -358,12 +366,12 @@ public class MappingsReader_v1 extends MappingsReader { /** * Creates a {@link CustomBlockComponents} object for the passed state override or base block node, Java block state identifier, and custom block name * - * @param node the state override or base block {@link JsonNode} + * @param element the state override or base block {@link JsonObject} * @param stateKey the Java block state identifier * @param name the name of the custom block * @return the {@link CustomBlockComponents} object */ - private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode node, String stateKey, String name) { + private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonElement element, String stateKey, String name) { // This is needed to find the correct selection box for the given block int id = BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1); BoxComponent boxComponent = createBoxComponent(id); @@ -372,7 +380,7 @@ public class MappingsReader_v1 extends MappingsReader { .collisionBox(boxComponent) .selectionBox(boxComponent); - if (node == null) { + if (!(element instanceof JsonObject node)) { // No other components were defined return new CustomBlockComponentsMapping(builder.build(), extendedBoxComponent); } @@ -394,28 +402,28 @@ public class MappingsReader_v1 extends MappingsReader { // We set this to max value by default so that we may dictate the correct destroy time ourselves float destructibleByMining = Float.MAX_VALUE; if (node.has("destructible_by_mining")) { - destructibleByMining = node.get("destructible_by_mining").floatValue(); + destructibleByMining = node.get("destructible_by_mining").getAsFloat(); } builder.destructibleByMining(destructibleByMining); if (node.has("geometry")) { - if (node.get("geometry").isTextual()) { + if (node.get("geometry").isJsonPrimitive()) { builder.geometry(new GeyserGeometryComponent.Builder() - .identifier(node.get("geometry").asText()) + .identifier(node.get("geometry").getAsString()) .build()); } else { - JsonNode geometry = node.get("geometry"); + JsonObject geometry = node.getAsJsonObject("geometry"); GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder(); if (geometry.has("identifier")) { - geometryBuilder.identifier(geometry.get("identifier").asText()); + geometryBuilder.identifier(geometry.get("identifier").getAsString()); } if (geometry.has("bone_visibility")) { - JsonNode boneVisibility = geometry.get("bone_visibility"); - if (boneVisibility.isObject()) { + if (geometry.get("bone_visibility") instanceof JsonObject boneVisibility) { Map boneVisibilityMap = new Object2ObjectOpenHashMap<>(); - boneVisibility.fields().forEachRemaining(entry -> { + boneVisibility.entrySet().forEach(entry -> { String key = entry.getKey(); - String value = entry.getValue().isBoolean() ? (entry.getValue().asBoolean() ? "1" : "0") : entry.getValue().asText(); + String value = entry.getValue() instanceof JsonPrimitive primitive && primitive.isBoolean() + ? (entry.getValue().getAsBoolean() ? "1" : "0") : entry.getValue().getAsString(); boneVisibilityMap.put(key, value); }); geometryBuilder.boneVisibility(boneVisibilityMap); @@ -427,30 +435,30 @@ public class MappingsReader_v1 extends MappingsReader { String displayName = name; if (node.has("display_name")) { - displayName = node.get("display_name").asText(); + displayName = node.get("display_name").getAsString(); } builder.displayName(displayName); if (node.has("friction")) { - builder.friction(node.get("friction").floatValue()); + builder.friction(node.get("friction").getAsFloat()); } if (node.has("light_emission")) { - builder.lightEmission(node.get("light_emission").asInt()); + builder.lightEmission(node.get("light_emission").getAsInt()); } if (node.has("light_dampening")) { - builder.lightDampening(node.get("light_dampening").asInt()); + builder.lightDampening(node.get("light_dampening").getAsInt()); } boolean placeAir = true; if (node.has("place_air")) { - placeAir = node.get("place_air").asBoolean(); + placeAir = node.get("place_air").getAsBoolean(); } builder.placeAir(placeAir); if (node.has("transformation")) { - JsonNode transformation = node.get("transformation"); + JsonObject transformation = node.getAsJsonObject("transformation"); int rotationX = 0; int rotationY = 0; @@ -463,22 +471,22 @@ public class MappingsReader_v1 extends MappingsReader { float transformZ = 0; if (transformation.has("rotation")) { - JsonNode rotation = transformation.get("rotation"); - rotationX = rotation.get(0).asInt(); - rotationY = rotation.get(1).asInt(); - rotationZ = rotation.get(2).asInt(); + JsonArray rotation = transformation.getAsJsonArray("rotation"); + rotationX = rotation.get(0).getAsInt(); + rotationY = rotation.get(1).getAsInt(); + rotationZ = rotation.get(2).getAsInt(); } if (transformation.has("scale")) { - JsonNode scale = transformation.get("scale"); - scaleX = scale.get(0).floatValue(); - scaleY = scale.get(1).floatValue(); - scaleZ = scale.get(2).floatValue(); + JsonArray scale = transformation.getAsJsonArray("scale"); + scaleX = scale.get(0).getAsFloat(); + scaleY = scale.get(1).getAsFloat(); + scaleZ = scale.get(2).getAsFloat(); } if (transformation.has("translation")) { - JsonNode translation = transformation.get("translation"); - transformX = translation.get(0).floatValue(); - transformY = translation.get(1).floatValue(); - transformZ = translation.get(2).floatValue(); + JsonArray translation = transformation.getAsJsonArray("translation"); + transformX = translation.get(0).getAsFloat(); + transformY = translation.get(1).getAsFloat(); + transformZ = translation.get(2).getAsFloat(); } builder.transformation(new TransformationComponent(rotationX, rotationY, rotationZ, scaleX, scaleY, scaleZ, transformX, transformY, transformZ)); } @@ -490,12 +498,10 @@ public class MappingsReader_v1 extends MappingsReader { } if (node.has("material_instances")) { - JsonNode materialInstances = node.get("material_instances"); - if (materialInstances.isObject()) { - materialInstances.fields().forEachRemaining(entry -> { + if (node.get("material_instances") instanceof JsonObject materialInstances) { + materialInstances.entrySet().forEach(entry -> { String key = entry.getKey(); - JsonNode value = entry.getValue(); - if (value.isObject()) { + if (entry.getValue() instanceof JsonObject value) { MaterialInstance materialInstance = createMaterialInstanceComponent(value); builder.materialInstance(key, materialInstance); } @@ -503,16 +509,10 @@ public class MappingsReader_v1 extends MappingsReader { } } - if (node.has("placement_filter")) { - JsonNode placementFilter = node.get("placement_filter"); - if (placementFilter.isObject()) { - if (placementFilter.has("conditions")) { - JsonNode conditions = placementFilter.get("conditions"); - if (conditions.isArray()) { - List filter = createPlacementFilterComponent(conditions); - builder.placementFilter(filter); - } - } + if (node.get("placement_filter") instanceof JsonObject placementFilter) { + if (placementFilter.get("conditions") instanceof JsonArray conditions) { + List filter = createPlacementFilterComponent(conditions); + builder.placementFilter(filter); } } @@ -521,9 +521,9 @@ public class MappingsReader_v1 extends MappingsReader { // Ideally we could programmatically extract the tags here https://wiki.bedrock.dev/blocks/block-tags.html // This would let us automatically apply the correct vanilla tags to blocks // However, its worth noting that vanilla tools do not currently honor these tags anyway - if (node.get("tags") instanceof ArrayNode tags) { + if (node.get("tags") instanceof JsonArray tags) { Set tagsSet = new ObjectOpenHashSet<>(); - tags.forEach(tag -> tagsSet.add(tag.asText())); + tags.forEach(tag -> tagsSet.add(tag.getAsString())); builder.tags(tagsSet); } @@ -613,21 +613,21 @@ public class MappingsReader_v1 extends MappingsReader { /** * Creates a {@link BoxComponent} from a JSON Node * - * @param node the JSON node + * @param element the JSON node * @return the {@link BoxComponent} */ - private @Nullable BoxComponent createBoxComponent(JsonNode node) { - if (node != null && node.isObject()) { + private @Nullable BoxComponent createBoxComponent(JsonElement element) { + if (element instanceof JsonObject node) { if (node.has("origin") && node.has("size")) { - JsonNode origin = node.get("origin"); - float originX = origin.get(0).floatValue(); - float originY = origin.get(1).floatValue(); - float originZ = origin.get(2).floatValue(); + JsonArray origin = node.getAsJsonArray("origin"); + float originX = origin.get(0).getAsFloat(); + float originY = origin.get(1).getAsFloat(); + float originZ = origin.get(2).getAsFloat(); - JsonNode size = node.get("size"); - float sizeX = size.get(0).floatValue(); - float sizeY = size.get(1).floatValue(); - float sizeZ = size.get(2).floatValue(); + JsonArray size = node.getAsJsonArray("size"); + float sizeX = size.get(0).getAsFloat(); + float sizeY = size.get(1).getAsFloat(); + float sizeZ = size.get(2).getAsFloat(); return new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ); } @@ -642,26 +642,26 @@ public class MappingsReader_v1 extends MappingsReader { * @param node the material instance node * @return the {@link MaterialInstance} */ - private MaterialInstance createMaterialInstanceComponent(JsonNode node) { + private MaterialInstance createMaterialInstanceComponent(JsonObject node) { // Set default values, and use what the user provides if they have provided something String texture = null; if (node.has("texture")) { - texture = node.get("texture").asText(); + texture = node.get("texture").getAsString(); } String renderMethod = "opaque"; if (node.has("render_method")) { - renderMethod = node.get("render_method").asText(); + renderMethod = node.get("render_method").getAsString(); } boolean faceDimming = true; if (node.has("face_dimming")) { - faceDimming = node.get("face_dimming").asBoolean(); + faceDimming = node.get("face_dimming").getAsBoolean(); } boolean ambientOcclusion = true; if (node.has("ambient_occlusion")) { - ambientOcclusion = node.get("ambient_occlusion").asBoolean(); + ambientOcclusion = node.get("ambient_occlusion").getAsBoolean(); } return new GeyserMaterialInstance.Builder() @@ -678,32 +678,33 @@ public class MappingsReader_v1 extends MappingsReader { * @param node the conditions node * @return the list of {@link PlacementConditions} */ - private List createPlacementFilterComponent(JsonNode node) { + private List createPlacementFilterComponent(JsonArray node) { List conditions = new ArrayList<>(); // The structure of the placement filter component is the most complex of the current components // Each condition effectively separated into two arrays: one of allowed faces, and one of blocks/block Molang queries - node.forEach(condition -> { + node.forEach(json -> { + if (!(json instanceof JsonObject condition)) { + return; + } Set faces = EnumSet.noneOf(Face.class); if (condition.has("allowed_faces")) { - JsonNode allowedFaces = condition.get("allowed_faces"); - if (allowedFaces.isArray()) { - allowedFaces.forEach(face -> faces.add(Face.valueOf(face.asText().toUpperCase()))); + if (condition.get("allowed_faces") instanceof JsonArray allowedFaces) { + allowedFaces.forEach(face -> faces.add(Face.valueOf(face.getAsString().toUpperCase()))); } } LinkedHashMap blockFilters = new LinkedHashMap<>(); if (condition.has("block_filter")) { - JsonNode blockFilter = condition.get("block_filter"); - if (blockFilter.isArray()) { + if (condition.get("block_filter") instanceof JsonArray blockFilter) { blockFilter.forEach(filter -> { - if (filter.isObject()) { - if (filter.has("tags")) { - JsonNode tags = filter.get("tags"); - blockFilters.put(tags.asText(), BlockFilterType.TAG); + if (filter instanceof JsonObject jsonObject) { + if (jsonObject.has("tags")) { + JsonElement tags = jsonObject.get("tags"); + blockFilters.put(tags.getAsString(), BlockFilterType.TAG); } - } else if (filter.isTextual()) { - blockFilters.put(filter.asText(), BlockFilterType.BLOCK); + } else if (filter instanceof JsonPrimitive primitive && primitive.isString()) { + blockFilters.put(filter.getAsString(), BlockFilterType.BLOCK); } }); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index f287a4a7a..f1a1bcb99 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -25,11 +25,12 @@ package org.geysermc.geyser.registry.populator; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Interner; import com.google.common.collect.Interners; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -65,6 +66,7 @@ import org.geysermc.geyser.registry.populator.conversion.Conversion827_819; import org.geysermc.geyser.registry.populator.conversion.Conversion844_827; import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.GeyserBedrockBlock; +import org.geysermc.geyser.util.JsonUtils; import java.io.DataInputStream; import java.io.InputStream; @@ -441,21 +443,21 @@ public final class BlockRegistryPopulator { BLOCKS_NBT = blocksNbt; JAVA_BLOCKS_SIZE = blocksNbt.size(); - JsonNode blockInteractionsJson; + JsonObject blockInteractionsJson; try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/interactions.json")) { - blockInteractionsJson = GeyserImpl.JSON_MAPPER.readTree(stream); + blockInteractionsJson = JsonUtils.fromJson(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java block interaction mappings", e); } - BlockRegistries.INTERACTIVE.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("always_consumes"))); - BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("requires_may_build"))); + BlockRegistries.INTERACTIVE.set(toBlockStateSet(blockInteractionsJson.getAsJsonArray("always_consumes"))); + BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet(blockInteractionsJson.getAsJsonArray("requires_may_build"))); } - private static BitSet toBlockStateSet(ArrayNode node) { + private static BitSet toBlockStateSet(JsonArray node) { BitSet blockStateSet = new BitSet(node.size()); - for (JsonNode javaIdentifier : node) { - blockStateSet.set(BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.textValue())); + for (JsonElement javaIdentifier : node) { + blockStateSet.set(BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.getAsString())); } return blockStateSet; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java index cbc0515f9..faf4b652e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java @@ -25,7 +25,9 @@ package org.geysermc.geyser.registry.populator; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -40,6 +42,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.GeyserBedrockBlock; +import org.geysermc.geyser.util.JsonUtils; import org.geysermc.geyser.registry.type.GeyserMappingItem; import java.io.ByteArrayInputStream; @@ -62,20 +65,21 @@ public class CreativeItemRegistryPopulator { static List readCreativeItemGroups(ItemRegistryPopulator.PaletteVersion palette, List creativeItemData) { GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); - JsonNode creativeItemEntries; + JsonArray creativeItemEntries; try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/creative_items.%s.json", palette.version()))) { - creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("groups"); + creativeItemEntries = JsonUtils.fromJson(stream).getAsJsonArray("groups"); } catch (Exception e) { throw new AssertionError("Unable to load creative item groups", e); } List creativeItemGroups = new ArrayList<>(); - for (JsonNode creativeItemEntry : creativeItemEntries) { - CreativeItemCategory category = CreativeItemCategory.valueOf(creativeItemEntry.get("category").asText().toUpperCase(Locale.ROOT)); - String name = creativeItemEntry.get("name").asText(); + for (JsonElement creativeItemEntry : creativeItemEntries) { + JsonObject creativeItemEntryObject = creativeItemEntry.getAsJsonObject(); + CreativeItemCategory category = CreativeItemCategory.valueOf(creativeItemEntryObject.get("category").getAsString().toUpperCase(Locale.ROOT)); + String name = creativeItemEntryObject.get("name").getAsString(); - JsonNode icon = creativeItemEntry.get("icon"); - String identifier = icon.get("id").asText(); + JsonElement icon = creativeItemEntryObject.get("icon"); + String identifier = icon.getAsJsonObject().get("id").getAsString(); ItemData itemData; if (identifier.equals("minecraft:air")) { @@ -98,32 +102,33 @@ public class CreativeItemRegistryPopulator { GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); // Load creative items - JsonNode creativeItemEntries; + JsonArray creativeItemEntries; try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/creative_items.%s.json", palette.version()))) { - creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items"); + creativeItemEntries = JsonUtils.fromJson(stream).getAsJsonArray("items"); } catch (Exception e) { throw new AssertionError("Unable to load creative items", e); } BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion()); - for (JsonNode itemNode : creativeItemEntries) { - ItemData.Builder itemBuilder = createItemData(itemNode, items, blockMappings, definitions); + for (JsonElement itemNode : creativeItemEntries) { + ItemData.Builder itemBuilder = createItemData((JsonObject) itemNode, items, blockMappings, definitions); if (itemBuilder == null) { continue; } - int groupId = itemNode.get("groupId") != null ? itemNode.get("groupId").asInt() : 0; + var groupIdElement = itemNode.getAsJsonObject().get("groupId"); + int groupId = groupIdElement != null ? groupIdElement.getAsInt() : 0; itemConsumer.accept(itemBuilder, groupId); } } - private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, Map items, BlockMappings blockMappings, Map definitions) { + private static ItemData.@Nullable Builder createItemData(JsonObject itemNode, Map items, BlockMappings blockMappings, Map definitions) { int count = 1; int damage = 0; NbtMap tag = null; - String identifier = itemNode.get("id").textValue(); + String identifier = itemNode.get("id").getAsString(); for (BiPredicate predicate : JAVA_ONLY_ITEM_FILTER) { if (predicate.test(identifier, damage)) { return null; @@ -150,20 +155,20 @@ public class CreativeItemRegistryPopulator { // } } - JsonNode damageNode = itemNode.get("damage"); + JsonElement damageNode = itemNode.get("damage"); if (damageNode != null) { - damage = damageNode.asInt(); + damage = damageNode.getAsInt(); } - JsonNode countNode = itemNode.get("count"); + JsonElement countNode = itemNode.get("count"); if (countNode != null) { - count = countNode.asInt(); + count = countNode.getAsInt(); } GeyserBedrockBlock blockDefinition = null; - JsonNode blockStateNode; + JsonElement blockStateNode; if ((blockStateNode = itemNode.get("block_state_b64")) != null) { - byte[] bytes = Base64.getDecoder().decode(blockStateNode.asText()); + byte[] bytes = Base64.getDecoder().decode(blockStateNode.getAsString()); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); try { NbtMap stateTag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); @@ -182,9 +187,9 @@ public class CreativeItemRegistryPopulator { } } - JsonNode nbtNode = itemNode.get("nbt_b64"); + JsonElement nbtNode = itemNode.get("nbt_b64"); if (nbtNode != null) { - byte[] bytes = Base64.getDecoder().decode(nbtNode.asText()); + byte[] bytes = Base64.getDecoder().decode(nbtNode.getAsString()); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); try { tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); 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 284c89f03..06a132820 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 @@ -104,7 +104,7 @@ public class CustomBlockRegistryPopulator { * @param stage the stage to populate */ public static void populate(Stage stage) { - if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { + if (!GeyserImpl.getInstance().config().gameplay().enableCustomContent()) { return; } 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 71d6a5319..775ab3a47 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 @@ -59,7 +59,7 @@ public class CustomSkullRegistryPopulator { SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap()); - if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { + if (!GeyserImpl.getInstance().config().gameplay().enableCustomContent()) { return; } 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 5fb3590e2..862e47c87 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 @@ -25,9 +25,9 @@ package org.geysermc.geyser.registry.populator; -import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; +import com.google.gson.reflect.TypeToken; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -82,8 +82,10 @@ import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.geyser.registry.type.PaletteItem; +import org.geysermc.geyser.util.JsonUtils; import java.io.InputStream; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -201,17 +203,17 @@ public class ItemRegistryPopulator { GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); - TypeReference> mappingItemsType = new TypeReference<>() { }; + Type mappingItemsType = new TypeToken>() { }.getType(); Map items; try (InputStream stream = bootstrap.getResourceOrThrow("mappings/items.json")) { // Load item mappings from Java Edition to Bedrock Edition - items = GeyserImpl.JSON_MAPPER.readValue(stream, mappingItemsType); + items = JsonUtils.fromJson(stream, mappingItemsType); } catch (Exception e) { throw new AssertionError("Unable to load Java runtime item IDs", e); } - boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); + boolean customItemsAllowed = GeyserImpl.getInstance().config().gameplay().enableCustomContent(); // List values here is important compared to HashSet - we need to preserve the order of what's given to us // (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom @@ -228,11 +230,11 @@ public class ItemRegistryPopulator { /* Load item palette */ for (PaletteVersion palette : paletteVersions) { - TypeReference> paletteEntriesType = new TypeReference<>() {}; + Type paletteEntriesType = new TypeToken>() { }.getType(); List itemEntries; try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/runtime_item_states.%s.json", palette.version()))) { - itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType); + itemEntries = JsonUtils.fromJson(stream, paletteEntriesType); } catch (Exception e) { throw new AssertionError("Unable to load Bedrock runtime item IDs", e); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java index ae682e8b5..903e16a96 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.type; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -43,13 +43,13 @@ import lombok.With; @NoArgsConstructor @AllArgsConstructor public class GeyserMappingItem { - @JsonProperty("bedrock_identifier") String bedrockIdentifier; - @JsonProperty("bedrock_data") int bedrockData; + @SerializedName("bedrock_identifier") String bedrockIdentifier; + @SerializedName("bedrock_data") int bedrockData; Integer firstBlockRuntimeId; Integer lastBlockRuntimeId; - @JsonProperty("tool_type") String toolType; - @JsonProperty("armor_type") String armorType; - @JsonProperty("protection_value") int protectionValue; - @JsonProperty("is_edible") boolean edible = false; - @JsonProperty("is_entity_placer") boolean entityPlacer = false; + @SerializedName("tool_type") String toolType; + @SerializedName("armor_type") String armorType; + @SerializedName("protection_value") int protectionValue; + @SerializedName("is_edible") boolean edible = false; + @SerializedName("is_entity_placer") boolean entityPlacer = false; } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index 10d8aa525..4f5678a51 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -80,7 +80,9 @@ import static org.geysermc.geyser.scoreboard.UpdateType.REMOVE; */ public final class Scoreboard { private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true")); - private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean(System.getProperty("Geyser.AddTeamSuggestions", "true")); + private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean( + System.getProperty("Geyser.AddTeamSuggestions", String.valueOf(GeyserImpl.getInstance().config().advanced().addTeamSuggestions())) + ); private final GeyserSession session; private final GeyserLogger logger; 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 18a4bce39..19c5e64f0 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.advanced().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 5d4cd7816..55a2ffcb2 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.session; -import com.google.gson.Gson; import com.google.gson.JsonObject; import io.netty.channel.Channel; import io.netty.channel.EventLoop; @@ -125,8 +124,7 @@ import org.geysermc.geyser.api.skin.SkinData; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.GeyserEntityData; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; @@ -256,8 +254,6 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter public class GeyserSession implements GeyserConnection, GeyserCommandSource { - private static final Gson GSON = new Gson(); - private final GeyserImpl geyser; private final UpstreamSession upstream; private DownstreamSession downstream; @@ -715,7 +711,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { /** * A cache of IDs from ClientboundKeepAlivePackets that have been sent to the Bedrock client, but haven't been returned to the server. - * Only used if {@link GeyserConfiguration#isForwardPlayerPing()} is enabled. + * Only used if {@link GeyserConfig.GameplayConfig#forwardPlayerPing()} is enabled. */ private final Queue keepAliveCache = new ConcurrentLinkedQueue<>(); @@ -753,6 +749,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Accessors(fluent = true) private boolean hasAcceptedCodeOfConduct = false; + @Accessors(fluent = true) + @Setter + private boolean integratedPackActive = false; + private final Set inputLocksSet = EnumSet.noneOf(InputLocksFlag.class); private boolean inputLockDirty; @@ -799,12 +799,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.spawned = false; this.loggedIn = false; - if (geyser.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) { - this.emotes = new HashSet<>(); - geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes())); - } else { - this.emotes = null; - } + this.emotes = new HashSet<>(); + geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes())); this.remoteServer = geyser.defaultRemoteServer(); } @@ -870,7 +866,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { upstream.sendPacket(playStatusPacket); SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket(); - setCommandsEnabledPacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled()); + setCommandsEnabledPacket.setCommandsEnabled(!geyser.config().gameplay().xboxAchievementsEnabled()); upstream.sendPacket(setCommandsEnabledPacket); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); @@ -929,7 +925,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { MinecraftProfile mcProfile; MinecraftToken mcToken; try { - JsonObject parsedAuthChain = GSON.fromJson(authChain, JsonObject.class); + JsonObject parsedAuthChain = GeyserImpl.GSON.fromJson(authChain, JsonObject.class); if (parsedAuthChain.has("mcProfile")) { // Old Minecraft v4 auth chain parsedAuthChain = MinecraftAuth4To5Migrator.migrateJavaSave(parsedAuthChain, GeyserImpl.OAUTH_CONFIG); } @@ -946,7 +942,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { new GameProfile(mcProfile.getId(), mcProfile.getName()), mcToken.getToken() ); - geyser.saveAuthChain(bedrockUsername(), GSON.toJson(JavaAuthManager.toJson(authManager))); + geyser.saveAuthChain(bedrockUsername(), GeyserImpl.GSON.toJson(JavaAuthManager.toJson(authManager))); return Boolean.TRUE; }).whenComplete((successful, ex) -> { if (this.closed) { @@ -1039,7 +1035,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } // Save our auth chain for later use - geyser.saveAuthChain(bedrockUsername(), GSON.toJson(JavaAuthManager.toJson(result))); + geyser.saveAuthChain(bedrockUsername(), GeyserImpl.GSON.toJson(JavaAuthManager.toJson(result))); return true; }).getNow(false); } @@ -1091,10 +1087,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Disable automatic creation of a new TcpClientSession when transferring - we don't use that functionality. this.downstream.getSession().setFlag(MinecraftConstants.FOLLOW_TRANSFERS, false); - if (geyser.getConfig().getRemote().isUseProxyProtocol()) { + if (geyser.config().advanced().java().useHaproxyProtocol()) { downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); } - if (geyser.getConfig().isForwardPlayerPing()) { + if (geyser.config().gameplay().forwardPlayerPing()) { // Let Geyser handle sending the keep alive downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); } @@ -1140,7 +1136,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } else { // Downstream's disconnect will fire an event that prints a log message // Otherwise, we print a message here - String address = geyser.getConfig().isLogPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : ""; + String address = geyser.config().logPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : ""; geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, MessageTranslator.convertMessage(reason))); } @@ -1740,7 +1736,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); startGamePacket.setDefaultSpawn(Vector3i.ZERO); - startGamePacket.setAchievementsDisabled(!geyser.getConfig().isXboxAchievementsEnabled()); + startGamePacket.setAchievementsDisabled(!geyser.config().gameplay().xboxAchievementsEnabled()); startGamePacket.setCurrentTick(-1); startGamePacket.setEduEditionOffers(0); startGamePacket.setEduFeaturesEnabled(false); @@ -1750,7 +1746,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setBroadcastingToLan(true); startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); - startGamePacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled()); + startGamePacket.setCommandsEnabled(!geyser.config().gameplay().xboxAchievementsEnabled()); startGamePacket.setTexturePacksRequired(false); startGamePacket.setBonusChestEnabled(false); startGamePacket.setStartingWithMap(false); @@ -1768,7 +1764,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setEducationProductionId(""); startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty()); - String serverName = geyser.getConfig().getBedrock().serverName(); + String serverName = geyser.config().gameplay().serverName(); startGamePacket.setLevelId(serverName); startGamePacket.setLevelName(serverName); @@ -1914,7 +1910,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public void sendDownstreamPacket(Packet packet, ProtocolState intendedState) { // protocol can be null when we're not yet logged in (online auth) if (protocol == null) { - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { geyser.getLogger().debug("Tried to send downstream packet with no downstream session!"); Thread.dumpStack(); } @@ -1940,7 +1936,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (channel == null) { // Channel is only null before the connection has initialized geyser.getLogger().warning("Tried to send a packet to the Java server too early!"); - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { Thread.dumpStack(); } return; @@ -2431,7 +2427,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private void softEnumPacket(String name, SoftEnumUpdateType type, String enums) { // There is no need to send command enums if command suggestions are disabled - if (!this.geyser.getConfig().isCommandSuggestions()) { + if (!this.geyser.config().gameplay().commandSuggestions()) { return; } UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java index 73f8b3989..80b4978d1 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java @@ -110,7 +110,7 @@ public class GeyserSessionAdapter extends SessionAdapter { } String address; - if (geyser.getConfig().getRemote().isForwardHost()) { + if (geyser.config().java().forwardHostname()) { address = session.joinAddress(); } else { address = intentionPacket.getHostname(); @@ -172,7 +172,7 @@ public class GeyserSessionAdapter extends SessionAdapter { customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale); // Explain that they may be looking for Floodgate. geyser.getLogger().warning(GeyserLocale.getLocaleStringLog( - geyser.getPlatformType() == PlatformType.STANDALONE ? + geyser.platformType() == PlatformType.STANDALONE ? "geyser.network.remote.floodgate_explanation_standalone" : "geyser.network.remote.floodgate_explanation_plugin", Constants.FLOODGATE_DOWNLOAD_LOCATION @@ -180,7 +180,7 @@ public class GeyserSessionAdapter extends SessionAdapter { } else { // Likely that Floodgate is not configured correctly. customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.floodgate_login_error", locale); - if (geyser.getPlatformType() == PlatformType.STANDALONE) { + if (geyser.platformType() == PlatformType.STANDALONE) { geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.remote.floodgate_login_error_standalone")); } } @@ -203,7 +203,7 @@ public class GeyserSessionAdapter extends SessionAdapter { } else { GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause); } - if (geyser.getConfig().isDebugMode()) { + if (geyser.config().debugMode()) { cause.printStackTrace(); } } @@ -233,7 +233,7 @@ public class GeyserSessionAdapter extends SessionAdapter { (event.getPacketClass() != null ? "(" + event.getPacketClass().getSimpleName() + ") " : "") + event.getCause().getMessage()) ); - if (geyser.getConfig().isDebugMode()) + if (geyser.config().debugMode()) event.getCause().printStackTrace(); event.setSuppress(true); } diff --git a/core/src/main/java/org/geysermc/geyser/session/SessionDisconnectListener.java b/core/src/main/java/org/geysermc/geyser/session/SessionDisconnectListener.java index da1ed75f4..071038e30 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionDisconnectListener.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionDisconnectListener.java @@ -55,7 +55,7 @@ public final class SessionDisconnectListener { String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale()); if (testForOutdatedServer(disconnectReason)) { String locale = session.locale(); - PlatformType platform = session.getGeyser().getPlatformType(); + PlatformType platform = session.getGeyser().platformType(); String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY || platform == PlatformType.VIAPROXY) ? "geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server"; event.disconnectReason(GeyserLocale.getPlayerLocaleString(outdatedType, locale, GameProtocol.getJavaVersions().get(0)) + '\n' 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/BundleCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/BundleCache.java index d5405d1de..cb9677bff 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/BundleCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/BundleCache.java @@ -68,7 +68,7 @@ public final class BundleCache { if (itemStack.is(session, ItemTag.BUNDLES)) { if (itemStack.getBundleData() != null) { session.getGeyser().getLogger().warning("Stack has bundle data already! It should not!"); - if (session.getGeyser().getConfig().isDebugMode()) { + if (session.getGeyser().getLogger().isDebug()) { session.getGeyser().getLogger().debug("Player: " + session.javaUsername()); session.getGeyser().getLogger().debug("Stack: " + itemStack); } 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..5a3ad7bef 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 @@ -27,7 +27,7 @@ package org.geysermc.geyser.session.cache; import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserConfig; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.CooldownUtils; @@ -54,15 +54,16 @@ public class PreferencesCache { private boolean prefersCustomSkulls; /** - * Which CooldownType the client prefers. Initially set to {@link CooldownUtils#getDefaultShowCooldown()}. + * Which CooldownType the client prefers. Initially set to the config default. */ @Setter - private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown(); + private CooldownUtils.CooldownType cooldownPreference; public PreferencesCache(GeyserSession session) { this.session = session; - prefersCustomSkulls = session.getGeyser().getConfig().isAllowCustomSkulls(); + prefersCustomSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0; + cooldownPreference = session.getGeyser().config().gameplay().showCooldown(); } /** @@ -71,10 +72,10 @@ public class PreferencesCache { * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
*
* {@link GeyserSession#isReducedDebugInfo()} is enabled - * {@link GeyserConfiguration#isShowCoordinates()} is disabled + * {@link GeyserConfig.GameplayConfig#showCoordinates()} is disabled */ public void updateShowCoordinates() { - allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().getConfig().isShowCoordinates(); + allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().config().gameplay().showCoordinates(); session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); } @@ -82,6 +83,6 @@ public class PreferencesCache { * @return true if the session prefers custom skulls, and the config allows them. */ public boolean showCustomSkulls() { - return prefersCustomSkulls && session.getGeyser().getConfig().isAllowCustomSkulls(); + return prefersCustomSkulls && session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0; } } 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 ac314a458..b0e819408 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 @@ -67,11 +67,11 @@ public class SkullCache { public SkullCache(GeyserSession session) { this.session = session; - this.maxVisibleSkulls = session.getGeyser().getConfig().getMaxVisibleCustomSkulls(); + this.maxVisibleSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls(); this.cullingEnabled = this.maxVisibleSkulls != -1; // Normal skulls are not rendered beyond 64 blocks - int distance = Math.min(session.getGeyser().getConfig().getCustomSkullRenderDistance(), 64); + int distance = Math.min(session.getGeyser().config().gameplay().customSkullRenderDistance(), 64); this.skullRenderDistanceSquared = distance * distance; } @@ -99,7 +99,7 @@ public class SkullCache { } } catch (IOException e) { session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty); - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } } 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 68c5d7b74..cbc191101 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); @@ -198,24 +194,17 @@ public final class FloodgateSkinUploader { return; } - ObjectNode node = JACKSON.createObjectNode(); + JsonObject node = new JsonObject(); if (chainData != null) { - ArrayNode chainDataNode = JACKSON.createArrayNode(); + JsonArray chainDataNode = new JsonArray(); chainData.forEach(chainDataNode::add); - node.set("chain_data", chainDataNode); + node.add("chain_data", chainDataNode); } else { - node.put("token", token); + node.addProperty("token", token); } - node.put("client_data", clientData); + node.addProperty("client_data", clientData); - // The reason why I don't like Jackson - String jsonString; - try { - jsonString = JACKSON.writeValueAsString(node); - } catch (Exception e) { - logger.error("Failed to upload skin", e); - return; - } + String jsonString = node.toString(); if (client.isOpen()) { client.send(jsonString); 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 dd943e984..48246c4e8 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,10 @@ package org.geysermc.geyser.skin; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.checkerframework.checker.nullness.qual.Nullable; @@ -46,6 +49,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.JsonUtils; import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile; @@ -303,7 +307,7 @@ public class SkinManager { public static void handleBedrockSkin(AvatarEntity 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())); } @@ -317,7 +321,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()); } @@ -359,7 +363,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; @@ -387,7 +391,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(); } } @@ -395,29 +399,25 @@ public class SkinManager { } public static @Nullable GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException { - 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; } @@ -434,11 +434,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 ec88f6e93..b95c973a6 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.skin; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.bytes.ByteArrays; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -56,7 +58,11 @@ import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Objects; import java.util.UUID; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; public class SkinProvider { @@ -124,11 +130,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() { @@ -147,7 +148,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().advanced().cacheImages() > 0) { geyser.getScheduledThread().scheduleAtFixedRate(() -> { File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { @@ -155,7 +156,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().advanced().cacheImages()) * ((long)1000 * 60 * 60 * 24); for (File imageFile : Objects.requireNonNull(cacheFolder.listFiles())) { if (imageFile.lastModified() < System.currentTimeMillis() - expireTime) { //noinspection ResultOfMethodCallIgnored @@ -186,7 +187,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) { @@ -425,7 +426,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().advanced().cacheImages() > 0) { imageFile.getParentFile().mkdirs(); try { ImageIO.write(image, "png", imageFile); @@ -503,15 +504,15 @@ public class SkinProvider { public static CompletableFuture<@Nullable String> requestUsernameFromUUID(UUID uuid) { return CompletableFuture.supplyAsync(() -> { try { - JsonNode node = WebUtils.getJson("https://api.minecraftservices.com/minecraft/profile/lookup/" + shorthandUUID(uuid)); - JsonNode name = node.get("name"); + JsonObject node = WebUtils.getJson("https://api.minecraftservices.com/minecraft/profile/lookup/" + shorthandUUID(uuid)); + JsonElement name = node.get("name"); if (name == null) { GeyserImpl.getInstance().getLogger().debug("No username found in Mojang response for " + uuid); return null; } - return name.asText(); + return name.getAsString(); } catch (Exception e) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } return null; @@ -528,15 +529,15 @@ public class SkinProvider { public static CompletableFuture<@Nullable UUID> requestUUIDFromUsername(String username) { return CompletableFuture.supplyAsync(() -> { try { - JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username); - JsonNode id = node.get("id"); + JsonObject node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username); + JsonElement id = node.get("id"); if (id == null) { GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username); return null; } - return expandUUID(id.asText()); + return expandUUID(id.getAsString()); } catch (Exception e) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { e.printStackTrace(); } return null; @@ -553,16 +554,16 @@ public class SkinProvider { public static CompletableFuture<@Nullable String> requestTexturesFromUUID(UUID uuid) { return CompletableFuture.supplyAsync(() -> { try { - JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + shorthandUUID(uuid)); - JsonNode properties = node.get("properties"); + JsonObject node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + shorthandUUID(uuid)); + JsonArray properties = node.getAsJsonArray("properties"); if (properties == null) { GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid); return null; } - return node.get("properties").get(0).get("value").asText(); + 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; diff --git a/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java index 66b61dbff..ec79fb906 100644 --- a/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java +++ b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java @@ -25,84 +25,67 @@ package org.geysermc.geyser.text; -import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.BeanProperty; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.ContextualSerializer; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.spongepowered.configurate.objectmapping.meta.Processor; +import org.spongepowered.configurate.serialize.SerializationException; -import java.io.IOException; -import java.io.Serial; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Optional; - -public class AsteriskSerializer extends StdSerializer implements ContextualSerializer { - - @Serial - private static final long serialVersionUID = 1L; +import java.lang.reflect.Type; +import java.net.InetAddress; +public class AsteriskSerializer implements JsonSerializer { public static final String[] NON_SENSITIVE_ADDRESSES = {"", "0.0.0.0", "localhost", "127.0.0.1", "auto", "unknown"}; public static boolean showSensitive = false; - @Target({ElementType.FIELD}) - @Retention(RetentionPolicy.RUNTIME) - @JacksonAnnotationsInside - @JsonSerialize(using = AsteriskSerializer.class) - public @interface Asterisk { - String value() default "***"; - /** - * If true, this value will be shown if {@link #showSensitive} is true, or if the IP is determined to not be a public IP - * - * @return true if this should be analyzed and treated as an IP - */ - boolean isIp() default false; - } - - String asterisk; - boolean isIp; - - @SuppressWarnings("unused") // Used by Jackson for Geyser dumps - public AsteriskSerializer() { - super(Object.class); - } - - public AsteriskSerializer(String asterisk, boolean isIp) { - super(Object.class); - this.asterisk = asterisk; - this.isIp = isIp; - } - @Override - public JsonSerializer createContextual(SerializerProvider serializerProvider, BeanProperty property) { - Optional anno = Optional.ofNullable(property) - .map(prop -> prop.getAnnotation(Asterisk.class)); - - return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::isIp).orElse(false)); - } - - @Override - public void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) throws IOException { - if (isIp && (showSensitive || !isSensitiveIp((String) obj))) { - gen.writeObject(obj); - return; + public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) { + if (showSensitive || !isSensitiveIp(src)) { + return new JsonPrimitive(src); } - gen.writeString(asterisk); + return new JsonPrimitive("***"); } - private boolean isSensitiveIp(String ip) { + private static boolean isSensitiveIp(String ip) { for (String address : NON_SENSITIVE_ADDRESSES) { if (address.equalsIgnoreCase(ip)) { return false; } } + + try { + InetAddress address = InetAddress.getByName(ip); + if (address.isSiteLocalAddress() || address.isLoopbackAddress()) { + return false; + } + } catch (Exception e) { + // Ignore + } + return true; } + + public static Processor.Factory CONFIGURATE_SERIALIZER = (data, fieldType) -> (value, destination) -> { + if (showSensitive || !isSensitiveIp(value)) { + return; + } + try { + destination.set("***"); + } catch (SerializationException e) { + throw new RuntimeException("Unable to censor IP address", e); // Error over silently printing an IP address. + } + }; + + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Asterisk { + + } } 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 e0472070a..163c93e3a 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.Properties; public class GeyserLocale { + public static final String SYSTEM_LOCALE = "system"; /** * If we determine the default locale that the user wishes to use, use that locale @@ -80,8 +81,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_LOCALE.equals(newDefaultLocale)) { // We want to use the system locale which is already loaded return; } diff --git a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java index 74ab1e7e6..65a62665d 100644 --- a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java @@ -25,11 +25,13 @@ package org.geysermc.geyser.text; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.util.AssetUtils; import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.JsonUtils; import org.geysermc.geyser.util.WebUtils; import java.io.FileNotFoundException; @@ -39,7 +41,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.HashMap; -import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -194,14 +195,12 @@ public class MinecraftLocale { // Read the localefile try (InputStream localeStream = Files.newInputStream(localeFile, StandardOpenOption.READ)) { // Parse the file as json - JsonNode localeObj = GeyserImpl.JSON_MAPPER.readTree(localeStream); + JsonObject localeObj = JsonUtils.fromJson(localeStream); // Parse all the locale fields - Iterator> localeIterator = localeObj.fields(); Map langMap = new HashMap<>(); - while (localeIterator.hasNext()) { - Map.Entry entry = localeIterator.next(); - langMap.put(entry.getKey(), entry.getValue().asText()); + for (Map.Entry entry : localeObj.entrySet()) { + langMap.put(entry.getKey(), entry.getValue().getAsString()); } return langMap; } catch (FileNotFoundException e){ 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 0c08c8df1..4b3642d03 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 @@ -306,7 +306,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()); } @@ -477,7 +477,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); } @@ -984,7 +984,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()); 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 9c9e61454..260d8977f 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 @@ -107,7 +107,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(); } } @@ -118,7 +118,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements resolvedFuture.whenComplete((resolved, throwable) -> { if (throwable != null ) { session.getGeyser().getLogger().debug("Failed resolving profile of player head at: " + blockPosition + " " + javaNbt); - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + if (GeyserImpl.getInstance().config().debugMode()) { throwable.printStackTrace(); } return; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 7cd5cd98b..d20782006 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -26,8 +26,6 @@ package org.geysermc.geyser.translator.protocol.bedrock; import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; 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; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java index b4c47c1bf..91c5e9a45 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -48,7 +48,7 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator= 0) { - if (session.getGeyser().getConfig().isForwardPlayerPing()) { + if (session.getGeyser().config().gameplay().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 674dd44bc..01388ed41 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 @@ -46,7 +46,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().authChainFor(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 07af4ddc4..04007bfd6 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 @@ -26,8 +26,8 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import org.cloudburstmc.protocol.bedrock.packet.EmotePacket; +import org.geysermc.geyser.GeyserImpl; 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,17 +39,12 @@ 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()); + if (!GeyserImpl.getInstance().config().gameplay().emotesEnabled()) { + event.setCancelled(true); + } + session.getGeyser().eventBus().fire(event); if (event.isCancelled()) { return; 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 7b567b148..8602d37dc 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 @@ -123,7 +123,7 @@ public class JavaCommandsTranslator 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 15b2add86..12f029908 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 @@ -110,7 +110,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator { try { // Get the version manifest from Mojang - VersionManifest versionManifest = GeyserImpl.JSON_MAPPER.readValue( + VersionManifest versionManifest = GeyserImpl.GSON.fromJson( WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); // Get the url for the latest version of the games manifest @@ -101,26 +111,24 @@ public final class AssetUtils { } // Get the individual version manifest - VersionInfo versionInfo = GeyserImpl.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + VersionInfo versionInfo = GeyserImpl.GSON.fromJson(WebUtils.getBody(latestInfoURL), VersionInfo.class); // Get the client jar for use when downloading the en_us locale - GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); + GeyserImpl.getInstance().getLogger().debug(versionInfo.getDownloads()); // Was previously a Jackson call for writeValueToString CLIENT_JAR_INFO = versionInfo.getDownloads().get("client"); - GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(CLIENT_JAR_INFO)); + GeyserImpl.getInstance().getLogger().debug(CLIENT_JAR_INFO); // Was previously a Jackson call for writeValueToString // Get the assets list - JsonNode assets = GeyserImpl.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + JsonObject assets = ((JsonObject) new JsonParser().parse(WebUtils.getBody(versionInfo.getAssetIndex().getUrl()))).getAsJsonObject("objects"); // Put each asset into an array for use later - Iterator> assetIterator = assets.fields(); - while (assetIterator.hasNext()) { - Map.Entry entry = assetIterator.next(); + for (Map.Entry entry : assets.entrySet()) { if (!entry.getKey().startsWith("minecraft/lang/")) { // No need to cache non-language assets as we don't use them continue; } - Asset asset = GeyserImpl.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + Asset asset = GeyserImpl.GSON.fromJson(entry.getValue(), Asset.class); ASSET_MAP.put(entry.getKey(), asset); } @@ -221,106 +229,108 @@ public final class AssetUtils { /* Classes that map to JSON files served by Mojang */ - @JsonIgnoreProperties(ignoreUnknown = true) @Getter static class VersionManifest { - @JsonProperty("latest") + @SerializedName("latest") private LatestVersion latestVersion; - @JsonProperty("versions") + @SerializedName("versions") private List versions; } - @JsonIgnoreProperties(ignoreUnknown = true) @Getter static class LatestVersion { - @JsonProperty("release") + @SerializedName("release") private String release; - @JsonProperty("snapshot") + @SerializedName("snapshot") private String snapshot; } - @JsonIgnoreProperties(ignoreUnknown = true) @Getter static class Version { - @JsonProperty("id") + @SerializedName("id") private String id; - @JsonProperty("type") + @SerializedName("type") private String type; - @JsonProperty("url") + @SerializedName("url") private String url; - @JsonProperty("time") + @SerializedName("time") private String time; - @JsonProperty("releaseTime") + @SerializedName("releaseTime") private String releaseTime; } - @JsonIgnoreProperties(ignoreUnknown = true) @Getter static class VersionInfo { - @JsonProperty("id") + @SerializedName("id") private String id; - @JsonProperty("type") + @SerializedName("type") private String type; - @JsonProperty("time") + @SerializedName("time") private String time; - @JsonProperty("releaseTime") + @SerializedName("releaseTime") private String releaseTime; - @JsonProperty("assetIndex") + @SerializedName("assetIndex") private AssetIndex assetIndex; - @JsonProperty("downloads") + @SerializedName("downloads") private Map downloads; } - @JsonIgnoreProperties(ignoreUnknown = true) @Getter static class VersionDownload { - @JsonProperty("sha1") + @SerializedName("sha1") private String sha1; - @JsonProperty("size") + @SerializedName("size") private int size; - @JsonProperty("url") + @SerializedName("url") private String url; + + @Override + public String toString() { + return "VersionDownload{" + + "sha1='" + sha1 + '\'' + + ", size=" + size + + ", url='" + url + '\'' + + '}'; + } } - @JsonIgnoreProperties(ignoreUnknown = true) @Getter static class AssetIndex { - @JsonProperty("id") + @SerializedName("id") private String id; - @JsonProperty("sha1") + @SerializedName("sha1") private String sha1; - @JsonProperty("size") + @SerializedName("size") private int size; - @JsonProperty("totalSize") + @SerializedName("totalSize") private int totalSize; - @JsonProperty("url") + @SerializedName("url") private String url; } - @JsonIgnoreProperties(ignoreUnknown = true) @Getter public static class Asset { - @JsonProperty("hash") + @SerializedName("hash") private String hash; - @JsonProperty("size") + @SerializedName("size") private int size; } diff --git a/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java b/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java index 9de67303d..33f3c3df7 100644 --- a/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java @@ -38,23 +38,13 @@ import java.util.concurrent.TimeUnit; * Much of the work here is from the wonderful folks from ViaRewind */ 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().gameplay().showCooldown() == CooldownType.DISABLED) return; CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference(); if (sessionPreference == CooldownType.DISABLED) return; @@ -170,10 +160,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/FileUtils.java b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java index 87ed8af02..dc208a802 100644 --- a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java @@ -25,15 +25,17 @@ package org.geysermc.geyser.util; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.annotation.Annotation; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -44,28 +46,18 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -public class FileUtils { - - /** - * Load the given YAML file into the given class - * - * @param src File to load - * @param valueType Class to load file into - * @param the type - * @return The data as the given class - * @throws IOException if the config could not be loaded - */ +public final class FileUtils { public static T loadConfig(File src, Class valueType) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()) - // Allow inference of single values as arrays - .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); - return objectMapper.readValue(src, valueType); + YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .file(src) + .build(); + ConfigurationNode node = loader.load(); + return node.get(valueType); } - public static T loadJson(InputStream src, Class valueType) throws IOException { + public static T loadJson(InputStream src, Class valueType) { // Read specifically with UTF-8 to allow any non-UTF-encoded JSON to read - return GeyserImpl.JSON_MAPPER.readValue(new InputStreamReader(src, StandardCharsets.UTF_8), valueType); + return GeyserImpl.GSON.fromJson(new InputStreamReader(src, StandardCharsets.UTF_8), valueType); } /** @@ -254,4 +246,7 @@ public class FileUtils { throw new RuntimeException(e); } } + + private FileUtils() { + } } diff --git a/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java b/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java new file mode 100644 index 000000000..b89a2d48a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.util; + +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.pack.PackCodec; +import org.geysermc.geyser.api.pack.PathPackCodec; +import org.geysermc.geyser.api.pack.ResourcePack; +import org.geysermc.geyser.api.pack.ResourcePackManifest; +import org.geysermc.geyser.api.pack.UrlPackCodec; +import org.geysermc.geyser.api.pack.exception.ResourcePackException; +import org.geysermc.geyser.api.pack.option.PriorityOption; +import org.geysermc.geyser.event.type.GeyserDefineResourcePacksEventImpl; +import org.geysermc.geyser.pack.GeyserResourcePack; +import org.geysermc.geyser.pack.ResourcePackHolder; +import org.geysermc.geyser.registry.loader.ResourcePackLoader; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public interface GeyserIntegratedPackUtil { + + Path CACHE = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache"); + Path PACK_PATH = CACHE.resolve("GeyserIntegratedPack.mcpack"); + UUID OPTIONAL_PACK_UUID = UUID.fromString("e5f5c938-a701-11eb-b2a3-047d7bb283ba"); + UUID INTEGRATED_PACK_UUID = UUID.fromString("2254393d-8430-45b0-838a-bd397828c765"); + AtomicReference INTEGRATED_PACK_VERSION = new AtomicReference<>(); + AtomicBoolean PACK_ENABLED = new AtomicBoolean(GeyserImpl.getInstance().config().gameplay().enableIntegratedPack()); + + default void registerGeyserPack(GeyserDefineResourcePacksEventImpl event) { + if (!GeyserImpl.getInstance().config().gameplay().enableIntegratedPack()) { + return; + } + + try { + Files.createDirectories(CACHE); + Files.copy(GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("GeyserIntegratedPack.mcpack"), + PACK_PATH, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Could not copy over Geyser integrated resource pack!", e); + PACK_ENABLED.set(false); + return; + } + + GeyserResourcePack integrated = ResourcePackLoader.readPack(PACK_PATH).build(); + INTEGRATED_PACK_VERSION.set(integrated.manifest().header().version()); + + try { + event.getPacks().put(INTEGRATED_PACK_UUID, ResourcePackHolder.of(integrated)); + event.registerOptions(INTEGRATED_PACK_UUID, PriorityOption.LOW); + PACK_ENABLED.set(true); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Could not register GeyserIntegratedPack!", e); + PACK_ENABLED.set(false); + } + } + + /** + * Pre-processes a resource pack about to be registered + * @param pack the pack to check + * @throws ResourcePackException if duplicate was discovered + */ + default void preProcessPack(GeyserResourcePack pack) { + if (!PACK_ENABLED.get()) { + return; + } + + if (Objects.equals(pack.uuid(), INTEGRATED_PACK_UUID)) { + handleDuplicateIntegratedPack(pack); + return; + } + + if (Objects.equals(pack.uuid(), OPTIONAL_PACK_UUID)) { + handleOptionalPack(pack); + } + } + + default void handleDuplicateIntegratedPack(ResourcePack duplicate) { + ResourcePackManifest.Version version = duplicate.manifest().header().version(); + if (duplicate.codec() instanceof UrlPackCodec) { + if (Objects.equals(version, INTEGRATED_PACK_VERSION.get())) { + GeyserImpl.getInstance().getLogger().debug("Found GeyserIntegratedPack sent via UrlPackCodec (version: %s)!".formatted(version)); + } else { + GeyserImpl.getInstance().getLogger().warning("Found GeyserIntegratedPack sent via UrlPackCodec, but the version differs! " + + "(found: %s, expected: %s). Skipping our own, but things may not work as expected!".formatted(duplicate, INTEGRATED_PACK_VERSION.get())); + } + unregisterIntegratedPack(); + PACK_ENABLED.set(true); + return; + } + + throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE); + } + + default void handleOptionalPack(ResourcePack pack) { + // Gracefully handle optional pack presence to avoid issues + if (pack.codec() instanceof UrlPackCodec) { + GeyserImpl.getInstance().getLogger().warning("Detected GeyserOptionalPack sent via the UrlPackCodec! Please migrate to sending the " + + "GeyserIntegratedPack instead - it will be required in the future for advanced features to work correctly!"); + } else { + GeyserImpl.getInstance().getLogger().warning("Detected GeyserOptionalPack! " + + "It should be removed " + warnMessageLocation(pack.codec()) + ", as Geyser now includes an improved version of this resource pack by default!" + ); + } + GeyserImpl.getInstance().getLogger().warning("Disabling the integrated pack..."); + unregisterIntegratedPack(); + PACK_ENABLED.set(false); + } + + default String warnMessageLocation(PackCodec codec) { + if (codec instanceof PathPackCodec pathPackCodec) { + try { + // try to create nicer /packs/xyz.mcpack path if possible + return "(found in: %s)".formatted(GeyserImpl.getInstance().getBootstrap().getConfigFolder().relativize(pathPackCodec.path())); + } catch (Exception e) { + return "(found in: %s)".formatted(pathPackCodec.path()); + } + } + return "(registered with codec: %s)".formatted(codec); + } + + void unregisterIntegratedPack(); + + boolean integratedPackRegistered(); + + default boolean isIntegratedPackActive() { + return GeyserImpl.getInstance().config().gameplay().enableIntegratedPack() && integratedPackRegistered(); + } +} 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 406f413cb..17040a9ce 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -268,7 +268,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; @@ -320,7 +320,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().gameplay().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 new file mode 100644 index 000000000..d1e4a9d99 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/JsonUtils.java @@ -0,0 +1,76 @@ +/* + * 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.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import marcono1234.gson.recordadapter.RecordTypeAdapterFactory; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.pack.GeyserResourcePackManifest; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; + +public final class JsonUtils { + + public static T fromJson(byte[] bytes, Class type) { + return GeyserImpl.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), type); + } + + public static JsonObject fromJson(InputStream stream) { + 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); + } + + public static Gson createGson() { + GsonBuilder builder = new GsonBuilder().setPrettyPrinting(); + try { + new Gson().fromJson("{\"version\":[1,0,0],\"uuid\":\"eebb4ea8-a701-11eb-95ba-047d7bb283ba\"}", GeyserResourcePackManifest.Dependency.class); + } catch (Throwable e) { + // 1.16.5 and 1.17.1 (at minimum) have an outdated Gson version that doesn't support records. + // Remove this workaround when all platforms support Gson 2.10+ + // (Explicitly allow missing component values - the dependencies module for resource packs, for example, can be missing) + builder.registerTypeAdapterFactory(RecordTypeAdapterFactory.builder().allowMissingComponentValues().create()) + // Since this is a record, the above will take precedence unless we explicitly declare it. + .registerTypeAdapter(GeyserResourcePackManifest.Version.class, new GeyserResourcePackManifest.Version.VersionDeserializer()); + } + return builder.create(); + } + + private JsonUtils() { + } +} 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 0741ba225..ef1d641c3 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 net.raphimc.minecraftauth.msa.model.MsaDeviceCode; import org.cloudburstmc.protocol.bedrock.data.auth.AuthPayload; import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload; @@ -55,8 +52,6 @@ import java.security.PublicKey; 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) { @@ -71,7 +66,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().advanced().bedrock().validateBedrockLogin()) { session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.remote.invalid_xbox_account")); return; } @@ -97,8 +92,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(jwt); session.setClientData(data); @@ -106,7 +100,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(); } @@ -225,7 +219,7 @@ public class LoginEncryptionUtils { .append("\n%xbox.signin.enterCode\n") .append(ChatColor.GREEN) .append(msCode.getUserCode()); - 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 cb6ad6f0c..34b23a93d 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -54,8 +54,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().gameplay().showCooldown() != CooldownUtils.CooldownType.DISABLED; + boolean customSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0; // 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/VersionCheckUtils.java b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java index b21492d18..9404f6590 100644 --- a/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.util; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonObject; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.event.ClickEvent; @@ -49,7 +49,7 @@ import java.util.regex.Pattern; public final class VersionCheckUtils { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static @NonNull OptionalInt LATEST_BEDROCK_RELEASE = OptionalInt.empty(); - private static final int SUPPORTED_JAVA_VERSION = 17; + private static final int SUPPORTED_JAVA_VERSION = 21; public static void checkForOutdatedFloodgate(GeyserLogger logger) { try { @@ -93,9 +93,9 @@ public final class VersionCheckUtils { public static void checkForGeyserUpdate(Supplier recipient) { CompletableFuture.runAsync(() -> { try { - JsonNode json = WebUtils.getJson("https://api.geysermc.org/v2/versions/geyser"); - JsonNode bedrock = json.get("bedrock").get("protocol"); - int protocolVersion = bedrock.get("id").asInt(); + JsonObject json = WebUtils.getJson("https://api.geysermc.org/v2/versions/geyser"); + JsonObject bedrock = json.getAsJsonObject("bedrock").getAsJsonObject("protocol"); + int protocolVersion = bedrock.get("id").getAsInt(); if (GameProtocol.getBedrockCodec(protocolVersion) != null) { LATEST_BEDROCK_RELEASE = OptionalInt.empty(); // We support the latest version! No need to print a message. @@ -103,7 +103,7 @@ public final class VersionCheckUtils { } LATEST_BEDROCK_RELEASE = OptionalInt.of(protocolVersion); - final String newBedrockVersion = bedrock.get("name").asText(); + final String newBedrockVersion = bedrock.get("name").getAsString(); // Delayed for two reasons: save unnecessary processing, and wait to load locale if this is on join. GeyserCommandSource sender = recipient.get(); 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 382c65fba..04c652e3d 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -25,8 +25,10 @@ package org.geysermc.geyser.util; -import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; @@ -84,18 +86,22 @@ public class WebUtils { } /** - * Makes a web request to the given URL and returns the body as a {@link JsonNode}. + * Makes a web request to the given URL and returns the body as a {@link JsonObject}. * * @param reqURL URL to fetch * @return the response as JSON */ - public static JsonNode getJson(String reqURL) throws IOException { + public static JsonObject getJson(String reqURL) throws IOException { HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection(); con.setRequestProperty("User-Agent", getUserAgent()); con.setConnectTimeout(10000); con.setReadTimeout(10000); checkResponseCode(con); - return GeyserImpl.JSON_MAPPER.readTree(con.getInputStream()); + try (InputStreamReader isr = new InputStreamReader(con.getInputStream()); + JsonReader reader = GeyserImpl.GSON.newJsonReader(isr)) { + //noinspection deprecation + return new JsonParser().parse(reader).getAsJsonObject(); + } } /** @@ -105,14 +111,24 @@ public class WebUtils { * @param fileLocation Location to save on disk */ public static void downloadFile(String reqURL, String fileLocation) { + downloadFile(reqURL, Paths.get(fileLocation)); + } + + /** + * Downloads a file from the given URL and saves it to disk + * + * @param reqURL File to fetch + * @param path Location to save on disk as a path + */ + public static void downloadFile(String reqURL, Path path) { try { HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection(); con.setRequestProperty("User-Agent", getUserAgent()); checkResponseCode(con); InputStream in = con.getInputStream(); - Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING); + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { - throw new RuntimeException("Unable to download and save file: " + fileLocation + " (" + reqURL + ")", e); + throw new RuntimeException("Unable to download and save file: " + path.toAbsolutePath() + " (" + reqURL + ")", e); } } @@ -132,7 +148,7 @@ public class WebUtils { con.setConnectTimeout(10000); con.setReadTimeout(10000); - con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION); + con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().platformType().platformName() + "/" + GeyserImpl.VERSION); con.setInstanceFollowRedirects(true); int responseCode = con.getResponseCode(); @@ -362,7 +378,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 } @@ -393,6 +409,6 @@ public class WebUtils { } public static String getUserAgent() { - return "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION; + return "Geyser-" + GeyserImpl.getInstance().platformType().platformName() + "/" + GeyserImpl.VERSION; } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java b/core/src/main/java/org/geysermc/geyser/util/metrics/MetricsPlatform.java similarity index 52% rename from bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java rename to core/src/main/java/org/geysermc/geyser/util/metrics/MetricsPlatform.java index bc084a34e..48eac4245 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/util/metrics/MetricsPlatform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.bungeecord; +package org.geysermc.geyser.util.metrics; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import net.md_5.bungee.api.plugin.Plugin; -import org.geysermc.geyser.FloodgateKeyLoader; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.PlatformType; -import java.nio.file.Path; +public interface MetricsPlatform { + boolean enabled(); -@Getter -@JsonIgnoreProperties(ignoreUnknown = true) -public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { - @JsonIgnore - private Path floodgateKeyPath; + String serverUuid(); - public void loadFloodgate(GeyserBungeePlugin plugin) { - Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate"); - Path geyserDataFolder = plugin.getDataFolder().toPath(); - Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; + boolean logFailedRequests(); - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); + boolean logSentData(); + + boolean logResponseStatusText(); + + // We're not relocating on Geyser-Standalone, and using JiJ on modded platforms + default boolean disableRelocateCheck() { + PlatformType platformType = GeyserImpl.getInstance().platformType(); + return platformType == PlatformType.FABRIC || + platformType == PlatformType.NEOFORGE || + platformType == PlatformType.STANDALONE; } } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java b/core/src/main/java/org/geysermc/geyser/util/metrics/ProvidedMetricsPlatform.java similarity index 59% rename from bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java rename to core/src/main/java/org/geysermc/geyser/util/metrics/ProvidedMetricsPlatform.java index a24380bd6..fdafaf86b 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/util/metrics/ProvidedMetricsPlatform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,34 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.mod; +package org.geysermc.geyser.util.metrics; -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.geysermc.geyser.FloodgateKeyLoader; -import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.GeyserImpl; -import java.nio.file.Path; +public final class ProvidedMetricsPlatform implements MetricsPlatform { -public class GeyserModConfiguration extends GeyserJacksonConfiguration { - @JsonIgnore - private Path floodgateKeyPath; - - public void loadFloodgate(GeyserModBootstrap geyser, Path floodgateDataFolder) { - Path geyserDataFolder = geyser.getConfigFolder(); - - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); + @Override + public boolean enabled() { + return GeyserImpl.getInstance().config().enableMetrics(); } @Override - public Path getFloodgateKeyPath() { - return floodgateKeyPath; + public String serverUuid() { + return GeyserImpl.getInstance().config().metricsUuid().toString(); + } + + @Override + public boolean logFailedRequests() { + return GeyserImpl.getInstance().config().debugMode(); + } + + @Override + public boolean logSentData() { + return GeyserImpl.getInstance().config().debugMode(); + } + + @Override + public boolean logResponseStatusText() { + return GeyserImpl.getInstance().config().debugMode(); } } diff --git a/core/src/main/resources/GeyserIntegratedPack.mcpack b/core/src/main/resources/GeyserIntegratedPack.mcpack new file mode 100644 index 0000000000000000000000000000000000000000..9ff4abb3d60168942d112a928e04b6e226c86a32 GIT binary patch literal 82681 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 bpkO@QtxpHe@+g!k_+ { + CommentedConfigurationNode config1 = new ConfigLoader(file).loadConfigurationNode(type, PlatformType.STANDALONE); + + long initialModification = file.lastModified(); + assertTrue(file.exists()); // should have been created + List firstContents = Files.readAllLines(file.toPath()); + + CommentedConfigurationNode config2 = new ConfigLoader(file).loadConfigurationNode(type, PlatformType.STANDALONE); + List secondContents = Files.readAllLines(file.toPath()); + + assertEquals(initialModification, file.lastModified()); // should not have been touched + assertEquals(firstContents, secondContents); + + // Must ignore this, as when the config is read back, the header is interpreted as a comment on the first node in the map + config1.node("java").comment(null); + config2.node("java").comment(null); + assertEquals(config1, config2); + }); + } + + @Test + void testDefaultConfigMigration() throws Exception { + testConfiguration("default"); + } + + @Test + void testAllChangedConfigMigration() throws Exception { + testConfiguration("all-changed"); + } + + @Test + void testLegacyConfigMigration() throws Exception { + testConfiguration("legacy"); + } + + @Test + void allowCustomSkullsMigration() throws Exception { + testConfiguration("allow-custom-skulls"); + } + + @Test + void testNoEmotesMigration() throws Exception { + testConfiguration("migrate-no-emotes"); + } + + @Test + void testChewsOldConfig() throws Exception { + testConfiguration("chew"); + } + + @Test + void testInvalidConfig() throws Exception { + streamResourceFiles(CONFIG_PREFIX + "/invalid").forEach(resource -> { + try { + forAllConfigs(type -> { + assertThrows(ConfigurateException.class, + () -> new ConfigLoader(resource).loadConfigurationNode(type, getPlatformType(type)), + "Did not get exception while loading %s (file: %s)".formatted(type.getSimpleName(), resource.getName())); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + public void testConfiguration(String folder) throws Exception { + forAllConfigs(type -> { + CommentedConfigurationNode oldTransformed = new ConfigLoader(copyResourceToTempFile(folder, "before.yml")) + .loadConfigurationNode(type, getPlatformType(type)); + + String configName = type == GeyserRemoteConfig.class ? "remote" : "plugin"; + CommentedConfigurationNode newTransformed = new ConfigLoader(copyResourceToTempFile(folder, configName + ".yml")) + .loadConfigurationNode(type, getPlatformType(type)); + + assertEquals(oldTransformed, newTransformed); + }); + } + + File copyResourceToTempFile(String... path) throws Exception { + File resource = getConfigResource(CONFIG_PREFIX + "/" + String.join("/", path)); + return Files.copy(resource.toPath(), tempDirectory.resolve(resource.getName()), StandardCopyOption.REPLACE_EXISTING).toFile(); + } + + PlatformType getPlatformType(Class configClass) { + if (configClass == GeyserRemoteConfig.class) { + return PlatformType.STANDALONE; + } + if (configClass == GeyserPluginConfig.class) { + return PlatformType.SPIGOT; + } + throw new IllegalArgumentException("Unsupported config class " + configClass); + } + + void forAllConfigs(CheckedConsumer, Exception> consumer) throws Exception { + consumer.accept(GeyserPluginConfig.class); + consumer.accept(GeyserRemoteConfig.class); + } + + private static Stream streamResourceFiles(String directory) throws IOException, URISyntaxException { + URL resourceUrl = ConfigLoaderTest.class.getClassLoader().getResource(directory); + Objects.requireNonNull(resourceUrl, "Resource directory not found: " + directory); + + Path resourcePath = Path.of(resourceUrl.toURI()); + // Walk the directory, but don't go into subdirectories (maxDepth = 1) + return Files.walk(resourcePath, 1) + .filter(path -> !path.equals(resourcePath)) // Exclude the directory itself + .filter(Files::isRegularFile) // Ensure we only get files + .map(Path::toFile); + } + + @SneakyThrows + private File getConfigResource(String name) { + URL url = Objects.requireNonNull(getClass().getClassLoader().getResource(name), "No resource for name: " + name); + return Path.of(url.toURI()).toFile(); + } +} diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java index 2b89867fb..4ddb0df2f 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContext.java @@ -25,20 +25,20 @@ package org.geysermc.geyser.scoreboard.network.util; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.configuration.GeyserConfig; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; -import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.mockito.Mockito; + +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; public class GeyserMockContext { private final List mocksAndSpies = new ArrayList<>(); @@ -49,11 +49,11 @@ public class GeyserMockContext { var context = new GeyserMockContext(); var geyserImpl = context.mock(GeyserImpl.class); - var config = context.mock(GeyserConfiguration.class); - - when(config.getScoreboardPacketThreshold()).thenReturn(1_000); - - when(geyserImpl.getConfig()).thenReturn(config); + var config = context.mock(GeyserConfig.class); + when(geyserImpl.config()).thenReturn(config); + var advancedConfig = context.mock(GeyserConfig.AdvancedConfig.class); + when(config.advanced()).thenReturn(advancedConfig); + when(advancedConfig.scoreboardPacketThreshold()).thenReturn(1_000); var logger = context.storeObject(new EmptyGeyserLogger()); when(geyserImpl.getLogger()).thenReturn(logger); diff --git a/core/src/test/resources/configuration/all-changed/before.yml b/core/src/test/resources/configuration/all-changed/before.yml new file mode 100644 index 000000000..33601a326 --- /dev/null +++ b/core/src/test/resources/configuration/all-changed/before.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + address: 127.0.0.1 + # The port that will listen for connections + port: 19122 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: true + # 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" + motd1: "Gayser" + motd2: "Another Gayser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Gayser" + # 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. + compression-level: 5 + # 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. + broadcast-port: 19133 + # 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. + enable-proxy-protocol: true + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/13" ] +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: test.geysermc.org + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25564 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: floodgate + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: true + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: true + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: keyyy.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsername + - ThisOther + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 111 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: false + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: false +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: false +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: true +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 2 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: true + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 99 + +# If debug messages should be sent through console +debug-mode: true + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: actionbar + +# Controls if coordinates are shown to players. +show-coordinates: false + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: true + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "emotes-and-offhand" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +default-locale: en_uk + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 10 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: true + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 111 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 19 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: false + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: true + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: false + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: true + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: false + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: false + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:apple + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: false + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 10 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: true + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1399 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: false + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: false + +config-version: 4 diff --git a/core/src/test/resources/configuration/all-changed/plugin.yml b/core/src/test/resources/configuration/all-changed/plugin.yml new file mode 100644 index 000000000..ea9830cd3 --- /dev/null +++ b/core/src/test/resources/configuration/all-changed/plugin.yml @@ -0,0 +1,239 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 127.0.0.1 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19122 + + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + clone-remote-port: true + +# Network settings for the Java server connection +java: + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: floodgate + +# MOTD settings +motd: + # 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" + primary-motd: Gayser + secondary-motd: Another Gayser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: false + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 99 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: false + + # Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + # There is no need to disable this unless your MOTD or player count does not appear properly. + integrated-ping-passthrough: false + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + # Only relevant if integrated-ping-passthrough is disabled. + ping-passthrough-interval: 2 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Gayser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: actionbar + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: false + + # Controls if coordinates are shown to players. + show-coordinates: false + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: true + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: true + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:apple + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: false + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: false + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: true + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: true + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 111 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 19 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: en_uk + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: false + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsername + - ThisOther + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 111 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: false + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 10 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 10 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: keyyy.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: true + + # Whether to connect directly into the Java server without creating a TCP connection. + # This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + # If enabled, the remote address and port sections are ignored. + # If disabled, expect performance decrease and latency increase. + use-direct-connection: false + + # Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + # This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + # This requires use-direct-connection to be true. + disable-compression: false + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 19133 + + # 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. + compression-level: 5 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: true + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: + - 127.0.0.1 + - 172.18.0.0/13 + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1399 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# If debug messages should be sent through console +debug-mode: true + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/all-changed/remote.yml b/core/src/test/resources/configuration/all-changed/remote.yml new file mode 100644 index 000000000..144abce08 --- /dev/null +++ b/core/src/test/resources/configuration/all-changed/remote.yml @@ -0,0 +1,238 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 127.0.0.1 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19122 + +# Network settings for the Java server connection +java: + # The IP address of the Java Edition server. + address: test.geysermc.org + + # The port of the Java Edition server. + port: 25564 + + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: floodgate + + # Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + # This is designed to be used for forced hosts on proxies. + forward-hostname: true + +# MOTD settings +motd: + # 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" + primary-motd: Gayser + secondary-motd: Another Gayser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: false + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 99 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: false + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + ping-passthrough-interval: 2 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Gayser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: actionbar + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: false + + # Controls if coordinates are shown to players. + show-coordinates: false + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: true + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: true + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:apple + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: false + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: false + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: true + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: true + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 111 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 19 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: en_uk + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: false + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsername + - ThisOther + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 111 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: false + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 10 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 10 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: keyyy.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: true + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 19133 + + # 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. + compression-level: 5 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: true + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: + - 127.0.0.1 + - 172.18.0.0/13 + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1399 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +enable-metrics: false + +# The bstats metrics uuid. Do not touch! +metrics-uuid: 00000000-0000-0000-0000-000000000000 + +# If debug messages should be sent through console +debug-mode: true + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/allow-custom-skulls/before.yml b/core/src/test/resources/configuration/allow-custom-skulls/before.yml new file mode 100644 index 000000000..5544122a2 --- /dev/null +++ b/core/src/test/resources/configuration/allow-custom-skulls/before.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 + # The port that will listen for connections + port: 19132 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # 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" + motd1: "Geyser" + motd2: "Another Geyser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Geyser" + # 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. + compression-level: 6 + # 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. + # broadcast-port: 19132 + # 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. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ] +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: online + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: false + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: false + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: key.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: true +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: true +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: false + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: title + +# Controls if coordinates are shown to players. +show-coordinates: true + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: false + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "disabled" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: false + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: true + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: false + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: false + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1400 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: true + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + +config-version: 4 diff --git a/core/src/test/resources/configuration/allow-custom-skulls/plugin.yml b/core/src/test/resources/configuration/allow-custom-skulls/plugin.yml new file mode 100644 index 000000000..d311e0353 --- /dev/null +++ b/core/src/test/resources/configuration/allow-custom-skulls/plugin.yml @@ -0,0 +1,237 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + clone-remote-port: false + +# Network settings for the Java server connection +java: + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + # There is no need to disable this unless your MOTD or player count does not appear properly. + integrated-ping-passthrough: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + # Only relevant if integrated-ping-passthrough is disabled. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 0 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Whether to connect directly into the Java server without creating a TCP connection. + # This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + # If enabled, the remote address and port sections are ignored. + # If disabled, expect performance decrease and latency increase. + use-direct-connection: true + + # Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + # This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + # This requires use-direct-connection to be true. + disable-compression: true + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/allow-custom-skulls/remote.yml b/core/src/test/resources/configuration/allow-custom-skulls/remote.yml new file mode 100644 index 000000000..9a91bcedf --- /dev/null +++ b/core/src/test/resources/configuration/allow-custom-skulls/remote.yml @@ -0,0 +1,236 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + +# Network settings for the Java server connection +java: + # The IP address of the Java Edition server. + address: 127.0.0.1 + + # The port of the Java Edition server. + port: 25565 + + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + + # Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + # This is designed to be used for forced hosts on proxies. + forward-hostname: false + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 0 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +enable-metrics: true + +# The bstats metrics uuid. Do not touch! +metrics-uuid: 00000000-0000-0000-0000-000000000000 + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/chew/before.yml b/core/src/test/resources/configuration/chew/before.yml new file mode 100644 index 000000000..247f19f63 --- /dev/null +++ b/core/src/test/resources/configuration/chew/before.yml @@ -0,0 +1,146 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.geysermc.org/ +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # There is no reason to change this unless you want to limit what IPs can connect to your server. + address: 0.0.0.0 + # The port that will listen for connections + port: 19132 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true + motd1: "gsauta" + motd2: "owo" + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "GSA" +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, Geyser will attempt to find the best address to connect to. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + auth-type: floodgate + +# Floodgate uses encryption to ensure use from authorised sources. +# This should point to the public key generated by Floodgate (Bungee or CraftBukkit) +# You can ignore this when not using Floodgate. +floodgate-key-file: public-key.pem + +## the Xbox/MCPE username is the key for the Java server auth-info +## this allows automatic configuration/login to the remote Java server +## if you are brave/stupid enough to put your Mojang account info into +## a config file +#userAuths: +# bluerkelp2: # MCPE/Xbox username +# email: not_really_my_email_address_mr_minecrafter53267@gmail.com # Mojang account email address +# password: "this isn't really my password" +# +# herpderp40300499303040503030300500293858393589: +# email: herpderp@derpherp.com +# password: dooooo + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: false +# Relay the protocol name (e.g. BungeeCord [X.X], Paper 1.X) - only really useful when using a custom protocol name! +# This will also show up on sites like MCSrvStatus. +passthrough-protocol-name: false +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: false +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Maximum amount of players that can connect +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Thread pool size +general-thread-pool: 32 + +# Allow third party capes to be visible. Currently allowing: +# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes +allow-third-party-capes: true + +# Allow third party deadmau5 ears to be visible. Currently allowing: +# MinecraftCapes +allow-third-party-ears: false + +# Allow a fake cooldown indicator to be sent. Bedrock players do not see a cooldown as they still use 1.8 combat +show-cooldown: true + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Configures if chunk caching should be enabled or not. This keeps an individual +# record of each block the client loads in. While this feature does allow for a few +# things such as block break animations to show up in creative mode and among others, +# it is HIGHLY recommended you disable this on a production environment as it can eat +# up a lot of RAM. However, when using the Spigot version of Geyser, support for features +# or implementations this allows is automatically enabled without the additional caching as +# Geyser has direct access to the server itself. +cache-chunks: false + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Bedrock prevents building and displaying blocks above Y127 in the Nether - +# enabling this config option works around that by changing the Nether dimension ID +# to the End ID. The main downside to this is that the sky will resemble that of +# the end sky in the nether, but ultimately it's the only way for this feature to work. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false it allows the user to disconnect from the server if they don't +# want to download the resource packs +force-resource-packs: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: ff99f931-ff7f-4dfc-a027-4bd15edc5980 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: false + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +# mtu: 1400 + +config-version: 4 diff --git a/core/src/test/resources/configuration/chew/plugin.yml b/core/src/test/resources/configuration/chew/plugin.yml new file mode 100644 index 000000000..0e568a7d3 --- /dev/null +++ b/core/src/test/resources/configuration/chew/plugin.yml @@ -0,0 +1,237 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + clone-remote-port: false + +# Network settings for the Java server connection +java: + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: floodgate + +# MOTD settings +motd: + # 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" + primary-motd: gsauta + secondary-motd: owo + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: false + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: false + + # Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + # There is no need to disable this unless your MOTD or player count does not appear properly. + integrated-ping-passthrough: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + # Only relevant if integrated-ping-passthrough is disabled. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: GSA + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 128 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Whether to connect directly into the Java server without creating a TCP connection. + # This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + # If enabled, the remote address and port sections are ignored. + # If disabled, expect performance decrease and latency increase. + use-direct-connection: true + + # Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + # This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + # This requires use-direct-connection to be true. + disable-compression: true + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/chew/remote.yml b/core/src/test/resources/configuration/chew/remote.yml new file mode 100644 index 000000000..0bcfcbe9e --- /dev/null +++ b/core/src/test/resources/configuration/chew/remote.yml @@ -0,0 +1,236 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + +# Network settings for the Java server connection +java: + # The IP address of the Java Edition server. + address: 127.0.0.1 + + # The port of the Java Edition server. + port: 25565 + + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: floodgate + + # Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + # This is designed to be used for forced hosts on proxies. + forward-hostname: false + +# MOTD settings +motd: + # 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" + primary-motd: gsauta + secondary-motd: owo + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: false + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: false + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: GSA + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 128 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +enable-metrics: true + +# The bstats metrics uuid. Do not touch! +metrics-uuid: ff99f931-ff7f-4dfc-a027-4bd15edc5980 + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/main/resources/config.yml b/core/src/test/resources/configuration/default/before.yml similarity index 99% rename from core/src/main/resources/config.yml rename to core/src/test/resources/configuration/default/before.yml index 15d3a20a6..c282d65a3 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/test/resources/configuration/default/before.yml @@ -187,7 +187,7 @@ metrics: # If metrics should be enabled enabled: true # UUID of server, don't change! - uuid: generateduuid + uuid: 00000000-0000-0000-0000-000000000000 # ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! diff --git a/core/src/test/resources/configuration/default/plugin.yml b/core/src/test/resources/configuration/default/plugin.yml new file mode 100644 index 000000000..e0acb4825 --- /dev/null +++ b/core/src/test/resources/configuration/default/plugin.yml @@ -0,0 +1,237 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + clone-remote-port: false + +# Network settings for the Java server connection +java: + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + # There is no need to disable this unless your MOTD or player count does not appear properly. + integrated-ping-passthrough: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + # Only relevant if integrated-ping-passthrough is disabled. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 128 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Whether to connect directly into the Java server without creating a TCP connection. + # This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + # If enabled, the remote address and port sections are ignored. + # If disabled, expect performance decrease and latency increase. + use-direct-connection: true + + # Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + # This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + # This requires use-direct-connection to be true. + disable-compression: true + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/default/remote.yml b/core/src/test/resources/configuration/default/remote.yml new file mode 100644 index 000000000..0feccca3d --- /dev/null +++ b/core/src/test/resources/configuration/default/remote.yml @@ -0,0 +1,236 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + +# Network settings for the Java server connection +java: + # The IP address of the Java Edition server. + address: 127.0.0.1 + + # The port of the Java Edition server. + port: 25565 + + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + + # Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + # This is designed to be used for forced hosts on proxies. + forward-hostname: false + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 128 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +enable-metrics: true + +# The bstats metrics uuid. Do not touch! +metrics-uuid: 00000000-0000-0000-0000-000000000000 + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/invalid/invalid_enum.yml b/core/src/test/resources/configuration/invalid/invalid_enum.yml new file mode 100644 index 000000000..e3de751e5 --- /dev/null +++ b/core/src/test/resources/configuration/invalid/invalid_enum.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 + # The port that will listen for connections + port: 19132 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # 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" + motd1: "Geyser" + motd2: "Another Geyser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Geyser" + # 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. + compression-level: 6 + # 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. + # broadcast-port: 19132 + # 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. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ] +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: online + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: false + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: false + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: key.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: true +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: true +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: false + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: invalidEnum + +# Controls if coordinates are shown to players. +show-coordinates: true + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: false + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "disabled" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: true + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: true + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: false + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: false + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1400 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: true + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + +config-version: 4 diff --git a/core/src/test/resources/configuration/invalid/invalid_remote.yml b/core/src/test/resources/configuration/invalid/invalid_remote.yml new file mode 100644 index 000000000..df4aea3e0 --- /dev/null +++ b/core/src/test/resources/configuration/invalid/invalid_remote.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 + # The port that will listen for connections + port: 19132 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # 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" + motd1: "Geyser" + motd2: "Another Geyser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Geyser" + # 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. + compression-level: 6 + # 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. + # broadcast-port: 19132 + # 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. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ] +remote:auto + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: online + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: false + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: false + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: key.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: true +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: true +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: false + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: title + +# Controls if coordinates are shown to players. +show-coordinates: true + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: false + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "disabled" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: true + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: true + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: false + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: false + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1400 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: true + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + +config-version: 4 diff --git a/core/src/test/resources/configuration/invalid/port_out_of_range.yml b/core/src/test/resources/configuration/invalid/port_out_of_range.yml new file mode 100644 index 000000000..1a52e42f6 --- /dev/null +++ b/core/src/test/resources/configuration/invalid/port_out_of_range.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 + # The port that will listen for connections + port: 99999 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # 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" + motd1: "Geyser" + motd2: "Another Geyser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Geyser" + # 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. + compression-level: 6 + # 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. + # broadcast-port: 19132 + # 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. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ] +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: online + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: false + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: false + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: key.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: true +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: true +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: false + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: title + +# Controls if coordinates are shown to players. +show-coordinates: true + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: false + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "disabled" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: true + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: true + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: false + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: false + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1400 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: true + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + +config-version: 4 diff --git a/core/src/test/resources/configuration/legacy/before.yml b/core/src/test/resources/configuration/legacy/before.yml new file mode 100644 index 000000000..52f90aaf5 --- /dev/null +++ b/core/src/test/resources/configuration/legacy/before.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 + # The port that will listen for connections + port: 19132 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # 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" + motd1: "Geyser" + motd2: "Another Geyser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Geyser" + # 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. + compression-level: 6 + # 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. + # broadcast-port: 19132 + # 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. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ] +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: online + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: false + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: false + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: public-key.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: true +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: true +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: false + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: true + +# Controls if coordinates are shown to players. +show-coordinates: true + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: false + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "disabled" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: true + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: true + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: false + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: true + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1400 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: true + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + +config-version: 4 diff --git a/core/src/test/resources/configuration/legacy/plugin.yml b/core/src/test/resources/configuration/legacy/plugin.yml new file mode 100644 index 000000000..e0acb4825 --- /dev/null +++ b/core/src/test/resources/configuration/legacy/plugin.yml @@ -0,0 +1,237 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + clone-remote-port: false + +# Network settings for the Java server connection +java: + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + # There is no need to disable this unless your MOTD or player count does not appear properly. + integrated-ping-passthrough: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + # Only relevant if integrated-ping-passthrough is disabled. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 128 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Whether to connect directly into the Java server without creating a TCP connection. + # This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + # If enabled, the remote address and port sections are ignored. + # If disabled, expect performance decrease and latency increase. + use-direct-connection: true + + # Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + # This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + # This requires use-direct-connection to be true. + disable-compression: true + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/legacy/remote.yml b/core/src/test/resources/configuration/legacy/remote.yml new file mode 100644 index 000000000..0feccca3d --- /dev/null +++ b/core/src/test/resources/configuration/legacy/remote.yml @@ -0,0 +1,236 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + +# Network settings for the Java server connection +java: + # The IP address of the Java Edition server. + address: 127.0.0.1 + + # The port of the Java Edition server. + port: 25565 + + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + + # Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + # This is designed to be used for forced hosts on proxies. + forward-hostname: false + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: true + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 128 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +enable-metrics: true + +# The bstats metrics uuid. Do not touch! +metrics-uuid: 00000000-0000-0000-0000-000000000000 + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/migrate-no-emotes/before.yml b/core/src/test/resources/configuration/migrate-no-emotes/before.yml new file mode 100644 index 000000000..1897a8e9e --- /dev/null +++ b/core/src/test/resources/configuration/migrate-no-emotes/before.yml @@ -0,0 +1,219 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://wiki.geysermc.org/ +# +# NOTICE: See https://wiki.geysermc.org/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +bedrock: + # The IP address that will listen for connections. + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 + # The port that will listen for connections + port: 19132 + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + # This option is for the plugin version only. + clone-remote-port: false + # 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" + motd1: "Geyser" + motd2: "Another Geyser server." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: "Geyser" + # 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. + compression-level: 6 + # 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. + # broadcast-port: 19132 + # 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. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ] +remote: + # The IP address of the remote (Java Edition) server + # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, + # for plugin versions, it is recommended to keep this as "auto" so Geyser will automatically configure address, port, and auth-type. + # Leave as "auto" if floodgate is installed. + address: auto + # The port of the remote (Java Edition) server + # For plugin versions, if address has been set to "auto", the port will also follow the server's listening port. + port: 25565 + # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). + # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. + # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. + auth-type: online + # Whether to enable PROXY protocol or not while connecting to the server. + # This is useful only when: + # 1) Your server supports PROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-proxy-protocol: false + # Forward the hostname that the Bedrock client used to connect over to the Java server + # This is designed to be used for forced hosts on proxies + forward-hostname: false + +# Floodgate uses encryption to ensure use from authorised 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. +floodgate-key-file: key.pem + +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: true +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: true +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 + +# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate +# ping, it may also cause players to time out more easily. +forward-player-ping: false + +# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count. +max-players: 100 + +# If debug messages should be sent through console +debug-mode: false + +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://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" +show-cooldown: title + +# Controls if coordinates are shown to players. +show-coordinates: true + +# Whether Bedrock players are blocked from performing their scaffolding-style bridging. +disable-bedrock-scaffolding: false + +# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind +# There are three options this can be set to: +# disabled - the default/fallback, which doesn't apply this workaround +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "no-emotes" + +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us + +# Specify how many days images will be cached to disk to save downloading them from the internet. +# A value of 0 is disabled. (Default: 0) +cache-images: 0 + +# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. +allow-custom-skulls: false + +# The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. +# Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. +max-visible-custom-skulls: 128 + +# The radius in blocks around the player in which custom skulls are displayed. +custom-skull-render-distance: 32 + +# Whether to add any items and blocks which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# Geyser's block, item, and skull mappings systems will also be disabled. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + +# Bedrock prevents building and displaying blocks above Y127 in the Nether. +# This config option works around that by changing the Nether dimension ID to the End ID. +# The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. +above-bedrock-nether-building: false + +# Force clients to load all resource packs if there are any. +# If set to false, it allows the user to connect to the server even if they don't +# want to download the resource packs. +force-resource-packs: true + +# Allows Xbox achievements to be unlocked. +xbox-achievements-enabled: false + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, +# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. +unusable-space-block: minecraft:barrier + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +metrics: + # If metrics should be enabled + enabled: true + # UUID of server, don't change! + uuid: 00000000-0000-0000-0000-000000000000 + +# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! + +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + +# Allow connections from ProxyPass and Waterdog. +# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. +enable-proxy-connections: false + +# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. +# 1400 is the default. +mtu: 1400 + +# Whether to connect directly into the Java server without creating a TCP connection. +# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. +# If enabled on plugin versions, the remote address and port sections are ignored +# If disabled on plugin versions, expect performance decrease and latency increase +use-direct-connection: true + +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + +config-version: 4 diff --git a/core/src/test/resources/configuration/migrate-no-emotes/plugin.yml b/core/src/test/resources/configuration/migrate-no-emotes/plugin.yml new file mode 100644 index 000000000..d5cd353ad --- /dev/null +++ b/core/src/test/resources/configuration/migrate-no-emotes/plugin.yml @@ -0,0 +1,237 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. + # This option makes the Bedrock port the same as the Java port every time you start the server. + clone-remote-port: false + +# Network settings for the Java server connection +java: + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # Whether to use server API methods to determine the Java server's MOTD and ping passthrough. + # There is no need to disable this unless your MOTD or player count does not appear properly. + integrated-ping-passthrough: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + # Only relevant if integrated-ping-passthrough is disabled. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: false + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 0 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Whether to connect directly into the Java server without creating a TCP connection. + # This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser. + # If enabled, the remote address and port sections are ignored. + # If disabled, expect performance decrease and latency increase. + use-direct-connection: true + + # Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players. + # This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network. + # This requires use-direct-connection to be true. + disable-compression: true + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/core/src/test/resources/configuration/migrate-no-emotes/remote.yml b/core/src/test/resources/configuration/migrate-no-emotes/remote.yml new file mode 100644 index 000000000..3ca312663 --- /dev/null +++ b/core/src/test/resources/configuration/migrate-no-emotes/remote.yml @@ -0,0 +1,236 @@ +# -------------------------------- +# Geyser Configuration File +# +# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. +# +# GitHub: https://github.com/GeyserMC/Geyser +# Discord: https://discord.gg/geysermc +# Wiki: https://geysermc.org/wiki +# +# NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated. +# In most cases, especially with server hosting providers, further hosting-specific configuration is required. +# -------------------------------- + +# Network settings for the Bedrock listener +bedrock: + # The IP address that Geyser will bind on to listen for incoming Bedrock connections. + # Generally, you should only change this if you want to limit what IPs can connect to your server. + address: 0.0.0.0 + + # The port that will Geyser will listen on for incoming Bedrock connections. + # Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic. + port: 19132 + +# Network settings for the Java server connection +java: + # The IP address of the Java Edition server. + address: 127.0.0.1 + + # The port of the Java Edition server. + port: 25565 + + # What type of authentication Bedrock players will be checked against when logging into the Java server. + # Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline". + auth-type: online + + # Whether to forward the hostname that the Bedrock client used to connect over to the Java server. + # This is designed to be used for forced hosts on proxies. + forward-hostname: false + +# MOTD settings +motd: + # 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" + primary-motd: Geyser + secondary-motd: Another Geyser server. + + # Whether Geyser should relay the MOTD from the Java server to Bedrock players. + passthrough-motd: true + + # Maximum amount of players that can connect. + # This is only visual, and is only applied if passthrough-motd is disabled. + max-players: 100 + + # Whether to relay the player count and max players from the Java server to Bedrock players. + passthrough-player-counts: true + + # How often to ping the Java server to refresh MOTD and player count, in seconds. + ping-passthrough-interval: 3 + +# Gameplay options that affect Bedrock players +gameplay: + # The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. + server-name: Geyser + + # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. + # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: + # https://geysermc.org/img/external/cooldown_indicator.png + # This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 + # This setting can be set to "title", "actionbar" or "false" + show-cooldown: title + + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. + # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. + command-suggestions: true + + # Controls if coordinates are shown to players. + show-coordinates: true + + # Whether Bedrock players are blocked from performing their scaffolding-style bridging. + disable-bedrock-scaffolding: false + + # Bedrock prevents building and displaying blocks above Y127 in the Nether. + # This config option works around that by changing the Nether dimension ID to the End ID. + # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. + nether-roof-workaround: false + + # Whether to show Bedrock Edition emotes to other Bedrock Edition players. + show-emotes: false + + # Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative, + # or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item. + # This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:" + unusable-space-block: minecraft:barrier + + # Whether to add any items and blocks which normally does not exist in Bedrock Edition. + # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. + # If this is disabled, furnace minecart items will be mapped to hopper minecart items. + # Geyser's block, item, and skull mappings systems will also be disabled. + # This option requires a restart of Geyser in order to change its setting. + enable-custom-content: true + + # Force clients to load all resource packs if there are any. + # If set to false, it allows the user to connect to the server even if they don't + # want to download the resource packs. + force-resource-packs: true + + # Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + # If enabled, force-resource-packs will be enabled. + enable-integrated-pack: true + + # Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate + # ping, it may also cause players to time out more easily. + forward-player-ping: false + + # Allows Xbox achievements to be unlocked. + # If a player types in an unknown command, they will receive a message that states cheats are disabled. + # Otherwise, commands work as expected. + xbox-achievements-enabled: false + + # The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices. + # A value of 0 will disable all custom skulls. + # Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number. + max-visible-custom-skulls: 0 + + # The radius in blocks around the player in which custom skulls are displayed. + custom-skull-render-distance: 32 + +# The default locale if we don't have the one the client requested. If set to "system", the system's language will be used. +default-locale: system + +# Whether player IP addresses will be logged by the server. +log-player-ip-addresses: true + +# For online mode authentication type only. +# Stores a list of Bedrock player usernames that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough + +# For online mode authentication type only. +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + +# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version +# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms +# auto-update. +notify-on-new-bedrock-update: true + +# Advanced configuration options. These usually do not need modifications. +advanced: + # Specify how many days player skin images will be cached to disk to save downloading them from the internet. + # A value of 0 is disabled. (Default: 0) + cache-images: 0 + + # Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle + # a lot of scoreboard packets per second, this can cause serious lag. + # This option allows you to specify after how many Scoreboard packets per seconds + # the Scoreboard updates will be limited to four updates per second. + scoreboard-packet-threshold: 20 + + # Whether Geyser should send team names in command suggestions. + # Disable this if you have a lot of teams used that you don't need as suggestions. + add-team-suggestions: true + + # A list of remote resource pack urls to send to the Bedrock client for downloading. + # The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/ + resource-pack-urls: [] + + # 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. + floodgate-key-file: key.pem + + # Advanced networking options for the Geyser to Java server connection + java: + # Whether to enable HAPROXY protocol when connecting to the Java server. + # This is useful only when: + # 1) Your Java server supports HAPROXY protocol (it probably doesn't) + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # Advanced networking options for Geyser's Bedrock listener + bedrock: + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # A value of 0 will broadcast the port specified above. + # DO NOT change this unless Geyser runs on a different port than the one that is used to connect. + broadcast-port: 0 + + # 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. + compression-level: 6 + + # Whether to expect HAPROXY protocol for connecting Bedrock clients. + # This is useful only when you are running a UDP reverse proxy in front of your Geyser instance. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! + use-haproxy-protocol: false + + # A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # IP addresses, subnets, and links to plain text files are supported. + haproxy-protocol-whitelisted-ips: [] + + # The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation. + # 1400 is the default. + mtu: 1400 + + # This option disables the auth step Geyser performs for connecting Bedrock players. + # It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users + # cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for + # assistance - and use UDP instead of TCP. + # Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk. + # All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled. + validate-bedrock-login: true + +# bStats is a stat tracker that is entirely anonymous and tracks only basic information +# about Geyser, such as how many people are online, how many servers are using Geyser, +# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. +# https://bstats.org/plugin/server-implementation/GeyserMC +enable-metrics: true + +# The bstats metrics uuid. Do not touch! +metrics-uuid: 00000000-0000-0000-0000-000000000000 + +# If debug messages should be sent through console +debug-mode: false + +# Do not change! +config-version: 5 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8deff33ad..a13c67548 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,16 @@ [versions] base-api = "1.0.2" +bstats = "3.1.0" cumulus = "1.1.2" +configurate = "4.2.0-GeyserMC-20251111.004649-11" erosion = "1.1-20240521.000109-3" events = "1.1-SNAPSHOT" -jackson = "2.17.0" +yaml = "2.2" fastutil = "8.5.15-SNAPSHOT" netty = "4.2.7.Final" guava = "29.0-jre" -gson = "2.3.1" # Provided by Spigot 1.8.8 +gson = "2.3.1" # Provided by Spigot 1.8.8 TODO bump to 2.8.1 or similar (Spigot 1.16.5 version) after Merge +gson-runtime = "2.10.1" websocket = "1.5.1" protocol-connection = "3.0.0.Beta10-20251014.180344-2" protocol-common = "3.0.0.Beta10-20251014.180344-2" @@ -63,10 +66,11 @@ erosion-bukkit-common = { group = "org.geysermc.erosion", name = "bukkit-common" erosion-bukkit-nms = { group = "org.geysermc.erosion", name = "bukkit-nms", version.ref = "erosion" } erosion-common = { group = "org.geysermc.erosion", name = "common", version.ref = "erosion" } -jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" } -jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" } -jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson" } -jackson-dataformat-yaml = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml", version.ref = "jackson" } +yaml = { module = "org.yaml:snakeyaml", version.ref = "yaml" } + +configurate-interface-ap = { module = "org.spongepowered:configurate-extra-interface-ap", version.ref = "configurate" } +configurate-interface = { module = "org.spongepowered:configurate-extra-interface", version.ref = "configurate" } +configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } fastutil-int-int-maps = { group = "org.cloudburstmc.fastutil.maps", name = "int-int-maps", version.ref = "fastutil" } fastutil-int-long-maps = { group = "org.cloudburstmc.fastutil.maps", name = "int-long-maps", version.ref = "fastutil" } @@ -127,6 +131,8 @@ checker-qual = { group = "org.checkerframework", name = "checker-qual", version. commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +gson-runtime = { group = "com.google.code.gson", name = "gson", version.ref = "gson-runtime" } +gson-record-factory = { group = "com.github.Marcono1234", name = "gson-record-type-adapter-factory", version = "0.3.0" } junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = "minecraftauth" } mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" } @@ -144,6 +150,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"} + mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } # plugins @@ -161,7 +169,6 @@ runvelocity = { id = "xyz.jpenilla.run-velocity", version.ref = "runtask" } runpaper = { id = "xyz.jpenilla.run-paper", version.ref = "runtask" } [bundles] -jackson = [ "jackson-annotations", "jackson-databind", "jackson-dataformat-yaml" ] fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-long-object-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps", "fastutil-reference-object-maps" ] adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ] log4j = [ "log4j-api", "log4j-core", "log4j-slf4j2-impl", "log4j-iostreams", "log4j-jul" ]