1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Switch config system to Configurate (#5010)

* Start implementing Configurate config system

* More development

* Start migrating to Gson only

* Progress

* Update usage of WebUtils

* Most things now use Gson for JSON

* Allow tests to succeed by using new Gson version

* Use slightly cleaner version for Version deserializer

* Work around older Gson versions without record support

* GeyserCustomSkullConfiguration uses Configurate

* Fix regression in properties get

* New config used in core

* The configuration is gone. Long live the config.

* More changes and migrations away from Jackson

* Improve node ordering when updating configs

* typo

* Better check for ignoring non-configurate configs for considering comment moving

* Ensure metrics UUID is valid

* Initial advanced config

* Remove Jackson; finish config value placements

* Remove duplicate relocate declarations

* Let annotations work

* Renaming to PluginSpecific

* Use global bStats config where possible

* Fix test

* Re-introduce asterisk behavior in configs

* Remove GeyserPluginBootstrap as it's no longer necessary

* Remove old config.yml file

* Update Xbox achievement comment

* Apply suggestions from code review

Co-authored-by: chris <github@onechris.mozmail.com>

* No need to remove values anymore

* Fix: disable bstats relocation on platforms where it is not needed

* ensure it builds

* Update custom unavailable slot comment

Co-authored-by: chris <github@onechris.mozmail.com>

* Update cooldown image

* Logger message for direct-compression still being enabled

* oops

* More explicit RuntimeException message

Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com>

* Constant for 'system' locale

* Better config JSON encoding (something is broken with Cloudflare; we'll figure it out

* Fix broadcast port default

* Add this file too

* Update configurate branch

* fix build

* Fix: Allow using custom config file on Standalone, add relocation comment

* Move config loading to GeyserBootstrap interface

* Add and rename some config options, add section notes (#5390)

* Add and rename some config options, add section notes

* adjust message

* Update core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java

Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz>

* Update core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java

Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz>

* Update ConfigLoader.java

* Update AdvancedConfig.java

* clarify that we're talking about the HAProxy protocol

* rename config option to use-haproxy-protocol

* remove ominous warning sign on xbox auth warning

* adjust wording

---------

Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz>

* Back to one config file

* Some minor touchups

* Configurate: Sectionification (#5904)

* Init: config sections

* Start on adding tests, move migrations over to ConfigMigrations class

* Get rid of auth section again, rename that one config option, fix mtu migration

* Move custom skulls config options to the bottom of the gameplay settings

* Add more tests

* Rename and migrate proxy-protocol-whitelisted-ips to haproxy-protocol-whitelisted-ips

* Add automatic downloading of the GeyserOptionalPack

* Revert "Add automatic downloading of the GeyserOptionalPack"

This reverts commit 65b96208fb.

* Add more invalid config tests

* Warn about emote-offhand-workaround removal

* Add automatic loading of the GeyserOptionalPack (feature/configurate) (#5964)

* Add automatic downloading of the GeyserOptionalPack

* Warn about including the OptionalPack from extensions when Geyser is already including it instead of throwing.

* Copy optional pack instead of downloading

---------

Co-authored-by: onebeastchris <github@onechris.mozmail.com>

* Remove unused variable

* Start warning users not running Java 21

* Update tests, temporarily remove NumericRanges test

* Remove duplicate advanced section from Geyser dump

* Address some "reviews"

* yeet md5 hash from geyser dump

* Add info about number of resource packs / amount of mappings into Geyser dump

* Re-enable invalid config loading test

* Fix: allow-custom-skulls migration

* Fix test

* Add "enable-emotes" configuration option

* Rename "emotes-enabled" to "show-emotes"

* Only enable integrated pack when optional pack isn't present

* Update integrated pack

* Exclude jackson annotations, remove leftover debug print

* Remove one-time config migration warnings as we don't have access to the logger at that stage

* Throw more detailed descriptive error when loading resource packs from the "packs" folder, add another legacy config test

* Fix NeoForge's fun module conflict

* Re-add warning about moved functionality, fix Geyser-ViaProxy

This reverts commit fbadfa574a.

* oops

* Move GeyserLegacyPingPassthrough to separate thread to avoid Standalone command locking issues

---------

Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com>
Co-authored-by: chris <github@onechris.mozmail.com>
Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz>
Co-authored-by: Aurora <auroranova8756@gmail.com>
This commit is contained in:
Camotoy
2025-11-18 11:55:12 -05:00
committed by GitHub
parent 48bf0942e9
commit 765128ce42
157 changed files with 8006 additions and 2670 deletions

View File

@@ -15,11 +15,12 @@ dependencies {
} }
platformRelocate("net.md_5.bungee.jni") platformRelocate("net.md_5.bungee.jni")
platformRelocate("com.fasterxml.jackson")
platformRelocate("net.kyori") platformRelocate("net.kyori")
platformRelocate("org.incendo") 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.yaml") // Broken as of 1.20
platformRelocate("org.spongepowered")
platformRelocate("org.bstats")
// These dependencies are already present on the platform // These dependencies are already present on the platform
provided(libs.bungeecord.proxy) provided(libs.bungeecord.proxy)

View File

@@ -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();
}
}
}
}

View File

@@ -157,7 +157,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
listenerInfo.isPingPassthrough(), listenerInfo.isPingPassthrough(),
listenerInfo.getQueryPort(), listenerInfo.getQueryPort(),
listenerInfo.isQueryEnabled(), 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 // The field that stores all listeners in BungeeCord
@@ -191,7 +191,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
} }
initChannel.invoke(channelInitializer, ch); initChannel.invoke(channelInitializer, ch);
if (bootstrap.getGeyserConfig().isDisableCompression()) { if (bootstrap.config().advanced().java().disableCompression()) {
ch.pipeline().addAfter(PipelineUtils.PACKET_ENCODER, "geyser-compression-disabler", ch.pipeline().addAfter(PipelineUtils.PACKET_ENCODER, "geyser-compression-disabler",
new GeyserBungeeCompressionDisabler()); new GeyserBungeeCompressionDisabler());
} }

View File

@@ -69,7 +69,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
try { try {
event = future.get(100, TimeUnit.MILLISECONDS); event = future.get(100, TimeUnit.MILLISECONDS);
} catch (Throwable cause) { } catch (Throwable cause) {
String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>"; String address = GeyserImpl.getInstance().config().logPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause); GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause);
return null; return null;
} }

View File

@@ -33,25 +33,25 @@ import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource; 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.dump.BootstrapDumpInfo;
import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
import org.geysermc.geyser.text.GeyserLocale; 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.CommandManager;
import org.incendo.cloud.bungee.BungeeCommandManager; import org.incendo.cloud.bungee.BungeeCommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.execution.ExecutionCoordinator;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -61,13 +61,12 @@ import java.nio.file.Paths;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private CommandRegistry commandRegistry; private CommandRegistry commandRegistry;
private GeyserBungeeConfiguration geyserConfig; private GeyserPluginConfig geyserConfig;
private GeyserBungeeInjector geyserInjector; private GeyserBungeeInjector geyserInjector;
private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger()); private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
private IGeyserPingPassthrough geyserBungeePingPassthrough; private IGeyserPingPassthrough geyserBungeePingPassthrough;
@@ -102,12 +101,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
System.setProperty("Mcpl.io_uring", "true"); System.setProperty("Mcpl.io_uring", "true");
} }
if (!this.loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode()); this.geyser = GeyserImpl.load(this);
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
this.geyserInjector = new GeyserBungeeInjector(this); this.geyserInjector = new GeyserBungeeInjector(this);
// Registration of listeners occurs only once // Registration of listeners occurs only once
@@ -168,16 +166,15 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
public void onGeyserEnable() { public void onGeyserEnable() {
if (GeyserImpl.getInstance().isReloading()) { if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} }
// Force-disable query if enabled, or else Geyser won't enable // Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) { for (ListenerInfo info : getProxy().getConfig().getListeners()) {
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) { if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.bedrock().port()) {
try { try {
Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled"); Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled");
queryField.setAccessible(true); queryField.setAccessible(true);
@@ -195,7 +192,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
GeyserImpl.start(); GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) { if (!geyserConfig.motd().integratedPingPassthrough()) {
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else { } else {
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
@@ -232,8 +229,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
} }
@Override @Override
public GeyserBungeeConfiguration getGeyserConfig() { public @NonNull PlatformType platformType() {
return geyserConfig; return PlatformType.BUNGEECORD;
}
@Override
public GeyserPluginConfig config() {
return this.geyserConfig;
} }
@Override @Override
@@ -290,11 +292,29 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
@Override @Override
public boolean testFloodgatePluginPresent() { public boolean testFloodgatePluginPresent() {
if (getProxy().getPluginManager().getPlugin("floodgate") != null) { return getProxy().getPluginManager().getPlugin("floodgate") != null;
geyserConfig.loadFloodgate(this); }
return true;
@Override
public Path getFloodgateKeyPath() {
Plugin floodgate = getProxy().getPluginManager().getPlugin("floodgate");
Path geyserDataFolder = getDataFolder().toPath();
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger);
}
@Override
public MetricsPlatform createMetricsPlatform() {
try {
return new BungeeMetrics(this);
} catch (IOException e) {
this.geyserLogger.debug("Integrated bStats support failed to load.");
if (this.config().debugMode()) {
e.printStackTrace();
}
return null;
} }
return false;
} }
private Optional<InetSocketAddress> findCompatibleListener() { private Optional<InetSocketAddress> findCompatibleListener() {
@@ -303,20 +323,4 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
.map(info -> (InetSocketAddress) info.getSocketAddress()) .map(info -> (InetSocketAddress) info.getSocketAddress())
.findFirst(); .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;
}
} }

View File

@@ -38,7 +38,7 @@ public final class GeyserBungeeUpdateListener implements Listener {
@EventHandler @EventHandler
public void onPlayerJoin(final PostLoginEvent event) { public void onPlayerJoin(final PostLoginEvent event) {
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
final ProxiedPlayer player = event.getPlayer(); final ProxiedPlayer player = event.getPlayer();
if (player.hasPermission(Permissions.CHECK_UPDATE)) { if (player.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player)); VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player));

View File

@@ -17,7 +17,7 @@ dependencies {
shadowBundle(projects.core) shadowBundle(projects.core)
includeTransitive(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 // Avoids fabric complaining about non-SemVer versioning
shadowBundle(libs.protocol.connection) shadowBundle(libs.protocol.connection)
shadowBundle(libs.protocol.common) shadowBundle(libs.protocol.common)

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.fabric;
import com.google.gson.annotations.JsonAdapter;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
@@ -48,7 +49,7 @@ public class GeyserFabricDumpInfo extends BootstrapDumpInfo {
private final String minecraftVersion; private final String minecraftVersion;
private final EnvType environmentType; private final EnvType environmentType;
@AsteriskSerializer.Asterisk(isIp = true) @JsonAdapter(value = AsteriskSerializer.class)
private final String serverIP; private final String serverIP;
private final int serverPort; private final int serverPort;
private final boolean onlineMode; private final boolean onlineMode;

View File

@@ -73,7 +73,7 @@ public class GeyserFabricPlatform implements GeyserModPlatform {
Optional<ModContainer> floodgate = FabricLoader.getInstance().getModContainer("floodgate"); Optional<ModContainer> floodgate = FabricLoader.getInstance().getModContainer("floodgate");
if (floodgate.isPresent()) { if (floodgate.isPresent()) {
Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate"); Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate");
bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder); bootstrap.loadFloodgate(floodgateDataFolder);
return true; return true;
} }

View File

@@ -13,9 +13,6 @@ architectury {
provided("org.cloudburstmc.math", "api") provided("org.cloudburstmc.math", "api")
provided("com.google.errorprone", "error_prone_annotations") 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 { dependencies {
// See https://github.com/google/guava/issues/6618 // See https://github.com/google/guava/issues/6618
modules { modules {
@@ -30,15 +27,12 @@ dependencies {
shadowBundle(project(path = ":mod", configuration = "transformProductionNeoForge")) shadowBundle(project(path = ":mod", configuration = "transformProductionNeoForge"))
shadowBundle(projects.core) 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 // Let's shade in our own api
shadowBundle(projects.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 // cannot be shaded, since neoforge will complain if floodgate-neoforge tries to provide this
include(projects.common) include(projects.common)
@@ -61,11 +55,6 @@ tasks {
remapModrinthJar { remapModrinthJar {
archiveBaseName.set("geyser-neoforge") archiveBaseName.set("geyser-neoforge")
} }
shadowJar {
// Without this, jackson's service files are not relocated
mergeServiceFiles()
}
} }
modrinth { modrinth {

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.neoforge; package org.geysermc.geyser.platform.neoforge;
import com.google.gson.annotations.JsonAdapter;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@@ -47,7 +48,7 @@ public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo {
private final String minecraftVersion; private final String minecraftVersion;
private final Dist dist; private final Dist dist;
@AsteriskSerializer.Asterisk(isIp = true) @JsonAdapter(value = AsteriskSerializer.class)
private final String serverIP; private final String serverIP;
private final int serverPort; private final int serverPort;
private final boolean onlineMode; private final boolean onlineMode;

View File

@@ -72,7 +72,7 @@ public class GeyserNeoForgePlatform implements GeyserModPlatform {
public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) { public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
if (ModList.get().isLoaded("floodgate")) { if (ModList.get().isLoaded("floodgate")) {
Path floodgateDataFolder = FMLPaths.CONFIGDIR.get().resolve("floodgate"); Path floodgateDataFolder = FMLPaths.CONFIGDIR.get().resolve("floodgate");
bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder); bootstrap.loadFloodgate(floodgateDataFolder);
return true; return true;
} }
return false; return false;

View File

@@ -31,11 +31,13 @@ import lombok.Setter;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; 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.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; 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.platform.GeyserModPlatform;
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager; import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale; 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.io.InputStream;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.UUID;
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class GeyserModBootstrap implements GeyserBootstrap { public abstract class GeyserModBootstrap implements GeyserBootstrap {
@@ -69,7 +67,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
@Setter @Setter
private CommandRegistry commandRegistry; private CommandRegistry commandRegistry;
private GeyserModConfiguration geyserConfig; private GeyserPluginConfig geyserConfig;
private GeyserModInjector geyserInjector; private GeyserModInjector geyserInjector;
private final GeyserModLogger geyserLogger = new GeyserModLogger(); private final GeyserModLogger geyserLogger = new GeyserModLogger();
private IGeyserPingPassthrough geyserPingPassthrough; private IGeyserPingPassthrough geyserPingPassthrough;
@@ -80,12 +78,11 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
instance = this; instance = this;
dataFolder = this.platform.dataFolder(this.platform.configPath()); dataFolder = this.platform.dataFolder(this.platform.configPath());
GeyserLocale.init(this); GeyserLocale.init(this);
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode()); this.geyser = GeyserImpl.load(this);
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
} }
public void onGeyserEnable() { public void onGeyserEnable() {
@@ -95,16 +92,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
} }
if (GeyserImpl.getInstance().isReloading()) { if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} }
GeyserImpl.start(); GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) { if (!geyserConfig.motd().integratedPingPassthrough()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else { } else {
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger); this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
@@ -145,7 +141,12 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
} }
@Override @Override
public GeyserModConfiguration getGeyserConfig() { public @NonNull PlatformType platformType() {
return this.platform.platformType();
}
@Override
public GeyserPluginConfig config() {
return geyserConfig; return geyserConfig;
} }
@@ -199,12 +200,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
@Override @Override
public int getServerPort() { public int getServerPort() {
if (isServer()) {
return ((GeyserServerPortGetter) server).geyser$getServerPort(); return ((GeyserServerPortGetter) server).geyser$getServerPort();
} else {
// Set in the IntegratedServerMixin
return geyserConfig.getRemote().port();
}
} }
public abstract boolean isServer(); public abstract boolean isServer();
@@ -214,31 +210,23 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.platform.testFloodgatePluginPresent(this); 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 @Nullable
@Override @Override
public InputStream getResourceOrNull(String resource) { public InputStream getResourceOrNull(String resource) {
return this.platform.resolveResource(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 @Override
public @NonNull String getServerPlatform() { public @NonNull String getServerPlatform() {
return server.getServerModName(); return server.getServerModName();

View File

@@ -96,7 +96,7 @@ public class GeyserModInjector extends GeyserInjector {
int index = ch.pipeline().names().indexOf("encoder"); int index = ch.pipeline().names().indexOf("encoder");
String baseName = index != -1 ? "encoder" : "outbound_config"; 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()); ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserModCompressionDisabler());
} }
} }
@@ -125,7 +125,7 @@ public class GeyserModInjector extends GeyserInjector {
childHandler = (ChannelInitializer<Channel>) childHandlerField.get(handler); childHandler = (ChannelInitializer<Channel>) childHandlerField.get(handler);
break; break;
} catch (Exception e) { } 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!"); bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -34,7 +34,7 @@ import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener { public final class GeyserModUpdateListener {
public static void onPlayReady(ServerPlayer player) { public static void onPlayReady(ServerPlayer player) {
// We could just not register the listener, but, this allows config reloading // 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. // 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. // 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()); ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());

View File

@@ -56,14 +56,13 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
// If the LAN is opened, starts Geyser. // If the LAN is opened, starts Geyser.
GeyserModBootstrap instance = GeyserModBootstrap.getInstance(); GeyserModBootstrap instance = GeyserModBootstrap.getInstance();
instance.setServer((MinecraftServer) (Object) this); instance.setServer((MinecraftServer) (Object) this);
instance.getGeyserConfig().getRemote().setPort(port);
instance.onGeyserEnable(); instance.onGeyserEnable();
// Ensure player locale has been loaded, in case it's different from Java system language // Ensure player locale has been loaded, in case it's different from Java system language
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
// Give indication that Geyser is loaded // Give indication that Geyser is loaded
Objects.requireNonNull(this.minecraft.player); Objects.requireNonNull(this.minecraft.player);
this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start", this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start.ip_suppressed",
this.minecraft.options.languageCode, "localhost", String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false); this.minecraft.options.languageCode, String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false);
} }
} }

View File

@@ -31,17 +31,25 @@ dependencies {
compileOnly(libs.folia.api) compileOnly(libs.folia.api)
compileOnlyApi(libs.viaversion) compileOnlyApi(libs.viaversion)
// For 1.16.5/1.17.1
implementation(libs.gson.record.factory) {
isTransitive = false
}
} }
platformRelocate("it.unimi.dsi.fastutil") platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("com.fasterxml.jackson")
// Relocate net.kyori but exclude the component logger // Relocate net.kyori but exclude the component logger
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
platformRelocate("org.objectweb.asm") platformRelocate("org.objectweb.asm")
platformRelocate("me.lucko.commodore") platformRelocate("me.lucko.commodore")
platformRelocate("org.incendo") 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.yaml") // Broken as of 1.20
platformRelocate("org.spongepowered")
platformRelocate("marcono1234.gson")
platformRelocate("org.bstats")
provided(libs.viaversion) provided(libs.viaversion)
tasks.withType<Jar> { tasks.withType<Jar> {

View File

@@ -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());
}
}

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.spigot; package org.geysermc.geyser.platform.spigot;
import com.google.gson.annotations.JsonAdapter;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -42,7 +43,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
private final String platformAPIVersion; private final String platformAPIVersion;
private final boolean onlineMode; private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true) @JsonAdapter(value = AsteriskSerializer.class)
private final String serverIP; private final String serverIP;
private final int serverPort; private final int serverPort;
private final List<PluginInfo> plugins; private final List<PluginInfo> plugins;

View File

@@ -25,11 +25,13 @@
package org.geysermc.geyser.platform.spigot; 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 com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap; 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.channel.local.LocalAddress;
import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.DefaultThreadFactory;
import org.bukkit.Bukkit; 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.GeyserInjector;
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
import org.geysermc.geyser.network.netty.LocalSession; 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.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -123,7 +127,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
int index = ch.pipeline().names().indexOf("encoder"); int index = ch.pipeline().names().indexOf("encoder");
String baseName = index != -1 ? "encoder" : "outbound_config"; 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()); ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
} }
} }
@@ -158,7 +162,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
} }
break; break;
} catch (Exception e) { } 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!"); bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
e.printStackTrace(); e.printStackTrace();
} }
@@ -178,8 +182,8 @@ public class GeyserSpigotInjector extends GeyserInjector {
private void workAroundWeirdBug(GeyserBootstrap bootstrap) { private void workAroundWeirdBug(GeyserBootstrap bootstrap) {
MinecraftProtocol protocol = new MinecraftProtocol(); MinecraftProtocol protocol = new MinecraftProtocol();
LocalSession session = new LocalSession(this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, Runnable::run); 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_HOST, bootstrap.config().java().address());
session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.getGeyserConfig().getRemote().port()); session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.config().java().port());
session.connect(); session.connect();
} }

View File

@@ -38,9 +38,11 @@ import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.permissions.Permission; import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault; import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.adapters.paper.PaperAdapters; 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.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource; 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.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol; 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.GeyserSpigotNativeWorldManager;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.text.GeyserLocale; 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.bukkit.BukkitCommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.LegacyPaperCommandManager; import org.incendo.cloud.paper.LegacyPaperCommandManager;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
private CommandRegistry commandRegistry; private CommandRegistry commandRegistry;
private GeyserSpigotConfiguration geyserConfig; private GeyserPluginConfig geyserConfig;
private GeyserSpigotInjector geyserInjector; private GeyserSpigotInjector geyserInjector;
private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ? private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger()); new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger());
@@ -161,16 +160,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// no-op // no-op
} }
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
// We'll disable ourselves later
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Turn "(MC: 1.16.4)" into 1.16.4. // Turn "(MC: 1.16.4)" into 1.16.4.
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this); this.geyser = GeyserImpl.load(this);
} }
@Override @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 // 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 // extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
if (GeyserImpl.getInstance().isReloading()) { if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} }
GeyserImpl.start(); GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) { if (!geyserConfig.motd().integratedPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else { } else {
if (ReflectedNames.checkPaperPingEvent()) { 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()); geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName());
} catch (Throwable e) { } 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. :)"); geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
e.printStackTrace(); e.printStackTrace();
} }
@@ -363,8 +362,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
@Override @Override
public GeyserSpigotConfiguration getGeyserConfig() { public @NonNull PlatformType platformType() {
return geyserConfig; return PlatformType.SPIGOT;
}
@Override
public GeyserPluginConfig config() {
return this.geyserConfig;
} }
@Override @Override
@@ -455,32 +459,21 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override @Override
public boolean testFloodgatePluginPresent() { public boolean testFloodgatePluginPresent() {
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) { return Bukkit.getPluginManager().getPlugin("floodgate") != null;
geyserConfig.loadFloodgate(this);
return true;
}
return false;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @Override
private boolean loadConfig() { public Path getFloodgateKeyPath() {
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
try { Path geyserDataFolder = getDataFolder().toPath();
if (!getDataFolder().exists()) { Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
//noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir(); return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger);
}
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;
} }
return true; @Override
public MetricsPlatform createMetricsPlatform() {
return new SpigotMetrics(this);
} }
private void warnInvalidProxySetups(String platform) { private void warnInvalidProxySetups(String platform) {

View File

@@ -38,7 +38,7 @@ public final class GeyserSpigotUpdateListener implements Listener {
@EventHandler @EventHandler
public void onPlayerJoin(final PlayerJoinEvent event) { public void onPlayerJoin(final PlayerJoinEvent event) {
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
final Player player = event.getPlayer(); final Player player = event.getPlayer();
if (player.hasPermission(Permissions.CHECK_UPDATE)) { if (player.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player)); VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player));

View File

@@ -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);
}
}

View File

@@ -15,6 +15,8 @@ dependencies {
implementation(libs.bundles.jline) implementation(libs.bundles.jline)
implementation(libs.bundles.log4j) implementation(libs.bundles.log4j)
implementation(libs.gson.runtime)
} }
application { application {

View File

@@ -25,11 +25,6 @@
package org.geysermc.geyser.platform.standalone; 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 io.netty.util.ResourceLeakDetector;
import lombok.Getter; import lombok.Getter;
import org.apache.logging.log4j.Level; 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.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.ConfigLoader;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.configuration.GeyserConfig;
import org.geysermc.geyser.configuration.GeyserRemoteConfig;
import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI; import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.LoopbackUtil; 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.File;
import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class GeyserStandaloneBootstrap implements GeyserBootstrap { public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private StandaloneCloudCommandManager cloud; private StandaloneCloudCommandManager cloud;
private CommandRegistry commandRegistry; private CommandRegistry commandRegistry;
private GeyserStandaloneConfiguration geyserConfig; private GeyserConfig geyserConfig;
private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger(); private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
private IGeyserPingPassthrough geyserPingPassthrough; private IGeyserPingPassthrough geyserPingPassthrough;
private GeyserStandaloneGUI gui; private GeyserStandaloneGUI gui;
@@ -80,9 +72,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private GeyserImpl geyser; private GeyserImpl geyser;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final Map<NodePath, String> argsConfigKeys = new HashMap<>();
private static final Map<String, String> argsConfigKeys = new HashMap<>();
public static void main(String[] args) { public static void main(String[] args) {
if (System.getProperty("io.netty.leakDetection.level") == null) { if (System.getProperty("io.netty.leakDetection.level") == null) {
@@ -99,8 +89,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
GeyserLocale.init(bootstrap); GeyserLocale.init(bootstrap);
List<BeanPropertyDefinition> availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class);
for (int i = 0; i < args.length; i++) { 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 // 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 // Optionally, you can force the use of a GUI or no GUI by specifying args
@@ -132,36 +120,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
// Split the argument by an = // Split the argument by an =
String[] argParts = arg.substring(2).split("="); String[] argParts = arg.substring(2).split("=");
if (argParts.length == 2) { if (argParts.length == 2) {
// Split the config key by . to allow for nested options argsConfigKeys.put(NodePath.of(argParts[0].split("\\.")), argParts[1]);
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; break;
} }
} }
} else {
found = true;
}
break;
}
}
// Add the found key to the stored list for later usage
if (found) {
argsConfigKeys.put(argParts[0], argParts[1]);
break;
}
}
}
System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg));
return; return;
} }
@@ -189,19 +151,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
@Override @Override
public void onGeyserEnable() { public void onGeyserEnable() {
try { this.geyserConfig = loadConfig(GeyserRemoteConfig.class);
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", if (this.geyserConfig == null) {
(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);
if (gui == null) { if (gui == null) {
System.exit(1); System.exit(1);
} else { } else {
@@ -209,13 +160,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
return; return;
} }
} }
geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Allow libraries like Protocol to have their debug information passthrough // 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(); boolean reloading = geyser.isReloading();
if (!reloading) { if (!reloading) {
@@ -245,6 +194,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
geyserLogger.start(); geyserLogger.start();
} }
@Override
public <T extends GeyserConfig> T loadConfig(Class<T> configClass) {
return new ConfigLoader(this)
.configFile(new File(getConfigFolder().toFile(), configFilename))
.transformer(this::handleArgsConfigOptions)
.load(configClass);
}
/** /**
* Check using {@link java.awt.GraphicsEnvironment} that we are a headless client * Check using {@link java.awt.GraphicsEnvironment} that we are a headless client
* *
@@ -273,8 +230,13 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
} }
@Override @Override
public GeyserConfiguration getGeyserConfig() { public @NonNull PlatformType platformType() {
return geyserConfig; return PlatformType.STANDALONE;
}
@Override
public GeyserConfig config() {
return this.geyserConfig;
} }
@Override @Override
@@ -330,100 +292,47 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
return false; return false;
} }
/** @Override
* Get the {@link BeanPropertyDefinition}s for the given class public Path getFloodgateKeyPath() {
* return Path.of(geyserConfig.advanced().floodgateKeyFile());
* @param clazz The class to get the definitions for
* @return A list of {@link BeanPropertyDefinition} for the given class
*/
public static List<BeanPropertyDefinition> getPOJOForClass(Class<?> clazz) {
JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz);
// Introspect the given type
BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType);
// Find properties
List<BeanPropertyDefinition> properties = beanDescription.findProperties();
// Get the ignored properties
Set<String> ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector()
.findPropertyIgnoralByName(OBJECT_MAPPER.getSerializationConfig(), beanDescription.getClassInfo()).getIgnored();
// Filter properties removing the ignored ones
return properties.stream()
.filter(property -> !ignoredProperties.contains(property.getName()))
.collect(Collectors.toList());
} }
/** /**
* Set a POJO property value on an object * 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 * @param value The new value of the property
*/ */
@SuppressWarnings({"unchecked", "rawtypes"}) // Required for enum usage private static void setConfigOption(CommentedConfigurationNode node, Object value) throws SerializationException {
private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) {
Object parsedValue = value; Object parsedValue = value;
// Change the values type if needed // 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); parsedValue = Integer.valueOf((String) parsedValue);
} else if (boolean.class.equals(property.getRawPrimaryType())) { } else if (Boolean.class == clazz) {
parsedValue = Boolean.valueOf((String) parsedValue); parsedValue = Boolean.valueOf((String) parsedValue);
} else if (Enum.class.isAssignableFrom(property.getRawPrimaryType())) {
parsedValue = Enum.valueOf((Class<? extends Enum>) property.getRawPrimaryType(), ((String) parsedValue).toUpperCase(Locale.ROOT));
} }
// Force the value to be set node.set(parsedValue);
AnnotatedField field = property.getField();
field.fixAccess(true);
field.setValue(parentObject, 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() { private void handleArgsConfigOptions(CommentedConfigurationNode node) {
// Get the available properties from the class for (Map.Entry<NodePath, String> configKey : argsConfigKeys.entrySet()) {
List<BeanPropertyDefinition> availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); NodePath path = configKey.getKey();
CommentedConfigurationNode subNode = node.node(path);
if (subNode.virtual()) {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", path));
continue;
}
for (Map.Entry<String, String> configKey : argsConfigKeys.entrySet()) {
String[] configKeyParts = configKey.getKey().split("\\.");
// Loop over the properties looking for any matches against the stored one from the argument
for (BeanPropertyDefinition property : availableProperties) {
if (configKeyParts[0].equals(property.getName())) {
if (configKeyParts.length > 1) {
// Loop through the sub property if the first part matches
for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) {
if (configKeyParts[1].equals(subProperty.getName())) {
geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue()));
// Set the sub property value on the config
try { try {
Object subConfig = property.getGetter().callOn(geyserConfig); setConfigOption(subNode, configKey.getValue());
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())); geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue()));
} catch (SerializationException e) {
// Set the property value on the config geyserLogger.error("Failed to set config option: " + path);
try {
setConfigOption(property, geyserConfig, configKey.getValue());
} catch (Exception e) {
geyserLogger.error("Failed to set config option: " + property.getFullName());
}
}
break;
}
} }
} }
} }

View File

@@ -32,6 +32,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.io.IoBuilder; import org.apache.logging.log4j.io.IoBuilder;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommandSource;
@@ -112,7 +113,12 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override @Override
public void debug(String message) { 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 @Override

View File

@@ -16,12 +16,13 @@ dependencies {
api(libs.cloud.velocity) api(libs.cloud.velocity)
} }
platformRelocate("com.fasterxml.jackson")
platformRelocate("it.unimi.dsi.fastutil") platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
platformRelocate("org.yaml") platformRelocate("org.yaml")
platformRelocate("org.spongepowered")
platformRelocate("org.bstats")
platformRelocate("org.incendo") 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 // These dependencies are already present on the platform
provided(libs.velocity.api) provided(libs.velocity.api)

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.velocity; package org.geysermc.geyser.platform.velocity;
import com.google.gson.annotations.JsonAdapter;
import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter; import lombok.Getter;
@@ -42,7 +43,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo {
private final String platformVendor; private final String platformVendor;
private final boolean onlineMode; private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true) @JsonAdapter(value = AsteriskSerializer.class)
private final String serverIP; private final String serverIP;
private final int serverPort; private final int serverPort;
private final List<PluginInfo> plugins; private final List<PluginInfo> plugins;

View File

@@ -128,7 +128,7 @@ public class GeyserVelocityInjector extends GeyserInjector {
protected void initChannel(@NonNull Channel ch) throws Exception { protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(channelInitializer, ch); 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", ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler",
new GeyserVelocityCompressionDisabler()); new GeyserVelocityCompressionDisabler());
} }

View File

@@ -39,31 +39,31 @@ import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter; import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource; 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.dump.BootstrapDumpInfo;
import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource;
import org.geysermc.geyser.text.GeyserLocale; 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.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.velocity.VelocityCommandManager; import org.incendo.cloud.velocity.VelocityCommandManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; 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") @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
public class GeyserVelocityPlugin implements GeyserBootstrap { public class GeyserVelocityPlugin implements GeyserBootstrap {
@@ -71,7 +71,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
private final ProxyServer proxyServer; private final ProxyServer proxyServer;
private final PluginContainer container; private final PluginContainer container;
private final GeyserVelocityLogger geyserLogger; private final GeyserVelocityLogger geyserLogger;
private GeyserVelocityConfiguration geyserConfig; private GeyserPluginConfig geyserConfig;
private GeyserVelocityInjector geyserInjector; private GeyserVelocityInjector geyserInjector;
private IGeyserPingPassthrough geyserPingPassthrough; private IGeyserPingPassthrough geyserPingPassthrough;
private CommandRegistry commandRegistry; private CommandRegistry commandRegistry;
@@ -106,13 +106,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
System.setProperty("Mcpl.io_uring", "true"); System.setProperty("Mcpl.io_uring", "true");
} }
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; 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); this.geyserInjector = new GeyserVelocityInjector(proxyServer);
// We need to register commands here, rather than in onGeyserEnable which is invoked during the appropriate ListenerBoundEvent. // 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; return;
} }
if (GeyserImpl.getInstance().isReloading()) { if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} }
GeyserImpl.start(); GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) { if (!geyserConfig.motd().integratedPingPassthrough()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else { } else {
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
@@ -178,7 +176,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
} }
@Override @Override
public GeyserVelocityConfiguration getGeyserConfig() { public @NonNull PlatformType platformType() {
return PlatformType.VELOCITY;
}
@Override
public GeyserPluginConfig config() {
return geyserConfig; return geyserConfig;
} }
@@ -250,27 +253,26 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
@Override @Override
public boolean testFloodgatePluginPresent() { public boolean testFloodgatePluginPresent() {
var floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); var floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
if (floodgate.isPresent()) { return floodgate.isPresent();
geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile());
return true;
}
return false;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @Override
private boolean loadConfig() { public Path getFloodgateKeyPath() {
try { Optional<PluginContainer> floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
if (!configFolder.toFile().exists()) Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null;
//noinspection ResultOfMethodCallIgnored return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataPath, configFolder, geyserLogger);
configFolder.toFile().mkdirs(); }
File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); @Override
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); public MetricsPlatform createMetricsPlatform() {
} catch (IOException ex) { try {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); return new VelocityMetrics(this.configFolder);
ex.printStackTrace(); } catch (IOException e) {
return false; this.geyserLogger.debug("Integrated bStats support failed to load.");
if (this.config().debugMode()) {
e.printStackTrace();
}
return null;
} }
return true;
} }
} }

View File

@@ -37,7 +37,7 @@ public final class GeyserVelocityUpdateListener {
@Subscribe @Subscribe
public void onPlayerJoin(PostLoginEvent event) { public void onPlayerJoin(PostLoginEvent event) {
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) { if (GeyserImpl.getInstance().config().notifyOnNewBedrockUpdate()) {
final Player player = event.getPlayer(); final Player player = event.getPlayer();
if (player.hasPermission(Permissions.CHECK_UPDATE)) { if (player.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player)); VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player));

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -25,28 +25,45 @@
package org.geysermc.geyser.platform.velocity; package org.geysermc.geyser.platform.velocity;
import com.fasterxml.jackson.annotation.JsonIgnore; import org.bstats.config.MetricsConfig;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.geysermc.geyser.util.metrics.MetricsPlatform;
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 java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
@Getter public final class VelocityMetrics implements MetricsPlatform {
@JsonIgnoreProperties(ignoreUnknown = true) private final MetricsConfig config;
public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore
private Path floodgateKeyPath;
public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) { public VelocityMetrics(Path dataDirectory) throws IOException {
Optional<PluginContainer> floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); // https://github.com/Bastian/bstats-metrics/blob/master/velocity/src/main/java/org/bstats/velocity/Metrics.java
Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null; File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile();
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataPath, dataFolder.toPath(), plugin.getGeyserLogger()); 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();
} }
} }

View File

@@ -12,8 +12,9 @@ platformRelocate("net.kyori")
platformRelocate("org.yaml") platformRelocate("org.yaml")
platformRelocate("it.unimi.dsi.fastutil") platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("org.cloudburstmc.netty") platformRelocate("org.cloudburstmc.netty")
platformRelocate("org.bstats")
platformRelocate("org.incendo") 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 // These dependencies are already present on the platform
provided(libs.viaproxy) provided(libs.viaproxy)

View File

@@ -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;
}
}

View File

@@ -24,6 +24,7 @@
*/ */
package org.geysermc.geyser.platform.viaproxy; package org.geysermc.geyser.platform.viaproxy;
import com.google.gson.annotations.JsonAdapter;
import lombok.Getter; import lombok.Getter;
import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.plugins.ViaProxyPlugin; import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
@@ -41,7 +42,7 @@ public class GeyserViaProxyDumpInfo extends BootstrapDumpInfo {
private final String platformVersion; private final String platformVersion;
private final boolean onlineMode; private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true) @JsonAdapter(value = AsteriskSerializer.class)
private final String serverIP; private final String serverIP;
private final int serverPort; private final int serverPort;
private final List<PluginInfo> plugins; private final List<PluginInfo> plugins;

View File

@@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.viaproxy;
import net.raphimc.viaproxy.cli.ConsoleFormatter; import net.raphimc.viaproxy.cli.ConsoleFormatter;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.command.GeyserCommandSource; 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 @Override
public void debug(String message, Object... arguments) { public void debug(String message, Object... arguments) {
if (this.debug) { if (this.debug) {

View File

@@ -36,7 +36,9 @@ import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; 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.plugins.events.types.ITyped;
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap; 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.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; 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.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListener; import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListener;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.LoopbackUtil; import org.geysermc.geyser.util.LoopbackUtil;
import org.spongepowered.configurate.serialize.SerializationException;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
@@ -67,10 +70,10 @@ import java.util.UUID;
public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap, EventRegistrar { 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 final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser"));
private GeyserViaProxyConfiguration config; private GeyserPluginConfig geyserConfig;
private GeyserImpl geyser; private GeyserImpl geyser;
private StandaloneCloudCommandManager cloud; private StandaloneCloudCommandManager cloud;
private CommandRegistry commandRegistry; private CommandRegistry commandRegistry;
@@ -79,10 +82,6 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
@Override @Override
public void onEnable() { public void onEnable() {
ROOT_FOLDER.mkdirs(); ROOT_FOLDER.mkdirs();
GeyserLocale.init(this);
this.onGeyserInitialize();
ViaProxy.EVENT_MANAGER.register(this); ViaProxy.EVENT_MANAGER.register(this);
} }
@@ -91,6 +90,12 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
this.onGeyserShutdown(); this.onGeyserShutdown();
} }
@EventHandler
private void onViaProxyLoaded(ViaProxyLoadedEvent event) {
GeyserLocale.init(this);
this.onGeyserInitialize();
}
@EventHandler @EventHandler
private void onConsoleCommand(final ConsoleCommandEvent event) { private void onConsoleCommand(final ConsoleCommandEvent event) {
final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand(); 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()) { if (event.getType() != ITyped.Type.POST || event.isLegacyPassthrough()) {
return; return;
} }
// TODO remove
if (System.getProperty("geyser.viaproxy.disableIpPassthrough") != null) { // Temporary until Configurate branch is merged if (System.getProperty("geyser.viaproxy.disableIpPassthrough") != null) { // Temporary until Configurate branch is merged
return; return;
} }
@@ -147,11 +153,12 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
@Override @Override
public void onGeyserInitialize() { public void onGeyserInitialize() {
if (!this.loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
this.geyser = GeyserImpl.load(PlatformType.VIAPROXY, this); this.geyser = GeyserImpl.load(this);
this.geyser.eventBus().register(this, new GeyserServerTransferListener()); this.geyser.eventBus().register(this, new GeyserServerTransferListener());
LoopbackUtil.checkAndApplyLoopback(this.logger); LoopbackUtil.checkAndApplyLoopback(this.logger);
} }
@@ -164,7 +171,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
} }
boolean reloading = geyser.isReloading(); boolean reloading = geyser.isReloading();
if (reloading) { if (reloading) {
if (!this.loadConfig()) { geyserConfig = loadConfig(GeyserPluginConfig.class);
if (geyserConfig == null) {
return; return;
} }
} else { } 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 // 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); this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser);
} }
if (this.config.getRemote().authType() == AuthType.FLOODGATE) { if (this.geyserConfig.java().authType() == AuthType.FLOODGATE) {
ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true); ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true);
} }
} }
@@ -201,8 +209,13 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
} }
@Override @Override
public GeyserConfiguration getGeyserConfig() { public @NonNull PlatformType platformType() {
return this.config; return PlatformType.VIAPROXY;
}
@Override
public GeyserPluginConfig config() {
return this.geyserConfig;
} }
@Override @Override
@@ -259,19 +272,36 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
return false; return false;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @Override
private boolean loadConfig() { public Path getFloodgateKeyPath() {
try { return new File(ROOT_FOLDER, geyserConfig.advanced().floodgateKeyFile()).toPath();
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 <T extends GeyserConfig> T loadConfig(Class<T> configClass) {
T config = new ConfigLoader(this)
.transformer(node -> {
try {
if (!ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE)) {
node.node("java", "forward-host").set(true);
}
var pingPassthroughInterval = node.node("ping-passthrough-interval");
int interval = pingPassthroughInterval.getInt();
if (interval < 15 && ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) {
// <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made
pingPassthroughInterval.set(15);
}
} catch (SerializationException e) {
throw new RuntimeException(e);
}
})
.configFile(new File(ROOT_FOLDER, "config.yml"))
.load(configClass);
if (config != null) {
this.geyserConfig = (GeyserPluginConfig) config;
config.java().authType(Files.isRegularFile(getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE);
}
return config;
}
} }

View File

@@ -14,19 +14,29 @@ dependencies {
api(projects.common) api(projects.common)
api(projects.api) api(projects.api)
// Jackson JSON and YAML serialization api(libs.yaml) // Used for extensions
api(libs.bundles.jackson) annotationProcessor(libs.configurate.`interface`.ap)
api(libs.configurate.`interface`)
implementation(libs.configurate.yaml)
api(libs.guava) api(libs.guava)
compileOnly(libs.gson.record.factory) {
isTransitive = false
}
// Fastutil Maps // Fastutil Maps
implementation(libs.bundles.fastutil) implementation(libs.bundles.fastutil)
// Network libraries // Network libraries
implementation(libs.websocket) 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) { api(libs.mcprotocollib) {
exclude("io.netty", "netty-all") exclude("io.netty", "netty-all")
exclude("net.raphimc", "MinecraftAuth") exclude("net.raphimc", "MinecraftAuth")
@@ -60,6 +70,7 @@ dependencies {
// Test // Test
testImplementation(libs.junit) testImplementation(libs.junit)
testImplementation(libs.gson.runtime) // Record support
testImplementation(libs.mockito) testImplementation(libs.mockito)
// Annotation Processors // Annotation Processors
@@ -68,6 +79,8 @@ dependencies {
annotationProcessor(projects.ap) annotationProcessor(projects.ap)
api(libs.events) api(libs.events)
api(libs.bstats)
} }
tasks.processResources { tasks.processResources {

View File

@@ -42,6 +42,10 @@ public final class Constants {
public static final String MINECRAFT_SKIN_SERVER_URL = "https://textures.minecraft.net/texture/"; 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 { static {
URI wsUri = null; URI wsUri = null;
try { try {

View File

@@ -25,14 +25,14 @@
package org.geysermc.geyser; package org.geysermc.geyser;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.configuration.GeyserConfig;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
public class FloodgateKeyLoader { 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. // 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 // This mostly prevents people from trying to copy the key and corrupting it in the process
if (floodgateDataFolder != null) { if (floodgateDataFolder != null) {
@@ -45,13 +45,7 @@ public class FloodgateKeyLoader {
} }
} }
Path floodgateKey; Path floodgateKey = geyserDataFolder.resolve(config.advanced().floodgateKeyFile());
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());
}
if (!Files.exists(floodgateKey)) { if (!Files.exists(floodgateKey)) {
logger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed")); logger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed"));

View File

@@ -27,12 +27,16 @@ package org.geysermc.geyser;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; 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.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; 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.io.InputStream;
import java.net.SocketAddress; import java.net.SocketAddress;
@@ -68,11 +72,19 @@ public interface GeyserBootstrap {
void onGeyserShutdown(); 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 * 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. * Tests if Floodgate is installed, loads the Floodgate key if so, and returns the result of Floodgate installed.
*/ */
boolean testFloodgatePluginPresent(); boolean testFloodgatePluginPresent();
/**
* TEMPORARY - will be removed after The Merge:tm:.
*/
Path getFloodgateKeyPath();
@Nullable
default MetricsPlatform createMetricsPlatform() {
return new ProvidedMetricsPlatform();
}
default <T extends GeyserConfig> T loadConfig(Class<T> configClass) {
return new ConfigLoader(this).createFolder().load(configClass);
}
} }

View File

@@ -25,10 +25,8 @@
package org.geysermc.geyser; package org.geysermc.geyser;
import com.fasterxml.jackson.core.JsonParser; import com.google.gson.Gson;
import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.reflect.TypeToken;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.Epoll;
import io.netty.util.NettyRuntime; import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory; 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.kyori.adventure.text.format.NamedTextColor;
import net.raphimc.minecraftauth.msa.data.MsaConstants; import net.raphimc.minecraftauth.msa.data.MsaConstants;
import net.raphimc.minecraftauth.msa.model.MsaApplicationConfig; 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.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; 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.MinecraftVersion;
import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; 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.entity.EntityDefinitions;
import org.geysermc.geyser.erosion.UnixSocketClientListener; import org.geysermc.geyser.erosion.UnixSocketClientListener;
import org.geysermc.geyser.event.GeyserEventBus; 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.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.network.netty.GeyserServer; import org.geysermc.geyser.network.netty.GeyserServer;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.loader.ResourcePackLoader; 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.translator.text.MessageTranslator;
import org.geysermc.geyser.util.AssetUtils; import org.geysermc.geyser.util.AssetUtils;
import org.geysermc.geyser.util.CodeOfConductManager; import org.geysermc.geyser.util.CodeOfConductManager;
import org.geysermc.geyser.util.CooldownUtils; import org.geysermc.geyser.util.JsonUtils;
import org.geysermc.geyser.util.Metrics;
import org.geysermc.geyser.util.NewsHandler; import org.geysermc.geyser.util.NewsHandler;
import org.geysermc.geyser.util.VersionCheckUtils; import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils; import org.geysermc.geyser.util.WebUtils;
import org.geysermc.geyser.util.metrics.MetricsPlatform;
import java.io.File; import java.io.File;
import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -129,12 +136,7 @@ import java.util.regex.Pattern;
@Getter @Getter
public class GeyserImpl implements GeyserApi, EventRegistrar { public class GeyserImpl implements GeyserApi, EventRegistrar {
public static final ObjectMapper JSON_MAPPER = new ObjectMapper() public static final Gson GSON = JsonUtils.createGson();
.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 String NAME = "Geyser"; public static final String NAME = "Geyser";
public static final String GIT_VERSION = BuildData.GIT_VERSION; public static final String GIT_VERSION = BuildData.GIT_VERSION;
@@ -167,13 +169,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
private ScheduledExecutorService scheduledThread; private ScheduledExecutorService scheduledThread;
private GeyserServer geyserServer; private GeyserServer geyserServer;
private final PlatformType platformType;
private final GeyserBootstrap bootstrap; private final GeyserBootstrap bootstrap;
private final GeyserEventBus eventBus; private final GeyserEventBus eventBus;
private final GeyserExtensionManager extensionManager; private final GeyserExtensionManager extensionManager;
private Metrics metrics; private MetricsBase metrics;
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
@Getter(AccessLevel.NONE) @Getter(AccessLevel.NONE)
@@ -194,12 +195,11 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
@Setter @Setter
private boolean isEnabled; private boolean isEnabled;
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { private GeyserImpl(GeyserBootstrap bootstrap) {
instance = this; instance = this;
Geyser.set(this); Geyser.set(this);
this.platformType = platformType;
this.bootstrap = bootstrap; this.bootstrap = bootstrap;
/* Initialize event bus */ /* Initialize event bus */
@@ -218,10 +218,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
public void initialize() { public void initialize() {
// Setup encryption early so we don't start if we can't auth // Setup encryption early so we don't start if we can't auth
if (config().advanced().bedrock().validateBedrockLogin()) {
try { try {
EncryptionUtils.getMojangPublicKey(); EncryptionUtils.getMojangPublicKey();
} catch (Throwable e) { } catch (Throwable t) {
throw new RuntimeException("Cannot setup authentication! Are you offline? ", e); 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(); long startupTime = System.currentTimeMillis();
@@ -278,19 +280,19 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
startInstance(); startInstance();
GeyserConfiguration config = bootstrap.getGeyserConfig(); GeyserConfig config = bootstrap.config();
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)); String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime));
message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console"); message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
logger.info(message); logger.info(message);
if (platformType == PlatformType.STANDALONE) { if (platformType() == PlatformType.STANDALONE) {
if (config.getRemote().authType() != AuthType.FLOODGATE) { if (config.java().authType() != AuthType.FLOODGATE) {
// If the auth-type is Floodgate, then this Geyser instance is probably owned by the Java server // 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")); 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); VersionCheckUtils.checkForOutdatedFloodgate(logger);
} }
@@ -308,7 +310,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
GeyserLogger logger = bootstrap.getGeyserLogger(); GeyserLogger logger = bootstrap.getGeyserLogger();
GeyserConfiguration config = bootstrap.getGeyserConfig(); GeyserConfig config = bootstrap.config();
ScoreboardUpdater.init(); ScoreboardUpdater.init();
@@ -316,6 +318,23 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
Registries.RESOURCE_PACKS.load(); 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 geyserUdpPort = System.getProperty("geyserUdpPort", "");
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort; String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
if ("-1".equals(pluginUdpPort)) { if ("-1".equals(pluginUdpPort)) {
@@ -324,33 +343,30 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
boolean portPropertyApplied = false; boolean portPropertyApplied = false;
String pluginUdpAddress = System.getProperty("geyserUdpAddress", System.getProperty("pluginUdpAddress", "")); String pluginUdpAddress = System.getProperty("geyserUdpAddress", System.getProperty("pluginUdpAddress", ""));
if (platformType != PlatformType.STANDALONE) { if (platformType() != PlatformType.STANDALONE) {
int javaPort = bootstrap.getServerPort(); int javaPort = bootstrap.getServerPort();
if (config.getRemote().address().equals("auto")) {
config.setAutoconfiguredRemote(true);
String serverAddress = bootstrap.getServerBindAddress(); String serverAddress = bootstrap.getServerBindAddress();
if (!serverAddress.isEmpty() && !"0.0.0.0".equals(serverAddress)) { if (!serverAddress.isEmpty() && !"0.0.0.0".equals(serverAddress)) {
config.getRemote().setAddress(serverAddress); config.java().address(serverAddress);
} else { } else {
// Set the remote address to localhost since that is where we are always connecting // Set the remote address to localhost since that is where we are always connecting
try { try {
config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress()); config.java().address(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException ex) { } catch (UnknownHostException ex) {
logger.debug("Unknown host when trying to find localhost."); logger.debug("Unknown host when trying to find localhost.");
if (config.isDebugMode()) { if (config.debugMode()) {
ex.printStackTrace(); ex.printStackTrace();
} }
config.getRemote().setAddress(InetAddress.getLoopbackAddress().getHostAddress()); config.java().address(InetAddress.getLoopbackAddress().getHostAddress());
} }
} }
if (javaPort != -1) { if (javaPort != -1) {
config.getRemote().setPort(javaPort); config.java().port(javaPort);
}
} }
boolean forceMatchServerPort = "server".equals(pluginUdpPort); boolean forceMatchServerPort = "server".equals(pluginUdpPort);
if ((config.getBedrock().isCloneRemotePort() || forceMatchServerPort) && javaPort != -1) { if ((config.bedrock().cloneRemotePort() || forceMatchServerPort) && javaPort != -1) {
config.getBedrock().setPort(javaPort); config.bedrock().port(javaPort);
if (forceMatchServerPort) { if (forceMatchServerPort) {
if (geyserUdpPort.isEmpty()) { if (geyserUdpPort.isEmpty()) {
logger.info("Port set from system generic property to match Java server."); 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)) { if ("server".equals(pluginUdpAddress)) {
String address = bootstrap.getServerBindAddress(); String address = bootstrap.getServerBindAddress();
if (!address.isEmpty()) { if (!address.isEmpty()) {
config.getBedrock().setAddress(address); config.bedrock().address(address);
} }
} else if (!pluginUdpAddress.isEmpty()) { } else if (!pluginUdpAddress.isEmpty()) {
config.getBedrock().setAddress(pluginUdpAddress); config.bedrock().address(pluginUdpAddress);
} }
if (!portPropertyApplied && !pluginUdpPort.isEmpty()) { if (!portPropertyApplied && !pluginUdpPort.isEmpty()) {
int port = Integer.parseInt(pluginUdpPort); int port = Integer.parseInt(pluginUdpPort);
config.getBedrock().setPort(port); config.bedrock().port(port);
if (geyserUdpPort.isEmpty()) { if (geyserUdpPort.isEmpty()) {
logger.info("Port set from generic system property: " + port); logger.info("Port set from generic system property: " + port);
} else { } else {
@@ -380,16 +396,17 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
} }
if (platformType != PlatformType.VIAPROXY) {
if (platformType() != PlatformType.VIAPROXY) {
boolean floodgatePresent = bootstrap.testFloodgatePluginPresent(); 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") + " " logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " "
+ GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
return; return;
} else if (config.isAutoconfiguredRemote() && floodgatePresent) { } else if (floodgatePresent) {
// Floodgate installed means that the user wants Floodgate authentication // Floodgate installed means that the user wants Floodgate authentication
logger.debug("Auto-setting to 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) { if (parsedPort < 1 || parsedPort > 65535) {
throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!"); 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); logger.info("Broadcast port set from system property: " + parsedPort);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
logger.error(String.format("Invalid broadcast port from system property: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")")); 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 // It's set to 0 only if no system property or manual config value was set
if (config.getBedrock().broadcastPort() == 0) { if (config.advanced().bedrock().broadcastPort() == 0) {
config.getBedrock().setBroadcastPort(config.getBedrock().port()); config.advanced().bedrock().broadcastPort(config.bedrock().port());
} }
String remoteAddress = config.getRemote().address(); if (!(config instanceof GeyserPluginConfig)) {
String remoteAddress = config.java().address();
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. // 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")) { if (!IP_REGEX.matcher(remoteAddress).matches() && !remoteAddress.equalsIgnoreCase("localhost")) {
String[] record = WebUtils.findSrvRecord(this, remoteAddress); String[] record = WebUtils.findSrvRecord(this, remoteAddress);
if (record != null) { if (record != null) {
int remotePort = Integer.parseInt(record[2]); int remotePort = Integer.parseInt(record[2]);
config.getRemote().setAddress(remoteAddress = record[3]); config.java().address(remoteAddress = record[3]);
config.getRemote().setPort(remotePort); config.java().port(remotePort);
logger.debug("Found SRV record \"" + remoteAddress + ":" + 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()); 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."); logger.debug("Epoll is not available; Erosion's Unix socket handling will not work.");
} }
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); BedrockDimension.changeBedrockNetherId(config.gameplay().netherRoofWorkaround()); // Apply End dimension ID workaround to Nether
BedrockDimension.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads"); int bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads", -1);
if (bedrockThreadCount == null) { if (bedrockThreadCount == -1) {
// Copy the code from Netty's default thread count fallback // Copy the code from Netty's default thread count fallback
bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
} }
this.geyserServer = new GeyserServer(this, bedrockThreadCount); 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) -> { .whenComplete((avoid, throwable) -> {
String address = config.getBedrock().address(); String address = config.bedrock().address();
String port = String.valueOf(config.getBedrock().port()); // otherwise we get commas String port = String.valueOf(config.bedrock().port()); // otherwise we get commas
if (throwable == null) { if (throwable == null) {
if ("0.0.0.0".equals(address)) { if ("0.0.0.0".equals(address)) {
@@ -469,9 +489,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
}).join(); }).join();
if (config.getRemote().authType() == AuthType.FLOODGATE) { if (config.java().authType() == AuthType.FLOODGATE) {
try { try {
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); Key key = new AesKeyProducer().produceFrom(bootstrap.getFloodgateKeyPath());
cipher = new AesCipher(new Base64Topping()); cipher = new AesCipher(new Base64Topping());
cipher.init(key); cipher.init(key);
logger.debug("Loaded Floodgate key!"); logger.debug("Loaded Floodgate key!");
@@ -483,28 +503,55 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
} }
if (config.getMetrics().isEnabled()) { MetricsPlatform metricsPlatform = bootstrap.createMetricsPlatform();
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); if (metricsPlatform != null && metricsPlatform.enabled()) {
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size)); 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 // Prevent unwanted words best we can
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT))); metrics.addCustomChart(new SimplePie("authMode", () -> config.java().authType().toString().toLowerCase(Locale.ROOT)));
Map<String, Map<String, Integer>> platformTypeMap = new HashMap<>(); Map<String, Map<String, Integer>> platformTypeMap = new HashMap<>();
Map<String, Integer> serverPlatform = new HashMap<>(); Map<String, Integer> serverPlatform = new HashMap<>();
serverPlatform.put(bootstrap.getServerPlatform(), 1); serverPlatform.put(bootstrap.getServerPlatform(), 1);
platformTypeMap.put(platformType().platformName(), serverPlatform); platformTypeMap.put(platformType().platformName(), serverPlatform);
metrics.addCustomChart(new Metrics.DrilldownPie("platform", () -> { metrics.addCustomChart(new DrilldownPie("platform", () -> {
// By the end, we should return, for example: // By the end, we should return, for example:
// Geyser-Spigot => (Paper, 1) // Geyser-Spigot => (Paper, 1)
return platformTypeMap; return platformTypeMap;
})); }));
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); metrics.addCustomChart(new SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION)); metrics.addCustomChart(new SimplePie("version", () -> GeyserImpl.VERSION));
metrics.addCustomChart(new Metrics.SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.getRemote().isUseProxyProtocol()))); metrics.addCustomChart(new SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.advanced().java().useHaproxyProtocol())));
metrics.addCustomChart(new Metrics.SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.getBedrock().isEnableProxyProtocol()))); metrics.addCustomChart(new SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.advanced().bedrock().useHaproxyProtocol())));
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { metrics.addCustomChart(new AdvancedPie("playerPlatform", () -> {
Map<String, Integer> valueMap = new HashMap<>(); Map<String, Integer> valueMap = new HashMap<>();
for (GeyserSession session : sessionManager.getAllSessions()) { for (GeyserSession session : sessionManager.getAllSessions()) {
if (session == null) continue; if (session == null) continue;
@@ -518,7 +565,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
return valueMap; return valueMap;
})); }));
metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { metrics.addCustomChart(new AdvancedPie("playerVersion", () -> {
Map<String, Integer> valueMap = new HashMap<>(); Map<String, Integer> valueMap = new HashMap<>();
for (GeyserSession session : sessionManager.getAllSessions()) { for (GeyserSession session : sessionManager.getAllSessions()) {
if (session == null) continue; if (session == null) continue;
@@ -540,7 +587,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
platformMap.put(bootstrap.getServerPlatform(), 1); platformMap.put(bootstrap.getServerPlatform(), 1);
versionMap.put(minecraftVersion, platformMap); versionMap.put(minecraftVersion, platformMap);
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> { metrics.addCustomChart(new DrilldownPie("minecraftServerVersion", () -> {
// By the end, we should return, for example: // By the end, we should return, for example:
// 1.16.5 => (Spigot, 1) // 1.16.5 => (Spigot, 1)
return versionMap; return versionMap;
@@ -549,7 +596,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
// The following code can be attributed to the PaperMC project // 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 // https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614
metrics.addCustomChart(new Metrics.DrilldownPie("javaVersion", () -> { metrics.addCustomChart(new DrilldownPie("javaVersion", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>(); Map<String, Map<String, Integer>> map = new HashMap<>();
String javaVersion = System.getProperty("java.version"); String javaVersion = System.getProperty("java.version");
Map<String, Integer> entry = new HashMap<>(); Map<String, Integer> entry = new HashMap<>();
@@ -584,22 +631,21 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
metrics = null; 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 // May be written/read to on multiple threads from each GeyserSession as well as writing the config
savedAuthChains = new ConcurrentHashMap<>(); savedAuthChains = new ConcurrentHashMap<>();
Type type = new TypeToken<Map<String, String>>() { }.getType();
File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile(); File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
if (authChainsFile.exists()) { if (authChainsFile.exists()) {
TypeReference<Map<String, String>> type = new TypeReference<>() { };
Map<String, String> authChainFile = null; Map<String, String> authChainFile = null;
try { try (FileReader reader = new FileReader(authChainsFile)) {
authChainFile = JSON_MAPPER.readValue(authChainsFile, type); authChainFile = GSON.fromJson(reader, type);
} catch (IOException e) { } catch (IOException e) {
logger.error("Cannot load saved user tokens!", e); logger.error("Cannot load saved user tokens!", e);
} }
if (authChainFile != null) { if (authChainFile != null) {
List<String> validUsers = config.getSavedUserLogins(); List<String> validUsers = config.savedUserLogins();
boolean doWrite = false; boolean doWrite = false;
for (Map.Entry<String, String> entry : authChainFile.entrySet()) { for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
String user = entry.getKey(); String user = entry.getKey();
@@ -627,7 +673,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
} }
if (config.isNotifyOnNewBedrockUpdate()) { if (config.notifyOnNewBedrockUpdate()) {
VersionCheckUtils.checkForGeyserUpdate(this::getLogger); VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
} }
} }
@@ -703,6 +749,10 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
runIfNonNull(newsHandler, NewsHandler::shutdown); runIfNonNull(newsHandler, NewsHandler::shutdown);
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close); runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
if (bootstrap.getGeyserPingPassthrough() instanceof GeyserLegacyPingPassthrough legacyPingPassthrough) {
legacyPingPassthrough.interrupt();
}
ResourcePackLoader.clear(); ResourcePackLoader.clear();
CodeOfConductManager.getInstance().save(); CodeOfConductManager.getInstance().save();
@@ -777,13 +827,13 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
@NonNull @NonNull
public RemoteServer defaultRemoteServer() { public RemoteServer defaultRemoteServer() {
return getConfig().getRemote(); return config().java();
} }
@Override @Override
@NonNull @NonNull
public BedrockListener bedrockListener() { public BedrockListener bedrockListener() {
return getConfig().getBedrock(); return config().bedrock();
} }
@Override @Override
@@ -801,7 +851,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
@Override @Override
@NonNull @NonNull
public PlatformType platformType() { public PlatformType platformType() {
return platformType; return bootstrap.platformType();
} }
@Override @Override
@@ -828,9 +878,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
return Integer.parseInt(BUILD_NUMBER); return Integer.parseInt(BUILD_NUMBER);
} }
public static GeyserImpl load(PlatformType platformType, GeyserBootstrap bootstrap) { public static GeyserImpl load(GeyserBootstrap bootstrap) {
if (instance == null) { if (instance == null) {
return new GeyserImpl(platformType, bootstrap); return new GeyserImpl(bootstrap);
} }
return instance; return instance;
@@ -853,8 +903,8 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
return bootstrap.getGeyserLogger(); return bootstrap.getGeyserLogger();
} }
public GeyserConfiguration getConfig() { public GeyserConfig config() {
return bootstrap.getGeyserConfig(); return bootstrap.config();
} }
public WorldManager getWorldManager() { public WorldManager getWorldManager() {
@@ -867,7 +917,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) { public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
if (!getConfig().getSavedUserLogins().contains(bedrockName)) { if (!config().savedUserLogins().contains(bedrockName)) {
// Do not save this login // Do not save this login
return; return;
} }
@@ -889,11 +939,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
scheduledThread.execute(() -> { scheduledThread.execute(() -> {
// Ensure all writes are handled on the same thread // Ensure all writes are handled on the same thread
File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile(); File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
TypeReference<Map<String, String>> type = new TypeReference<>() { }; Type type = new TypeToken<Map<String, String>>() { }.getType();
try (FileWriter writer = new FileWriter(savedAuthChains)) { try (FileWriter writer = new FileWriter(savedAuthChains)) {
JSON_MAPPER.writerFor(type) GSON.toJson(this.savedAuthChains, type, writer);
.withDefaultPrettyPrinter()
.writeValue(writer, this.savedAuthChains);
} catch (IOException e) { } catch (IOException e) {
getLogger().error("Unable to write saved refresh tokens!", e); getLogger().error("Unable to write saved refresh tokens!", e);
} }

View File

@@ -101,7 +101,10 @@ public interface GeyserLogger extends GeyserCommandSource {
* @param object the object to log * @param object the object to log
*/ */
default void debug(@Nullable Object object) { default void debug(@Nullable Object object) {
debug(String.valueOf(object)); if (isDebug()) {
// Don't create String object by default
info(String.valueOf(object));
}
} }
/** /**

View File

@@ -99,8 +99,8 @@ public class CommandRegistry implements EventRegistrar {
private static final String GEYSER_ROOT_PERMISSION = "geyser.command"; private static final String GEYSER_ROOT_PERMISSION = "geyser.command";
public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE || public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().platformType() == PlatformType.STANDALONE ||
GeyserImpl.getInstance().getPlatformType() == PlatformType.VIAPROXY; GeyserImpl.getInstance().platformType() == PlatformType.VIAPROXY;
protected final GeyserImpl geyser; protected final GeyserImpl geyser;
private final CommandManager<GeyserCommandSource> cloud; private final CommandManager<GeyserCommandSource> cloud;
@@ -171,7 +171,7 @@ public class CommandRegistry implements EventRegistrar {
registerBuiltInCommand(new CustomOptionsCommand("options", "geyser.commands.options.desc", "geyser.command.options")); registerBuiltInCommand(new CustomOptionsCommand("options", "geyser.commands.options.desc", "geyser.command.options"));
registerBuiltInCommand(new QuickActionsCommand("quickactions", "geyser.commands.quickactions.desc", "geyser.command.quickactions")); 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")); registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
} }

View File

@@ -25,12 +25,13 @@
package org.geysermc.geyser.command.defaults; 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.GeyserImpl;
import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource; 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.LoopbackUtil;
import org.geysermc.geyser.util.WebUtils; import org.geysermc.geyser.util.WebUtils;
import org.incendo.cloud.CommandManager; 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 // Replace "<" and ">" symbols if they are present to avoid the common issue of people including them
final String ip = ipArgument.replace("<", "").replace(">", ""); 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 // Issue: people commonly checking placeholders
if (ip.equals("ip")) { if (ip.equals("ip")) {
@@ -105,42 +106,42 @@ public class ConnectionTestCommand extends GeyserCommand {
return; 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 // 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 (config.advanced().bedrock().broadcastPort() == config.bedrock().port()) {
if (port != config.getBedrock().port()) { if (port != config.bedrock().port()) {
if (portArgument != null) { if (portArgument != null) {
source.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration (" 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."); 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."); 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 { } else {
source.sendMessage("You did not specify the port to check (add it with \":<port>\"), " + source.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
"and the default port 19132 does not match the port in your Geyser configuration (" "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`."); source.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
} }
} }
} else { } 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 (" 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("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."); 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? // 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."); 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? // Issue: did someone turn on enable-proxy-protocol, and they didn't mean it?
if (config.getBedrock().isEnableProxyProtocol()) { if (config.advanced().bedrock().useHaproxyProtocol()) {
source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + 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."); "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; CONNECTION_TEST_MOTD = connectionTestMotd;
source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait..."); source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
JsonNode output; JsonObject output;
try { try {
String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8); String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8);
output = WebUtils.getJson("https://checker.geysermc.org/ping?hostname=" + hostname + "&port=" + port); 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; CONNECTION_TEST_MOTD = null;
} }
if (output.get("success").asBoolean()) { if (output.get("success").getAsBoolean()) {
JsonNode cache = output.get("cache"); JsonObject cache = output.getAsJsonObject("cache");
String when; String when;
if (cache.get("fromCache").asBoolean()) { if (cache.get("fromCache").isJsonPrimitive()) {
when = cache.get("secondsSince").asInt() + " seconds ago"; when = cache.get("secondsSince").getAsBoolean() + " seconds ago";
} else { } else {
when = "now"; when = "now";
} }
JsonNode ping = output.get("ping"); JsonObject ping = output.getAsJsonObject("ping");
JsonNode pong = ping.get("pong"); JsonObject pong = ping.getAsJsonObject("pong");
String remoteMotd = pong.get("motd").asText(); String remoteMotd = pong.get("motd").getAsString();
if (!connectionTestMotd.equals(remoteMotd)) { if (!connectionTestMotd.equals(remoteMotd)) {
source.sendMessage("The MOTD did not match when we pinged the server (we got '" + 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?"); "Did you supply the correct IP and port of your server?");
@@ -198,7 +199,7 @@ public class ConnectionTestCommand extends GeyserCommand {
return; 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."); 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); sendLinks(source);
return; return;
@@ -210,9 +211,9 @@ public class ConnectionTestCommand extends GeyserCommand {
} }
source.sendMessage("Your server is likely unreachable from outside the network!"); source.sendMessage("Your server is likely unreachable from outside the network!");
JsonNode message = output.get("message"); JsonElement message = output.get("message");
if (message != null && !message.asText().isEmpty()) { if (message != null && !message.getAsString().isEmpty()) {
source.sendMessage("Got the error message: " + message.asText()); source.sendMessage("Got the error message: " + message.getAsString());
} }
sendLinks(source); sendLinks(source);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -25,11 +25,14 @@
package org.geysermc.geyser.command.defaults; package org.geysermc.geyser.command.defaults;
import com.fasterxml.jackson.core.JsonParseException; import com.google.gson.Gson;
import com.fasterxml.jackson.core.util.DefaultIndenter; import com.google.gson.GsonBuilder;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.google.gson.JsonObject;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonParseException;
import com.fasterxml.jackson.databind.ObjectMapper; 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.GeyserImpl;
import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand; 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.AsteriskSerializer;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.JsonUtils;
import org.geysermc.geyser.util.WebUtils; import org.geysermc.geyser.util.WebUtils;
import org.incendo.cloud.CommandManager; import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.suggestion.SuggestionProvider; 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; import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser;
public class DumpCommand extends GeyserCommand { public class DumpCommand extends GeyserCommand {
@@ -56,7 +55,6 @@ public class DumpCommand extends GeyserCommand {
private static final Iterable<String> SUGGESTIONS = List.of("full", "offline", "logs"); private static final Iterable<String> SUGGESTIONS = List.of("full", "offline", "logs");
private final GeyserImpl geyser; private final GeyserImpl geyser;
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String DUMP_URL = "https://dump.geysermc.org/"; private static final String DUMP_URL = "https://dump.geysermc.org/";
public DumpCommand(GeyserImpl geyser, String name, String description, String permission) { public DumpCommand(GeyserImpl geyser, String name, String description, String permission) {
@@ -111,20 +109,14 @@ public class DumpCommand extends GeyserCommand {
AsteriskSerializer.showSensitive = showSensitive; AsteriskSerializer.showSensitive = showSensitive;
Gson gson = new GsonBuilder().setPrettyPrinting().create();
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale())); source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale()));
String dumpData; String dumpData;
try { try {
DumpInfo dump = new DumpInfo(geyser, addLog); DumpInfo dump = new DumpInfo(geyser, addLog);
dumpData = gson.toJson(dump);
if (offlineDump) { } catch (Exception e) {
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) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale())); 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); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
return; return;
@@ -150,11 +142,11 @@ public class DumpCommand extends GeyserCommand {
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale())); source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale()));
String response = null; String response = null;
JsonNode responseNode; JsonObject responseNode;
try { try {
response = WebUtils.post(DUMP_URL + "documents", dumpData); response = WebUtils.post(DUMP_URL + "documents", dumpData);
responseNode = MAPPER.readTree(response); responseNode = JsonUtils.parseJson(response);
} catch (IOException e) { } catch (Throwable e) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale())); 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); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
if (e instanceof JsonParseException && response != null) { if (e instanceof JsonParseException && response != null) {
@@ -164,11 +156,11 @@ public class DumpCommand extends GeyserCommand {
} }
if (!responseNode.has("key")) { 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; 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); source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);

View File

@@ -25,7 +25,7 @@
package org.geysermc.geyser.command.defaults; 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.GeyserImpl;
import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.api.util.PlatformType; 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)); GeyserImpl.NAME, GeyserImpl.VERSION, SUPPORTED_JAVA_RANGE, SUPPORTED_BEDROCK_RANGE));
// Disable update checking in dev mode and for players in Geyser Standalone // 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; return;
} }
@@ -89,8 +89,8 @@ public class VersionCommand extends GeyserCommand {
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale())); source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale()));
try { try {
int buildNumber = this.geyser.buildNumber(); int buildNumber = this.geyser.buildNumber();
JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest"); JsonObject response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
int latestBuildNumber = response.get("build").asInt(); int latestBuildNumber = response.get("build").getAsInt();
if (latestBuildNumber == buildNumber) { if (latestBuildNumber == buildNumber) {
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale())); source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale()));

View File

@@ -25,21 +25,18 @@
package org.geysermc.geyser.command.standalone; package org.geysermc.geyser.command.standalone;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter; import lombok.Getter;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
@Getter @Getter
@JsonIgnoreProperties(ignoreUnknown = true) @ConfigSerializable
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final @SuppressWarnings("FieldMayBeFinal")
public class PermissionConfiguration { public class PermissionConfiguration {
@JsonProperty("default-permissions")
private Set<String> defaultPermissions = Collections.emptySet(); private Set<String> defaultPermissions = Collections.emptySet();
@JsonProperty("default-denied-permissions")
private Set<String> defaultDeniedPermissions = Collections.emptySet(); private Set<String> defaultDeniedPermissions = Collections.emptySet();
} }

View File

@@ -0,0 +1,217 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import com.google.common.annotations.VisibleForTesting;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.returnsreceiver.qual.This;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.text.GeyserLocale;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions;
import org.spongepowered.configurate.objectmapping.meta.Processor;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import org.spongepowered.configurate.yaml.NodeStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.function.Consumer;
public final class ConfigLoader {
private static final String HEADER = """
--------------------------------
Geyser Configuration File
A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition.
GitHub: https://github.com/GeyserMC/Geyser
Discord: https://discord.gg/geysermc
Wiki: https://geysermc.org/wiki
NOTICE: See https://geysermc.org/wiki/geyser/setup/ for the setup guide. Many video tutorials are outdated.
In most cases, especially with server hosting providers, further hosting-specific configuration is required.
--------------------------------""";
/**
* Only nullable for testing.
*/
private final @Nullable GeyserBootstrap bootstrap;
private PlatformType platformType;
private @Nullable Consumer<CommentedConfigurationNode> transformer;
private File configFile;
/**
* Only set during testing.
*/
@VisibleForTesting
CommentedConfigurationNode configurationNode;
public ConfigLoader(GeyserBootstrap bootstrap) {
this.bootstrap = bootstrap;
this.platformType = bootstrap.platformType();
configFile = new File(bootstrap.getConfigFolder().toFile(), "config.yml");
}
@VisibleForTesting
ConfigLoader(File file) {
this.bootstrap = null;
configFile = file;
}
/**
* Creates the directory as indicated by {@link GeyserBootstrap#getConfigFolder()}
*/
@This
public ConfigLoader createFolder() {
Path dataFolder = this.bootstrap.getConfigFolder();
if (!dataFolder.toFile().exists()) {
if (!dataFolder.toFile().mkdir()) {
GeyserImpl.getInstance().getLogger().warning("Failed to create config folder: " + dataFolder);
}
}
return this;
}
@This
public ConfigLoader transformer(Consumer<CommentedConfigurationNode> transformer) {
this.transformer = transformer;
return this;
}
@This
public ConfigLoader configFile(File configFile) {
this.configFile = configFile;
return this;
}
/**
* @return null if the config failed to load.
*/
@Nullable
public <T extends GeyserConfig> T load(Class<T> configClass) {
try {
return load0(configClass);
} catch (IOException ex) {
bootstrap.getGeyserLogger().error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
return null;
}
}
private <T extends GeyserConfig> T load0(Class<T> configClass) throws ConfigurateException {
var loader = createLoader(configFile);
CommentedConfigurationNode node = loader.load();
boolean originallyEmpty = !configFile.exists() || node.isNull();
ConfigurationTransformation.Versioned migrations = ConfigMigrations.TRANSFORMER.apply(configClass, bootstrap);
int currentVersion = migrations.version(node);
migrations.apply(node);
int newVersion = migrations.version(node);
T config = node.get(configClass);
// Serialize the instance to ensure strict field ordering. Additionally, if we serialized back
// to the old node, existing nodes would only have their value changed, keeping their position
// at the top of the ordered map, forcing all new nodes to the bottom (regardless of field order).
// For that reason, we must also create a new node.
CommentedConfigurationNode newRoot = CommentedConfigurationNode.root(loader.defaultOptions());
newRoot.set(config);
if (originallyEmpty || currentVersion != newVersion) {
if (!originallyEmpty && currentVersion > 4) {
// Only copy comments over if the file already existed, and we are going to replace it
// Second case: Version 4 is pre-configurate where there were commented out nodes.
// These get treated as comments on lower nodes, which produces very undesirable results.
ConfigurationCommentMover.moveComments(node, newRoot);
}
loader.save(newRoot);
}
if (transformer != null) {
// We transform AFTER saving so that these specific transformations aren't applied to file.
transformer.accept(newRoot);
config = newRoot.get(configClass);
}
if (this.bootstrap != null) { // Null for testing only.
this.bootstrap.getGeyserLogger().setDebug(config.debugMode());
} else {
this.configurationNode = newRoot;
}
return config;
}
@VisibleForTesting
CommentedConfigurationNode loadConfigurationNode(Class<? extends GeyserConfig> configClass, PlatformType platformType) throws ConfigurateException {
this.platformType = platformType;
load0(configClass);
return configurationNode.copy();
}
private YamlConfigurationLoader createLoader(File file) {
return YamlConfigurationLoader.builder()
.file(file)
.indent(2)
.nodeStyle(NodeStyle.BLOCK)
.defaultOptions(options -> InterfaceDefaultOptions.addTo(options, builder -> {
builder.addProcessor(ExcludePlatform.class, excludePlatform(platformType.platformName()))
.addProcessor(PluginSpecific.class, integrationSpecific(platformType != PlatformType.STANDALONE));
})
.shouldCopyDefaults(false) // If we use ConfigurationNode#get(type, default), do not write the default back to the node.
.header(ConfigLoader.HEADER)
.serializers(builder -> builder.register(new LowercaseEnumSerializer())))
.build();
}
private static Processor.Factory<ExcludePlatform, Object> excludePlatform(String thisPlatform) {
return (data, fieldType) -> (value, destination) -> {
for (String platform : data.platforms()) {
if (thisPlatform.equals(platform)) {
//noinspection DataFlowIssue
destination.parent().removeChild(destination.key());
break;
}
}
};
}
private static Processor.Factory<PluginSpecific, Object> integrationSpecific(boolean thisConfigPlugin) {
return (data, fieldType) -> (value, destination) -> {
if (data.forPlugin() != thisConfigPlugin) {
//noinspection DataFlowIssue
destination.parent().removeChild(destination.key());
}
};
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) 2025 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import org.geysermc.geyser.GeyserBootstrap;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import org.spongepowered.configurate.transformation.TransformAction;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.function.BiFunction;
import static org.spongepowered.configurate.NodePath.path;
import static org.spongepowered.configurate.transformation.TransformAction.remove;
import static org.spongepowered.configurate.transformation.TransformAction.rename;
public class ConfigMigrations {
public static final BiFunction<Class<? extends GeyserConfig>, GeyserBootstrap, ConfigurationTransformation.Versioned> TRANSFORMER = (configClass, bootstrap) ->
ConfigurationTransformation.versionedBuilder()
.versionKey("config-version")
.addVersion(5, ConfigurationTransformation.builder()
// Java section
.addAction(path("remote"), rename("java"))
.addAction(path("remote", "address"), (path, value) -> {
if ("auto".equals(value.getString())) {
// Auto-convert back to localhost
value.set("127.0.0.1");
}
return null;
})
// Motd section
.addAction(path("bedrock", "motd1"), renameAndMove("motd", "primary-motd"))
.addAction(path("bedrock", "motd2"), renameAndMove("motd", "secondary-motd"))
.addAction(path("passthrough-motd"), moveTo("motd"))
.addAction(path("passthrough-player-counts"), moveTo("motd"))
.addAction(path("ping-passthrough-interval"), moveTo("motd"))
.addAction(path("max-players"), moveTo("motd"))
.addAction(path("legacy-ping-passthrough"), configClass == GeyserRemoteConfig.class ? remove() : (path, value) -> {
// Invert value
value.set(!value.getBoolean());
return new Object[]{ "motd", "integrated-ping-passthrough" };
})
// gameplay
.addAction(path("command-suggestions"), moveTo("gameplay"))
.addAction(path("forward-player-ping"), moveTo("gameplay"))
.addAction(path("show-cooldown"), (path, value) -> {
String s = value.getString();
if (s != null) {
switch (s) {
case "true" -> value.set("title");
case "false" -> value.set("disabled");
}
}
return new Object[]{ "gameplay", "show-cooldown" };
})
.addAction(path("bedrock", "server-name"), moveTo("gameplay"))
.addAction(path("show-coordinates"), moveTo("gameplay"))
.addAction(path("disable-bedrock-scaffolding"), moveTo("gameplay"))
.addAction(path("custom-skull-render-distance"), moveTo("gameplay"))
.addAction(path("force-resource-packs"), moveTo("gameplay"))
.addAction(path("xbox-achievements-enabled"), moveTo("gameplay"))
.addAction(path("unusable-space-block"), moveTo("gameplay"))
.addAction(path("unusable-space-block"), moveTo("gameplay"))
.addAction(path("add-non-bedrock-items"), renameAndMove("gameplay", "enable-custom-content"))
.addAction(path("above-bedrock-nether-building"), renameAndMove("gameplay", "nether-roof-workaround"))
.addAction(path("xbox-achievements-enabled"), moveTo("gameplay"))
// NOTE: We're not explicitly removing the allow-custom-skulls option, it will already be removed since it
// won't be written back. If we remove it, we can't query the value of it!
.addAction(path("max-visible-custom-skulls"), (path, value) -> {
ConfigurationNode parent = value.parent();
if (parent != null && parent.isMap()) {
ConfigurationNode allowCustomSkulls = parent.childrenMap().get("allow-custom-skulls");
if (allowCustomSkulls != null && !allowCustomSkulls.getBoolean()) {
value.set(0);
}
}
return new Object[]{ "gameplay", "max-visible-custom-skulls" };
})
.addAction(path("emote-offhand-workaround"), (path, value) -> {
String previous = value.getString();
if (!Objects.equals(previous, "disabled") && bootstrap != null) {
bootstrap.getGeyserLogger().warning("The emote-offhand-workaround option has been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/EmoteOffhandExtension/");
}
if (Objects.equals(previous, "no-emotes")) {
value.set(false);
return new Object[]{ "gameplay", "show-emotes" };
}
return null;
})
// For the warning!
.addAction(path("allow-third-party-capes"), (node, value) -> {
if (bootstrap != null) {
bootstrap.getGeyserLogger().warning("Third-party ears/capes have been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/ThirdPartyCosmetics");
}
return null;
})
.addAction(path("allow-third-party-ears"), (node, value) -> {
if (bootstrap != null) {
bootstrap.getGeyserLogger().warning("Third-party ears/capes have been removed from Geyser. If you still wish to have this functionality, use this Geyser extension: https://github.com/GeyserMC/ThirdPartyCosmetics");
}
return null;
})
// Advanced section
.addAction(path("cache-images"), moveTo("advanced"))
.addAction(path("scoreboard-packet-threshold"), moveTo("advanced"))
.addAction(path("add-team-suggestions"), moveTo("advanced"))
.addAction(path("floodgate-key-file"), (path, value) -> {
// Elimate any legacy config values
if ("public-key.pem".equals(value.getString())) {
value.set("key.pem");
}
return new Object[]{ "advanced", "floodgate-key-file" };
})
// Bedrock
.addAction(path("bedrock", "broadcast-port"), moveTo("advanced", "bedrock"))
.addAction(path("bedrock", "compression-level"), moveTo("advanced", "bedrock"))
.addAction(path("bedrock", "enable-proxy-protocol"), renameAndMove("advanced", "bedrock", "use-haproxy-protocol"))
.addAction(path("bedrock", "proxy-protocol-whitelisted-ips"), renameAndMove("advanced", "bedrock", "haproxy-protocol-whitelisted-ips"))
.addAction(path("mtu"), moveTo("advanced", "bedrock"))
// Java
.addAction(path("remote", "use-proxy-protocol"), renameAndMove("advanced", "java", "use-haproxy-protocol"))
.addAction(path("disable-compression"), moveTo("advanced", "java"))
.addAction(path("use-direct-connection"), moveTo("advanced", "java"))
// Other
.addAction(path("default-locale"), (path, value) -> {
if (value.getString() == null) {
value.set("system");
}
return null;
})
.addAction(path("metrics", "uuid"), (path, value) -> {
if ("generateduuid".equals(value.getString())) {
// Manually copied config without Metrics UUID creation?
value.set(UUID.randomUUID());
}
return new Object[]{ "metrics-uuid" };
})
.addAction(path("metrics", "enabled"), (path, value) -> {
// Move to the root, not in the Metrics class.
return new Object[]{ "enable-metrics" };
})
.build())
.build();
static TransformAction renameAndMove(String... newPath) {
return ((path, value) -> Arrays.stream(newPath).toArray());
}
static TransformAction moveTo(String... newPath) {
return (path, value) -> {
Object[] arr = path.array();
if (arr.length == 0) {
throw new ConfigurateException(value, "The root node cannot be renamed!");
} else {
// create a new array with space for newPath segments + the original last segment
Object[] result = new Object[newPath.length + 1];
System.arraycopy(newPath, 0, result, 0, newPath.length);
result[newPath.length] = arr[arr.length - 1];
return result;
}
};
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationVisitor;
/**
* Moves comments from a different node and puts them on this node
*/
public final class ConfigurationCommentMover implements ConfigurationVisitor.Stateless<RuntimeException> {
private final CommentedConfigurationNode otherRoot;
private ConfigurationCommentMover(@NonNull CommentedConfigurationNode otherNode) {
this.otherRoot = otherNode;
}
@Override
public void enterNode(final ConfigurationNode node) {
if (!(node instanceof CommentedConfigurationNode destination)) {
// Should not occur because all nodes in a tree are the same type,
// and our static method below ensures this visitor is only used on CommentedConfigurationNodes
throw new IllegalStateException(node.path() + " is not a CommentedConfigurationNode");
}
// Node with the same path
CommentedConfigurationNode source = otherRoot.node(node.path());
moveSingle(source, destination);
}
private static void moveSingle(@NonNull CommentedConfigurationNode source, @NonNull CommentedConfigurationNode destination) {
// Only transfer the comment, overriding if necessary
String comment = source.comment();
if (comment != null) {
destination.comment(comment);
}
}
/**
* Moves comments from a source node and its children to a destination node and its children (of a different tree), overriding if necessary.
* Comments are only moved to the destination node and its children which exist.
* Comments are only moved to and from nodes with the exact same path.
*
* @param source the source of the comments, which must be the topmost parent of a tree
* @param destination the destination of the comments, any node in a different tree
*/
public static void moveComments(@NonNull CommentedConfigurationNode source, @NonNull CommentedConfigurationNode destination) {
if (source.parent() != null) {
throw new IllegalArgumentException("source is not the base of the tree it is within: " + source.path());
}
if (source.isNull()) {
// It has no value(s), but may still have a comment on it. Don't both traversing the whole destination tree.
moveSingle(source, destination);
} else {
destination.visit(new ConfigurationCommentMover(source));
}
}
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -23,20 +23,15 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.standalone; package org.geysermc.geyser.configuration;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.lang.annotation.ElementType;
import lombok.Getter; import java.lang.annotation.Retention;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Path; @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
import java.nio.file.Paths; @Retention(RetentionPolicy.RUNTIME)
public @interface ExcludePlatform {
@Getter String[] platforms();
@JsonIgnoreProperties(ignoreUnknown = true)
public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration {
@Override
public Path getFloodgateKeyPath() {
return Paths.get(getFloodgateKeyFile());
}
} }

View File

@@ -0,0 +1,475 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.CooldownUtils;
import org.spongepowered.configurate.interfaces.meta.Exclude;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString;
import org.spongepowered.configurate.interfaces.meta.range.NumericRange;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ConfigSerializable
public interface GeyserConfig {
@Comment("Network settings for the Bedrock listener")
BedrockConfig bedrock();
@Comment("Network settings for the Java server connection")
JavaConfig java();
@Comment("MOTD settings")
MotdConfig motd();
@Comment("Gameplay options that affect Bedrock players")
GameplayConfig gameplay();
@Comment("The default locale if we don't have the one the client requested. If set to \"system\", the system's language will be used.")
@DefaultString(GeyserLocale.SYSTEM_LOCALE)
@NonNull
String defaultLocale();
@Comment("Whether player IP addresses will be logged by the server.")
@DefaultBoolean(true)
boolean logPlayerIpAddresses();
@Comment("""
For online mode authentication type only.
Stores a list of Bedrock player usernames that should have their Java Edition account saved after login.
This saves a token that can be reused to authenticate the player later. This does not save emails or passwords,
but you should still be cautious when adding to this list and giving others access to this Geyser instance's files.
Removing a name from this list will delete its cached login information on the next Geyser startup.
The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json".""")
default List<String> savedUserLogins() {
return List.of("ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername",
"ThisOtherExampleUsernameShouldAlsoBeLongEnough");
}
@Comment("""
For online mode authentication type only.
Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account.
User is allowed to disconnect from the server during this period.""")
@DefaultNumeric(120)
int pendingAuthenticationTimeout();
@Comment("""
Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version
that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms
auto-update.""")
@DefaultBoolean(true)
boolean notifyOnNewBedrockUpdate();
@Comment("Advanced configuration options. These usually do not need modifications.")
AdvancedConfig advanced();
@Comment("""
bStats is a stat tracker that is entirely anonymous and tracks only basic information
about Geyser, such as how many people are online, how many servers are using Geyser,
what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.
https://bstats.org/plugin/server-implementation/GeyserMC""")
@DefaultBoolean(true)
@ExcludePlatform(platforms = {"BungeeCord", "Spigot", "Velocity"}) // bStats platform versions used
boolean enableMetrics();
@Comment("The bstats metrics uuid. Do not touch!")
@ExcludePlatform(platforms = {"BungeeCord", "Spigot", "Velocity"}) // bStats platform versions used
default UUID metricsUuid() {
return UUID.randomUUID();
}
@Comment("If debug messages should be sent through console")
boolean debugMode();
@Comment("Do not change!")
@SuppressWarnings("unused")
default int configVersion() {
return Constants.CONFIG_VERSION;
}
@ConfigSerializable
interface BedrockConfig extends BedrockListener {
@Comment("""
The IP address that Geyser will bind on to listen for incoming Bedrock connections.
Generally, you should only change this if you want to limit what IPs can connect to your server.""")
@NonNull
@Override
@DefaultString("0.0.0.0")
@AsteriskSerializer.Asterisk
String address();
@Comment("""
The port that will Geyser will listen on for incoming Bedrock connections.
Since Minecraft: Bedrock Edition uses UDP, this port must allow UDP traffic.""")
@Override
@DefaultNumeric(19132)
@NumericRange(from = 0, to = 65535)
int port();
@Comment("""
Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock.
This option makes the Bedrock port the same as the Java port every time you start the server.""")
@DefaultBoolean
@PluginSpecific
boolean cloneRemotePort();
void address(String address);
void port(int port);
@Exclude
@Override
default int broadcastPort() {
return GeyserImpl.getInstance().config().advanced().bedrock().broadcastPort();
}
@Exclude
@Override
default String primaryMotd() {
return GeyserImpl.getInstance().config().motd().primaryMotd();
}
@Exclude
@Override
default String secondaryMotd() {
return GeyserImpl.getInstance().config().motd().secondaryMotd();
}
@Exclude
@Override
default String serverName() {
return GeyserImpl.getInstance().config().gameplay().serverName();
}
}
@ConfigSerializable
interface JavaConfig extends RemoteServer {
void address(String address);
void port(int port);
@Comment("""
What type of authentication Bedrock players will be checked against when logging into the Java server.
Can be "floodgate" (see https://wiki.geysermc.org/floodgate/), "online", or "offline".""")
@NonNull
@Override
default AuthType authType() {
return AuthType.ONLINE;
}
void authType(AuthType authType);
boolean forwardHostname();
@Override
@Exclude
default String minecraftVersion() {
return GameProtocol.getJavaMinecraftVersion();
}
@Override
@Exclude
default int protocolVersion() {
return GameProtocol.getJavaProtocolVersion();
}
@Override
@Exclude
default boolean resolveSrv() {
return false;
}
}
@ConfigSerializable
interface MotdConfig {
@Comment("""
The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true.
If either of these are empty, the respective string will default to "Geyser\"""")
@DefaultString("Geyser")
String primaryMotd();
@DefaultString("Another Geyser server.")
String secondaryMotd();
@Comment("Whether Geyser should relay the MOTD from the Java server to Bedrock players.")
@DefaultBoolean(true)
boolean passthroughMotd();
@Comment("""
Maximum amount of players that can connect.
This is only visual, and is only applied if passthrough-motd is disabled.""")
@DefaultNumeric(100)
int maxPlayers();
@Comment("Whether to relay the player count and max players from the Java server to Bedrock players.")
@DefaultBoolean(true)
boolean passthroughPlayerCounts();
@Comment("""
Whether to use server API methods to determine the Java server's MOTD and ping passthrough.
There is no need to disable this unless your MOTD or player count does not appear properly.""")
@DefaultBoolean(true)
@PluginSpecific
boolean integratedPingPassthrough();
@Comment("How often to ping the Java server to refresh MOTD and player count, in seconds.")
@DefaultNumeric(3)
int pingPassthroughInterval();
}
@ConfigSerializable
interface GameplayConfig {
@Comment("The server name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.")
@DefaultString("Geyser")
String serverName();
@Comment("""
Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat.
Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below:
https://geysermc.org/img/external/cooldown_indicator.png
This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0
This setting can be set to "title", "actionbar" or "false\"""")
default CooldownUtils.CooldownType showCooldown() {
return CooldownUtils.CooldownType.TITLE;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
@Comment("""
Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.""")
@DefaultBoolean(true)
boolean commandSuggestions();
@Comment("Controls if coordinates are shown to players.")
@DefaultBoolean(true)
boolean showCoordinates();
@Comment("Whether Bedrock players are blocked from performing their scaffolding-style bridging.")
boolean disableBedrockScaffolding();
@Comment("""
Bedrock prevents building and displaying blocks above Y127 in the Nether.
This config option works around that by changing the Nether dimension ID to the End ID.
The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome.""")
boolean netherRoofWorkaround();
@Comment("""
Whether to show Bedrock Edition emotes to other Bedrock Edition players.
""")
@DefaultBoolean(true)
boolean emotesEnabled();
@Comment("""
Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative,
or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item.
This config option can be set to any Bedrock item identifier. If you want to set this to a custom item, make sure that you specify the item in the following format: "geyser_custom:<mapping-name>"
""")
@DefaultString("minecraft:barrier")
String unusableSpaceBlock();
@Comment("""
Whether to add any items and blocks which normally does not exist in Bedrock Edition.
This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching.
If this is disabled, furnace minecart items will be mapped to hopper minecart items.
Geyser's block, item, and skull mappings systems will also be disabled.
This option requires a restart of Geyser in order to change its setting.""")
@DefaultBoolean(true)
boolean enableCustomContent();
@Comment("""
Force clients to load all resource packs if there are any.
If set to false, it allows the user to connect to the server even if they don't
want to download the resource packs.""")
@DefaultBoolean(true)
boolean forceResourcePacks();
@Comment("""
Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players.
If enabled, force-resource-packs will be enabled.""")
@DefaultBoolean(true)
boolean enableIntegratedPack();
@Comment("""
Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate
ping, it may also cause players to time out more easily.""")
boolean forwardPlayerPing();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
@Comment("""
Allows Xbox achievements to be unlocked.
If a player types in an unknown command, they will receive a message that states cheats are disabled.
Otherwise, commands work as expected.""")
boolean xboxAchievementsEnabled();
@Comment("""
The maximum number of custom skulls to be displayed per player. Increasing this may decrease performance on weaker devices.
A value of 0 will disable all custom skulls.
Setting this to -1 will cause all custom skulls to be displayed regardless of distance or number.""")
@DefaultNumeric(128)
int maxVisibleCustomSkulls();
@Comment("The radius in blocks around the player in which custom skulls are displayed.")
@DefaultNumeric(32)
int customSkullRenderDistance();
}
@ConfigSerializable
interface AdvancedBedrockConfig {
@Comment("""
The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server.
A value of 0 will broadcast the port specified above.
DO NOT change this unless Geyser runs on a different port than the one that is used to connect.""")
@DefaultNumeric(0)
@NumericRange(from = 0, to = 65535)
int broadcastPort();
void broadcastPort(int port);
@Comment("""
How much to compress network traffic to the Bedrock client. The higher the number, the more CPU usage used, but
the smaller the bandwidth used. Does not have any effect below -1 or above 9. Set to -1 to disable.""")
@DefaultNumeric(6)
@NumericRange(from = -1, to = 9)
int compressionLevel();
@Comment("""
Whether to expect HAPROXY protocol for connecting Bedrock clients.
This is useful only when you are running a UDP reverse proxy in front of your Geyser instance.
IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!""")
@DefaultBoolean
boolean useHaproxyProtocol();
@Comment("""
A list of allowed HAPROXY protocol speaking proxy IP addresses/subnets. Only effective when "use-proxy-protocol" is enabled, and
should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.).
Keeping this list empty means there is no IP address whitelist.
IP addresses, subnets, and links to plain text files are supported.""")
default List<String> haproxyProtocolWhitelistedIps() {
return Collections.emptyList();
}
@Comment("""
The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation.
1400 is the default.""")
@DefaultNumeric(1400)
int mtu();
@Comment("""
This option disables the auth step Geyser performs for connecting Bedrock players.
It can be used to allow connections from ProxyPass and WaterdogPE. In these cases, make sure that users
cannot directly connect to this Geyser instance. See https://www.spigotmc.org/wiki/firewall-guide/ for
assistance - and use UDP instead of TCP.
Disabling Bedrock authentication for other use-cases is NOT SUPPORTED, as it allows anyone to spoof usernames, and is therefore a security risk.
All Floodgate functionality (including skin uploading and account linking) will also not work when this option is disabled.""")
@DefaultBoolean(true)
boolean validateBedrockLogin();
}
@ConfigSerializable
interface AdvancedJavaConfig {
@Comment("""
Whether to enable HAPROXY protocol when connecting to the Java server.
This is useful only when:
1) Your Java server supports HAPROXY protocol (it probably doesn't)
2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!""")
boolean useHaproxyProtocol();
@Comment("""
Whether to connect directly into the Java server without creating a TCP connection.
This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser.
If enabled, the remote address and port sections are ignored.
If disabled, expect performance decrease and latency increase.
""")
@DefaultBoolean(true)
@PluginSpecific
boolean useDirectConnection();
@Comment("""
Whether Geyser should attempt to disable packet compression (from the Java Server to Geyser) for Bedrock players.
This should be a benefit as there is no need to compress data when Java packets aren't being handled over the network.
This requires use-direct-connection to be true.
""")
@DefaultBoolean(true)
@PluginSpecific
boolean disableCompression();
}
@ConfigSerializable
interface AdvancedConfig {
@Comment("""
Specify how many days player skin images will be cached to disk to save downloading them from the internet.
A value of 0 is disabled. (Default: 0)""")
int cacheImages();
@Comment("""
Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle
a lot of scoreboard packets per second, this can cause serious lag.
This option allows you to specify after how many Scoreboard packets per seconds
the Scoreboard updates will be limited to four updates per second.""")
@DefaultNumeric(20)
int scoreboardPacketThreshold();
@Comment("""
Whether Geyser should send team names in command suggestions.
Disable this if you have a lot of teams used that you don't need as suggestions.""")
@DefaultBoolean(true)
boolean addTeamSuggestions();
@Comment("""
A list of remote resource pack urls to send to the Bedrock client for downloading.
The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/
""")
default List<String> resourcePackUrls() {
return Collections.emptyList();
}
// Cannot be type File yet because we may want to hide it in plugin instances.
@Comment("""
Floodgate uses encryption to ensure use from authorized sources.
This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity)
You can ignore this when not using Floodgate.
If you're using a plugin version of Floodgate on the same server, the key will automatically be picked up from Floodgate.""")
@DefaultString("key.pem")
String floodgateKeyFile();
@Comment("Advanced networking options for the Geyser to Java server connection")
AdvancedJavaConfig java();
@Comment("Advanced networking options for Geyser's Bedrock listener")
AdvancedBedrockConfig bedrock();
}
}

View File

@@ -1,191 +0,0 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.text.GeyserLocale;
import java.nio.file.Path;
import java.util.List;
public interface GeyserConfiguration {
/**
* If the config was originally 'auto' before the values changed
*/
void setAutoconfiguredRemote(boolean autoconfiguredRemote);
// Modify this when you introduce breaking changes into the config
int CURRENT_CONFIG_VERSION = 4;
IBedrockConfiguration getBedrock();
IRemoteConfiguration getRemote();
List<String> getSavedUserLogins();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
boolean isCommandSuggestions();
@JsonIgnore
boolean isPassthroughMotd();
@JsonIgnore
boolean isPassthroughPlayerCounts();
@JsonIgnore
boolean isLegacyPingPassthrough();
int getPingPassthroughInterval();
boolean isForwardPlayerPing();
int getMaxPlayers();
boolean isDebugMode();
@Deprecated
boolean isAllowThirdPartyCapes();
@Deprecated
boolean isAllowThirdPartyEars();
String getShowCooldown();
boolean isShowCoordinates();
boolean isDisableBedrockScaffolding();
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
String getDefaultLocale();
Path getFloodgateKeyPath();
boolean isAddNonBedrockItems();
boolean isAboveBedrockNetherBuilding();
boolean isForceResourcePacks();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
boolean isXboxAchievementsEnabled();
int getCacheImages();
boolean isAllowCustomSkulls();
int getMaxVisibleCustomSkulls();
int getCustomSkullRenderDistance();
boolean isLogPlayerIpAddresses();
boolean isNotifyOnNewBedrockUpdate();
String getUnusableSpaceBlock();
IMetricsInfo getMetrics();
int getPendingAuthenticationTimeout();
boolean isAutoconfiguredRemote();
interface IBedrockConfiguration extends BedrockListener {
void setAddress(String address);
void setPort(int port);
void setBroadcastPort(int broadcastPort);
boolean isCloneRemotePort();
int getCompressionLevel();
boolean isEnableProxyProtocol();
List<String> getProxyProtocolWhitelistedIPs();
/**
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()}
*/
List<CIDRMatcher> getWhitelistedIPsMatchers();
}
interface IRemoteConfiguration extends RemoteServer {
void setAddress(String address);
void setPort(int port);
boolean isUseProxyProtocol();
boolean isForwardHost();
default String minecraftVersion() {
return GameProtocol.getJavaMinecraftVersion();
}
default int protocolVersion() {
return GameProtocol.getJavaProtocolVersion();
}
void setAuthType(AuthType authType);
}
interface IMetricsInfo {
boolean isEnabled();
String getUniqueId();
}
int getScoreboardPacketThreshold();
// if u have offline mode enabled pls be safe
boolean isEnableProxyConnections();
int getMtu();
boolean isUseDirectConnection();
boolean isDisableCompression();
int getConfigVersion();
static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) {
if (geyserConfig.getConfigVersion() < CURRENT_CONFIG_VERSION) {
geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.outdated"));
} else if (geyserConfig.getConfigVersion() > CURRENT_CONFIG_VERSION) {
geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.too_new"));
}
}
}

View File

@@ -25,26 +25,20 @@
package org.geysermc.geyser.configuration; package org.geysermc.geyser.configuration;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true) @ConfigSerializable
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
public class GeyserCustomSkullConfiguration { public class GeyserCustomSkullConfiguration {
@JsonProperty("player-usernames")
private List<String> playerUsernames; private List<String> playerUsernames;
@JsonProperty("player-uuids")
private List<String> playerUUIDs; private List<String> playerUUIDs;
@JsonProperty("player-profiles")
private List<String> playerProfiles; private List<String> playerProfiles;
@JsonProperty("skin-hashes")
private List<String> skinHashes; private List<String> skinHashes;
public List<String> getPlayerUsernames() { public List<String> getPlayerUsernames() {
@@ -62,4 +56,14 @@ public class GeyserCustomSkullConfiguration {
public List<String> getPlayerSkinHashes() { public List<String> getPlayerSkinHashes() {
return Objects.requireNonNullElse(skinHashes, Collections.emptyList()); return Objects.requireNonNullElse(skinHashes, Collections.emptyList());
} }
@Override
public String toString() {
return "GeyserCustomSkullConfiguration{" +
"playerUsernames=" + playerUsernames +
", playerUUIDs=" + playerUUIDs +
", playerProfiles=" + playerProfiles +
", skinHashes=" + skinHashes +
'}';
}
} }

View File

@@ -1,366 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.WebUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
public abstract class GeyserJacksonConfiguration implements GeyserConfiguration {
@Setter
private boolean autoconfiguredRemote = false;
private BedrockConfiguration bedrock = new BedrockConfiguration();
private RemoteConfiguration remote = new RemoteConfiguration();
@JsonProperty("saved-user-logins")
private List<String> savedUserLogins = Collections.emptyList();
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile = "key.pem";
public abstract Path getFloodgateKeyPath();
@JsonProperty("command-suggestions")
private boolean commandSuggestions = true;
@JsonProperty("passthrough-motd")
private boolean isPassthroughMotd = false;
@JsonProperty("passthrough-player-counts")
private boolean isPassthroughPlayerCounts = false;
@JsonProperty("legacy-ping-passthrough")
private boolean isLegacyPingPassthrough = false;
@JsonProperty("ping-passthrough-interval")
private int pingPassthroughInterval = 3;
@JsonProperty("forward-player-ping")
private boolean forwardPlayerPing = false;
@JsonProperty("max-players")
private int maxPlayers = 100;
@JsonProperty("debug-mode")
private boolean debugMode = false;
@JsonProperty("allow-third-party-capes")
private boolean allowThirdPartyCapes = false;
@JsonProperty("show-cooldown")
private String showCooldown = "title";
@JsonProperty("show-coordinates")
private boolean showCoordinates = true;
@JsonProperty("disable-bedrock-scaffolding")
private boolean disableBedrockScaffolding = false;
@JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class)
@JsonProperty("emote-offhand-workaround")
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;
@JsonProperty("allow-third-party-ears")
private boolean allowThirdPartyEars = false;
@JsonProperty("default-locale")
private String defaultLocale = null; // is null by default so system language takes priority
@JsonProperty("cache-images")
private int cacheImages = 0;
@JsonProperty("allow-custom-skulls")
private boolean allowCustomSkulls = true;
@JsonProperty("max-visible-custom-skulls")
private int maxVisibleCustomSkulls = 128;
@JsonProperty("custom-skull-render-distance")
private int customSkullRenderDistance = 32;
@JsonProperty("add-non-bedrock-items")
private boolean addNonBedrockItems = true;
@JsonProperty("above-bedrock-nether-building")
private boolean aboveBedrockNetherBuilding = false;
@JsonProperty("force-resource-packs")
private boolean forceResourcePacks = true;
@JsonProperty("xbox-achievements-enabled")
private boolean xboxAchievementsEnabled = false;
@JsonProperty("log-player-ip-addresses")
private boolean logPlayerIpAddresses = true;
@JsonProperty("notify-on-new-bedrock-update")
private boolean notifyOnNewBedrockUpdate = true;
@JsonProperty("unusable-space-block")
private String unusableSpaceBlock = "minecraft:barrier";
private MetricsInfo metrics = new MetricsInfo();
@JsonProperty("pending-authentication-timeout")
private int pendingAuthenticationTimeout = 120;
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BedrockConfiguration implements IBedrockConfiguration {
@AsteriskSerializer.Asterisk(isIp = true)
@JsonProperty("address")
@Setter
private String address = "0.0.0.0";
@Override
public @NonNull String address() {
return address;
}
@Setter
@JsonProperty("port")
private int port = 19132;
@Override
public int port() {
return port;
}
@Setter
@JsonProperty("broadcast-port")
private int broadcastPort = 0;
@Override
public int broadcastPort() {
return broadcastPort;
}
@Getter
@JsonProperty("clone-remote-port")
private boolean cloneRemotePort = false;
@JsonProperty("motd1")
private String motd1 = "GeyserMC";
@Override
public String primaryMotd() {
return motd1;
}
@JsonProperty("motd2")
private String motd2 = "Geyser";
@Override
public String secondaryMotd() {
return motd2;
}
@JsonProperty("server-name")
private String serverName = GeyserImpl.NAME;
@Override
public String serverName() {
return serverName;
}
@JsonProperty("compression-level")
private int compressionLevel = 6;
public int getCompressionLevel() {
return Math.max(-1, Math.min(compressionLevel, 9));
}
@Getter
@JsonProperty("enable-proxy-protocol")
private boolean enableProxyProtocol = false;
@Getter
@JsonProperty("proxy-protocol-whitelisted-ips")
private List<String> proxyProtocolWhitelistedIPs = Collections.emptyList();
@JsonIgnore
private List<CIDRMatcher> whitelistedIPsMatchers = null;
@Override
public List<CIDRMatcher> getWhitelistedIPsMatchers() {
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
if (matchers == null) {
synchronized (this) {
// Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line
List<String> whitelistedCIDRs = new ArrayList<>();
for (String ip: proxyProtocolWhitelistedIPs) {
if (!ip.startsWith("http")) {
whitelistedCIDRs.add(ip);
continue;
}
WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add);
}
this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream()
.map(CIDRMatcher::new)
.collect(Collectors.toList());
}
}
return Collections.unmodifiableList(matchers);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class RemoteConfiguration implements IRemoteConfiguration {
@Setter
@AsteriskSerializer.Asterisk(isIp = true)
@JsonProperty("address")
private String address = "auto";
@Override
public String address() {
return address;
}
@JsonDeserialize(using = PortDeserializer.class)
@Setter
@JsonProperty("port")
private int port = 25565;
@Override
public int port() {
return port;
}
@Setter
@JsonDeserialize(using = AuthTypeDeserializer.class)
@JsonProperty("auth-type")
private AuthType authType = AuthType.ONLINE;
@Override
public @NonNull AuthType authType() {
return authType;
}
@Override
public boolean resolveSrv() {
return false;
}
@Getter
@JsonProperty("use-proxy-protocol")
private boolean useProxyProtocol = false;
@Getter
@JsonProperty("forward-hostname")
private boolean forwardHost = false;
}
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MetricsInfo implements IMetricsInfo {
private boolean enabled = true;
@JsonDeserialize(using = MetricsIdDeserializer.class)
@JsonProperty("uuid")
private String uniqueId = UUID.randomUUID().toString();
private static class MetricsIdDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String uuid = p.getValueAsString();
if ("generateduuid".equals(uuid)) {
// Compensate for configs not copied from the jar
return UUID.randomUUID().toString();
}
return uuid;
}
}
}
@JsonProperty("scoreboard-packet-threshold")
private int scoreboardPacketThreshold = 10;
@JsonProperty("enable-proxy-connections")
private boolean enableProxyConnections = false;
@JsonProperty("mtu")
private int mtu = 1400;
@JsonProperty("use-direct-connection")
private boolean useDirectConnection = true;
@JsonProperty("disable-compression")
private boolean isDisableCompression = true;
@JsonProperty("config-version")
private int configVersion = 0;
/**
* Ensure that the port deserializes in the config as a number no matter what.
*/
protected static class PortDeserializer extends JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.invalid_port"));
return 25565;
}
}
}
public static class AuthTypeDeserializer extends JsonDeserializer<AuthType> {
@Override
public AuthType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return AuthType.getByName(p.getValueAsString());
}
}
}

View File

@@ -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.
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import io.leangen.geantyref.TypeToken;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.Scalars;
import org.spongepowered.configurate.serialize.SerializationException;
import java.lang.reflect.Type;
import java.util.Locale;
import java.util.function.Predicate;
/**
* Ensures enum values are written to lowercase. {@link Scalars#ENUM} will read enum values
* in any case.
*/
final class LowercaseEnumSerializer extends ScalarSerializer<Enum<?>> {
LowercaseEnumSerializer() {
super(new TypeToken<Enum<?>>() {});
}
@Override
public Enum<?> deserialize(Type type, Object obj) throws SerializationException {
return Scalars.ENUM.deserialize(type, obj);
}
@Override
protected Object serialize(Enum<?> item, Predicate<Class<?>> typeSupported) {
return item.name().toLowerCase(Locale.ROOT);
}
}

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -25,26 +25,17 @@
package org.geysermc.geyser.configuration; package org.geysermc.geyser.configuration;
import com.fasterxml.jackson.core.JsonParser; import java.lang.annotation.ElementType;
import com.fasterxml.jackson.databind.DeserializationContext; import java.lang.annotation.Retention;
import com.fasterxml.jackson.databind.JsonDeserializer; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.io.IOException; /**
* Add to a config value to indicate this field is only for plugin versions of Geyser,
public enum EmoteOffhandWorkaroundOption { * or vice-versa.
NO_EMOTES, */
EMOTES_AND_OFFHAND, @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
DISABLED; @Retention(RetentionPolicy.RUNTIME)
public @interface PluginSpecific {
public static class Deserializer extends JsonDeserializer<EmoteOffhandWorkaroundOption> { boolean forPlugin() default true;
@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;
};
}
}
} }

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.dump; package org.geysermc.geyser.dump;
import com.google.gson.annotations.JsonAdapter;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
@@ -38,7 +39,7 @@ public class BootstrapDumpInfo {
private final PlatformType platform; private final PlatformType platform;
public BootstrapDumpInfo() { public BootstrapDumpInfo() {
this.platform = GeyserImpl.getInstance().getPlatformType(); this.platform = GeyserImpl.getInstance().platformType();
} }
@Getter @Getter
@@ -55,7 +56,7 @@ public class BootstrapDumpInfo {
@AllArgsConstructor @AllArgsConstructor
public static class ListenerInfo { public static class ListenerInfo {
@AsteriskSerializer.Asterisk(isIp = true) @JsonAdapter(value = AsteriskSerializer.class)
public String ip; public String ip;
public int port; public int port;
} }

View File

@@ -25,28 +25,39 @@
package org.geysermc.geyser.dump; 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.hash.Hashing;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import com.google.common.io.Files; 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.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter; import lombok.Getter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.FloodgateInfoHolder;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.network.GameProtocol; 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.session.GeyserSession;
import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.util.CpuUtils; import org.geysermc.geyser.util.CpuUtils;
import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.WebUtils; 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.File;
import java.io.IOException; import java.io.IOException;
@@ -57,12 +68,15 @@ import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Paths; 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; import java.util.stream.Collectors;
@Getter @Getter
public class DumpInfo { public class DumpInfo {
@JsonIgnore
private static final long MEGABYTE = 1024L * 1024L; private static final long MEGABYTE = 1024L * 1024L;
private final DumpInfo.VersionInfo versionInfo; private final DumpInfo.VersionInfo versionInfo;
@@ -71,16 +85,17 @@ public class DumpInfo {
private final Locale systemLocale; private final Locale systemLocale;
private final String systemEncoding; private final String systemEncoding;
private final GitInfo gitInfo; private final GitInfo gitInfo;
private final GeyserConfiguration config; private Object config;
private final Floodgate floodgate;
private final Object2IntMap<DeviceOs> userPlatforms; private final Object2IntMap<DeviceOs> userPlatforms;
private final int connectionAttempts; private final int connectionAttempts;
private final HashInfo hashInfo; private final String hash;
private final RamInfo ramInfo; private final RamInfo ramInfo;
private LogsInfo logsInfo; private LogsInfo logsInfo;
private final BootstrapDumpInfo bootstrapInfo; private final BootstrapDumpInfo bootstrapInfo;
private final FlagsInfo flagsInfo; private final FlagsInfo flagsInfo;
private final List<ExtensionInfo> extensionInfo; private final List<ExtensionInfo> extensionInfo;
private final List<PackInfo> packInfo;
private final MappingInfo mappingInfo;
public DumpInfo(GeyserImpl geyser, boolean addLog) { public DumpInfo(GeyserImpl geyser, boolean addLog) {
this.versionInfo = new VersionInfo(); 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.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 { try {
// https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file // Workaround for JsonAdapter not being allowed on methods
// https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java ConfigurationOptions options = InterfaceDefaultOptions.addTo(ConfigurationOptions.defaults(), builder ->
File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI()); builder.addProcessor(AsteriskSerializer.Asterisk.class, String.class, AsteriskSerializer.CONFIGURATE_SERIALIZER))
ByteSource byteSource = Files.asByteSource(file); .shouldCopyDefaults(false);
// Jenkins uses MD5 for its hash - TODO remove
//noinspection UnstableApiUsage,deprecation ConfigurationNode configNode = CommentedConfigurationNode.root(options);
md5Hash = byteSource.hash(Hashing.md5()).toString(); configNode.set(geyser.config());
//noinspection UnstableApiUsage this.config = toGson(configNode);
sha256Hash = byteSource.hash(Hashing.sha256()).toString(); } catch (SerializationException e) {
} catch (Exception e) { e.printStackTrace();
if (this.config.isDebugMode()) { if (geyser.config().debugMode()) {
e.printStackTrace(); 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(); this.ramInfo = new RamInfo();
@@ -140,6 +163,44 @@ public class DumpInfo {
for (Extension extension : GeyserApi.api().extensionManager().extensions()) { 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.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 @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 @Getter
public static class LogsInfo { public static class LogsInfo {
private String link; private String link;
@@ -253,16 +303,13 @@ public class DumpInfo {
Map<String, String> fields = new HashMap<>(); Map<String, String> fields = new HashMap<>();
fields.put("content", FileUtils.readAllLines(geyser.getBootstrap().getLogsPath()).collect(Collectors.joining("\n"))); 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) { } } catch (IOException ignored) { }
} }
} }
public record HashInfo(String md5Hash, String sha256Hash) {
}
public record RamInfo(long free, long total, long max) { public record RamInfo(long free, long total, long max) {
public RamInfo() { public RamInfo() {
this(Runtime.getRuntime().freeMemory() / MEGABYTE, this(Runtime.getRuntime().freeMemory() / MEGABYTE,
@@ -283,7 +330,17 @@ public class DumpInfo {
public record ExtensionInfo(boolean enabled, String name, String version, String apiVersion, String main, List<String> authors) { public record ExtensionInfo(boolean enabled, String name, String version, String apiVersion, String main, List<String> authors) {
} }
public record GitInfo(String buildNumber, @JsonProperty("git.commit.id.abbrev") String commitHashAbbrev, @JsonProperty("git.commit.id") String commitHash, public record GitInfo(String buildNumber, @SerializedName("git.commit.id.abbrev") String commitHashAbbrev, @SerializedName("git.commit.id") String commitHash,
@JsonProperty("git.branch") String branchName, @JsonProperty("git.remote.origin.url") String originUrl) { @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) {
} }
} }

View File

@@ -72,7 +72,7 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
if (translator.acceptedType() != metadata.getType()) { 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()); 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()); GeyserImpl.getInstance().getLogger().debug(metadata.toString());
} }
return; return;

View File

@@ -220,7 +220,7 @@ public class Entity implements GeyserEntity {
flagsDirty = false; flagsDirty = false;
if (session.getGeyser().getConfig().isDebugMode() && PRINT_ENTITY_SPAWN_DEBUG) { if (session.getGeyser().config().debugMode() && PRINT_ENTITY_SPAWN_DEBUG) {
EntityType type = definition.entityType(); EntityType type = definition.entityType();
String name = type != null ? type.name() : getClass().getSimpleName(); String name = type != null ? type.name() : getClass().getSimpleName();
session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");

View File

@@ -34,6 +34,7 @@ import org.geysermc.geyser.api.pack.exception.ResourcePackException;
import org.geysermc.geyser.api.pack.option.ResourcePackOption; import org.geysermc.geyser.api.pack.option.ResourcePackOption;
import org.geysermc.geyser.pack.GeyserResourcePack; import org.geysermc.geyser.pack.GeyserResourcePack;
import org.geysermc.geyser.pack.ResourcePackHolder; import org.geysermc.geyser.pack.ResourcePackHolder;
import org.geysermc.geyser.util.GeyserIntegratedPackUtil;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -42,11 +43,12 @@ import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@Getter @Getter
public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent { public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent implements GeyserIntegratedPackUtil {
private final Map<UUID, ResourcePackHolder> packs; private final Map<UUID, ResourcePackHolder> packs;
public GeyserDefineResourcePacksEventImpl(Map<UUID, ResourcePackHolder> packMap) { public GeyserDefineResourcePacksEventImpl(Map<UUID, ResourcePackHolder> packMap) {
this.packs = packMap; this.packs = packMap;
registerGeyserPack(this);
} }
@Override @Override
@@ -61,6 +63,8 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack
throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION); throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION);
} }
preProcessPack(pack);
UUID uuid = resourcePack.uuid(); UUID uuid = resourcePack.uuid();
if (packs.containsKey(uuid)) { if (packs.containsKey(uuid)) {
throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE); throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE);
@@ -120,4 +124,14 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack
holder.optionHolder().validateAndAdd(holder.pack(), options); holder.optionHolder().validateAndAdd(holder.pack(), options);
} }
@Override
public void unregisterIntegratedPack() {
unregister(INTEGRATED_PACK_UUID);
}
@Override
public boolean integratedPackRegistered() {
return packs.containsKey(INTEGRATED_PACK_UUID);
}
} }

View File

@@ -45,6 +45,7 @@ import org.geysermc.geyser.pack.ResourcePackHolder;
import org.geysermc.geyser.pack.option.OptionHolder; import org.geysermc.geyser.pack.option.OptionHolder;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.GeyserIntegratedPackUtil;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
@@ -55,7 +56,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; 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 * 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); throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION);
} }
preProcessPack(pack);
UUID uuid = resourcePack.uuid(); UUID uuid = resourcePack.uuid();
if (packs.containsKey(uuid)) { if (packs.containsKey(uuid)) {
throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE); throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE);
@@ -178,6 +181,16 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
holder.validateAndAdd(pack, options); 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 // Methods used internally for e.g. ordered packs, or resource pack entries
public List<ResourcePackStackPacket.Entry> orderedPacks() { public List<ResourcePackStackPacket.Entry> orderedPacks() {
@@ -201,7 +214,14 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
public List<ResourcePacksInfoPacket.Entry> infoPacketEntries() { public List<ResourcePacksInfoPacket.Entry> infoPacketEntries() {
List<ResourcePacksInfoPacket.Entry> entries = new ArrayList<>(); List<ResourcePacksInfoPacket.Entry> entries = new ArrayList<>();
boolean anyCdn = packs.values().stream().anyMatch(holder -> holder.codec() instanceof UrlPackCodec);
boolean warned = false;
for (ResourcePackHolder holder : packs.values()) { 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(); GeyserResourcePack pack = holder.pack();
ResourcePackManifest.Header header = pack.manifest().header(); ResourcePackManifest.Header header = pack.manifest().header();
entries.add(new ResourcePacksInfoPacket.Entry( entries.add(new ResourcePacksInfoPacket.Entry(

View File

@@ -460,7 +460,7 @@ public final class ClickPlan {
} else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { } else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
stateIdIncrements = 1; stateIdIncrements = 1;
} else { } 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); session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan);
} }
stateIdIncrements = 1; stateIdIncrements = 1;

View File

@@ -103,7 +103,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
super(geyser, session); super(geyser, session);
ZlibCompression compression = new ZlibCompression(Zlib.RAW); 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); 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 // Can happen if an error occurs in the resource pack event; that'll disconnect the player
return PacketSignal.HANDLED; return PacketSignal.HANDLED;
} }
session.integratedPackActive(resourcePackLoadEvent.isIntegratedPackActive());
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries()); resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries());
resourcePacksInfo.setVibrantVisualsForceDisabled(!session.isAllowVibrantVisuals()); resourcePacksInfo.setVibrantVisualsForceDisabled(!session.isAllowVibrantVisuals());
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().config().gameplay().forceResourcePacks() ||
resourcePackLoadEvent.isIntegratedPackActive());
resourcePacksInfo.setWorldTemplateId(UUID.randomUUID()); resourcePacksInfo.setWorldTemplateId(UUID.randomUUID());
resourcePacksInfo.setWorldTemplateVersion("*"); resourcePacksInfo.setWorldTemplateVersion("*");
session.sendUpstreamPacket(resourcePacksInfo); session.sendUpstreamPacket(resourcePacksInfo);
GeyserLocale.loadGeyserLocale(session.locale()); GeyserLocale.loadGeyserLocale(session.locale());
@@ -264,7 +267,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
switch (packet.getStatus()) { switch (packet.getStatus()) {
case COMPLETED -> { case COMPLETED -> {
finishedResourcePackSending = true; finishedResourcePackSending = true;
if (geyser.getConfig().getRemote().authType() != AuthType.ONLINE) { if (geyser.config().java().authType() != AuthType.ONLINE) {
session.authenticate(session.getAuthData().name()); session.authenticate(session.getAuthData().name());
} else if (!couldLoginUserByName(session.getAuthData().name())) { } else if (!couldLoginUserByName(session.getAuthData().name())) {
// We must spawn the white world // We must spawn the white world
@@ -296,6 +299,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.sendUpstreamPacket(stackPacket); session.sendUpstreamPacket(stackPacket);
} }
case REFUSED -> session.disconnect("disconnectionScreen.resourcePack");
default -> { default -> {
GeyserImpl.getInstance().getLogger().debug("received unknown status packet: " + packet); GeyserImpl.getInstance().getLogger().debug("received unknown status packet: " + packet);
session.disconnect("disconnectionScreen.resourcePack"); session.disconnect("disconnectionScreen.resourcePack");
@@ -315,7 +319,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
} }
private boolean couldLoginUserByName(String bedrockUsername) { private boolean couldLoginUserByName(String bedrockUsername) {
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) { if (geyser.config().savedUserLogins().contains(bedrockUsername)) {
String authChain = geyser.authChainFor(bedrockUsername); String authChain = geyser.authChainFor(bedrockUsername);
if (authChain != null) { if (authChain != null) {
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
@@ -425,7 +429,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
// Also flushes packets // Also flushes packets
// Avoids bursting slower / delayed clients // Avoids bursting slower / delayed clients
session.sendUpstreamPacketImmediately(data); session.sendUpstreamPacketImmediately(data);
GeyserImpl.getInstance().getScheduledThread().schedule(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS); session.scheduleInEventLoop(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS);
} else { } else {
session.sendUpstreamPacket(data); session.sendUpstreamPacket(data);
} }

View File

@@ -50,7 +50,7 @@ public abstract class GeyserInjector {
* @param bootstrap the bootstrap of the Geyser instance. * @param bootstrap the bootstrap of the Geyser instance.
*/ */
public void initializeLocalChannel(GeyserBootstrap bootstrap) { public void initializeLocalChannel(GeyserBootstrap bootstrap) {
if (!bootstrap.getGeyserConfig().isUseDirectConnection()) { if (!bootstrap.config().advanced().java().useDirectConnection()) {
bootstrap.getGeyserLogger().debug("Disabling direct injection!"); bootstrap.getGeyserLogger().debug("Disabling direct injection!");
return; return;
} }

View File

@@ -46,7 +46,7 @@ import org.cloudburstmc.protocol.bedrock.BedrockPong;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.connection.ConnectionRequestEvent; import org.geysermc.geyser.api.event.connection.ConnectionRequestEvent;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand; 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.event.type.GeyserBedrockPingEventImpl;
import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.network.GameProtocol; 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.skin.SkinProvider;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.WebUtils;
import org.geysermc.mcprotocollib.network.helper.TransportHelper; import org.geysermc.mcprotocollib.network.helper.TransportHelper;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -131,7 +134,7 @@ public final class GeyserServer {
this.listenCount = 1; this.listenCount = 1;
} }
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) { if (this.geyser.config().advanced().bedrock().useHaproxyProtocol()) {
this.proxiedAddresses = ExpiringMap.builder() this.proxiedAddresses = ExpiringMap.builder()
.expiration(30 + 1, TimeUnit.MINUTES) .expiration(30 + 1, TimeUnit.MINUTES)
.expirationPolicy(ExpirationPolicy.ACCESSED).build(); .expirationPolicy(ExpirationPolicy.ACCESSED).build();
@@ -139,7 +142,7 @@ public final class GeyserServer {
this.proxiedAddresses = null; this.proxiedAddresses = null;
} }
this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort(); this.broadcastPort = geyser.config().advanced().bedrock().broadcastPort();
} }
public CompletableFuture<Void> bind(InetSocketAddress address) { public CompletableFuture<Void> bind(InetSocketAddress address) {
@@ -161,12 +164,12 @@ public final class GeyserServer {
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this)); .addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
// Add proxy handler // Add proxy handler
boolean isProxyProtocol = this.geyser.getConfig().getBedrock().isEnableProxyProtocol(); boolean isProxyProtocol = this.geyser.config().advanced().bedrock().useHaproxyProtocol();
if (isProxyProtocol) { if (isProxyProtocol) {
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler()); 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) { 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 // We would already block any non-whitelisted IP addresses in onConnectionRequest so we can remove the rate limiter
channel.pipeline().remove(RakServerRateLimiter.NAME); channel.pipeline().remove(RakServerRateLimiter.NAME);
@@ -200,7 +203,7 @@ public final class GeyserServer {
@SuppressWarnings("Convert2MethodRef") @SuppressWarnings("Convert2MethodRef")
private ServerBootstrap createBootstrap() { private ServerBootstrap createBootstrap() {
if (this.geyser.getConfig().isDebugMode()) { if (this.geyser.config().debugMode()) {
this.geyser.getLogger().debug("Transport type: " + TRANSPORT.method().name()); this.geyser.getLogger().debug("Transport type: " + TRANSPORT.method().name());
if (TRANSPORT.datagramChannelClass() == NioDatagramChannel.class) { if (TRANSPORT.datagramChannelClass() == NioDatagramChannel.class) {
if (System.getProperties().contains("disableNativeEventLoop")) { if (System.getProperties().contains("disableNativeEventLoop")) {
@@ -216,7 +219,7 @@ public final class GeyserServer {
GeyserServerInitializer serverInitializer = new GeyserServerInitializer(this.geyser); GeyserServerInitializer serverInitializer = new GeyserServerInitializer(this.geyser);
playerGroup = serverInitializer.getEventLoopGroup(); 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); int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT);
this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit); this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit);
@@ -231,7 +234,7 @@ public final class GeyserServer {
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannelClass())) .channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannelClass()))
.group(group, childGroup) .group(group, childGroup)
.option(RakChannelOption.RAK_HANDLE_PING, true) .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_PACKET_LIMIT, rakPacketLimit)
.option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit) .option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit)
.option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie) .option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie)
@@ -239,10 +242,10 @@ public final class GeyserServer {
} }
public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
List<String> allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs(); List<String> allowedProxyIPs = geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps();
if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) { if (geyser.config().advanced().bedrock().useHaproxyProtocol() && !allowedProxyIPs.isEmpty()) {
boolean isWhitelistedIP = false; boolean isWhitelistedIP = false;
for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) { for (CIDRMatcher matcher : getWhitelistedIPsMatchers()) {
if (matcher.matches(inetSocketAddress.getAddress())) { if (matcher.matches(inetSocketAddress.getAddress())) {
isWhitelistedIP = true; isWhitelistedIP = true;
break; break;
@@ -256,8 +259,8 @@ public final class GeyserServer {
} }
String ip; String ip;
if (geyser.getConfig().isLogPlayerIpAddresses()) { if (geyser.config().logPlayerIpAddresses()) {
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { if (geyser.config().advanced().bedrock().useHaproxyProtocol()) {
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString(); ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
} else { } else {
ip = inetSocketAddress.toString(); ip = inetSocketAddress.toString();
@@ -283,10 +286,10 @@ public final class GeyserServer {
} }
public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) { public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) {
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { if (geyser.config().debugMode() && PRINT_DEBUG_PINGS) {
String ip; String ip;
if (geyser.getConfig().isLogPlayerIpAddresses()) { if (geyser.config().logPlayerIpAddresses()) {
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { if (geyser.config().advanced().bedrock().useHaproxyProtocol()) {
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString(); ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
} else { } else {
ip = inetSocketAddress.toString(); ip = inetSocketAddress.toString();
@@ -297,10 +300,10 @@ public final class GeyserServer {
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip)); geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip));
} }
GeyserConfiguration config = geyser.getConfig(); GeyserConfig config = geyser.config();
GeyserPingInfo pingInfo = null; GeyserPingInfo pingInfo = null;
if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) { if (config.motd().passthroughMotd() || config.motd().passthroughPlayerCounts()) {
IGeyserPingPassthrough pingPassthrough = geyser.getBootstrap().getGeyserPingPassthrough(); IGeyserPingPassthrough pingPassthrough = geyser.getBootstrap().getGeyserPingPassthrough();
if (pingPassthrough != null) { if (pingPassthrough != null) {
pingInfo = pingPassthrough.getPingInformation(inetSocketAddress); pingInfo = pingPassthrough.getPingInformation(inetSocketAddress);
@@ -317,25 +320,25 @@ public final class GeyserServer {
.ipv6Port(this.broadcastPort) .ipv6Port(this.broadcastPort)
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID)); .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[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
String mainMotd = (motd.length > 0) ? motd[0] : config.getBedrock().primaryMotd(); // First line of the motd. String mainMotd = (motd.length > 0) ? motd[0] : config.motd().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 subMotd = (motd.length > 1) ? motd[1] : config.motd().secondaryMotd(); // Second line of the motd if present, otherwise default.
pong.motd(mainMotd.trim()); 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. 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 { } else {
pong.motd(config.getBedrock().primaryMotd()); pong.motd(config.motd().primaryMotd());
pong.subMotd(config.getBedrock().secondaryMotd()); pong.subMotd(config.motd().secondaryMotd());
} }
// Placed here to prevent overriding values set in the ping event. // 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.playerCount(pingInfo.getPlayers().getOnline());
pong.maximumPlayerCount(pingInfo.getPlayers().getMax()); pong.maximumPlayerCount(pingInfo.getPlayers().getMax());
} else { } else {
pong.playerCount(geyser.getSessionManager().getSessions().size()); pong.playerCount(geyser.getSessionManager().getSessions().size());
pong.maximumPlayerCount(config.getMaxPlayers()); pong.maximumPlayerCount(config.motd().maxPlayers());
} }
this.geyser.eventBus().fire(new GeyserBedrockPingEventImpl(pong, inetSocketAddress)); this.geyser.eventBus().fire(new GeyserBedrockPingEventImpl(pong, inetSocketAddress));
@@ -386,6 +389,35 @@ public final class GeyserServer {
return pong; return pong;
} }
private List<CIDRMatcher> whitelistedIPsMatchers = null;
/**
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link GeyserConfig.AdvancedBedrockConfig#haproxyProtocolWhitelistedIps()}
*/
public List<CIDRMatcher> getWhitelistedIPsMatchers() {
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
if (matchers == null) {
synchronized (this) {
// Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line
List<String> whitelistedCIDRs = new ArrayList<>();
for (String ip: geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps()) {
if (!ip.startsWith("http")) {
whitelistedCIDRs.add(ip);
continue;
}
WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add);
}
this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream()
.map(CIDRMatcher::new)
.toList();
}
}
return Collections.unmodifiableList(matchers);
}
/** /**
* @return the throwable from the given supplier, or the throwable caught while calling the supplier. * @return the throwable from the given supplier, or the throwable caught while calling the supplier.
*/ */

View File

@@ -25,21 +25,23 @@
package org.geysermc.geyser.pack; package org.geysermc.geyser.pack;
import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.JsonArray;
import com.fasterxml.jackson.core.JsonParser; import com.google.gson.JsonDeserializationContext;
import com.fasterxml.jackson.databind.DeserializationContext; import com.google.gson.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonDeserializer; import com.google.gson.JsonElement;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 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.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.pack.ResourcePackManifest; import org.geysermc.geyser.api.pack.ResourcePackManifest;
import java.io.IOException; import java.lang.reflect.Type;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.UUID; import java.util.UUID;
public record GeyserResourcePackManifest( public record GeyserResourcePackManifest(
@JsonProperty("format_version") int formatVersion, @SerializedName("format_version") int formatVersion,
Header header, Header header,
Collection<Module> modules, Collection<Module> modules,
Collection<Dependency> dependencies, Collection<Dependency> dependencies,
@@ -60,13 +62,13 @@ public record GeyserResourcePackManifest(
this.settings = ensureNonNull(settings); 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 Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { } 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 { } public record Setting(String type, String text) implements ResourcePackManifest.Setting { }
@@ -75,7 +77,7 @@ public record GeyserResourcePackManifest(
return Collections.unmodifiableCollection(collection); 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 { public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
@Override @Override
@@ -83,11 +85,11 @@ public record GeyserResourcePackManifest(
return major + "." + minor + "." + patch; return major + "." + minor + "." + patch;
} }
public static class VersionDeserializer extends JsonDeserializer<Version> { public static class VersionDeserializer implements JsonDeserializer<Version> {
@Override @Override
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { public Version deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
int[] version = ctxt.readValue(p, int[].class); JsonArray array = json.getAsJsonArray();
return new Version(version[0], version[1], version[2]); return new Version(array.get(0).getAsInt(), array.get(1).getAsInt(), array.get(2).getAsInt());
} }
} }
} }

View File

@@ -78,7 +78,7 @@ public class SkullResourcePackManager {
Path packPath = cachePath.resolve("player_skulls.mcpack"); Path packPath = cachePath.resolve("player_skulls.mcpack");
File packFile = packPath.toFile(); 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 packFile.delete(); // No need to keep resource pack
return null; return null;
} }
@@ -161,7 +161,7 @@ public class SkullResourcePackManager {
} }
} catch (IOException e) { } catch (IOException e) {
GeyserImpl.getInstance().getLogger().debug("Unable to clean up skull skin cache."); GeyserImpl.getInstance().getLogger().debug("Unable to clean up skull skin cache.");
if (GeyserImpl.getInstance().getConfig().isDebugMode()) { if (GeyserImpl.getInstance().config().debugMode()) {
e.printStackTrace(); e.printStackTrace();
} }
} }

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -25,8 +25,7 @@
package org.geysermc.geyser.ping; package org.geysermc.geyser.ping;
import com.fasterxml.jackson.core.JsonParseException; import com.google.gson.JsonSyntaxException;
import com.fasterxml.jackson.databind.JsonMappingException;
import io.netty.handler.codec.haproxy.HAProxyCommand; import io.netty.handler.codec.haproxy.HAProxyCommand;
import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol; import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
@@ -34,18 +33,29 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.util.VarInts; import org.cloudburstmc.nbt.util.VarInts;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.util.JsonUtils;
import java.io.*; import java.io.ByteArrayOutputStream;
import java.net.*; import java.io.DataInputStream;
import java.util.concurrent.TimeUnit; 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 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 GeyserImpl geyser;
private final long interval;
public GeyserLegacyPingPassthrough(GeyserImpl geyser) { public GeyserLegacyPingPassthrough(GeyserImpl geyser, int interval) {
this.geyser = geyser; this.geyser = geyser;
this.interval = interval * 1000L;
} }
private GeyserPingInfo pingInfo; private GeyserPingInfo pingInfo;
@@ -56,12 +66,14 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
* @return GeyserPingPassthrough, or null if not initialized * @return GeyserPingPassthrough, or null if not initialized
*/ */
public static @Nullable IGeyserPingPassthrough init(GeyserImpl geyser) { public static @Nullable IGeyserPingPassthrough init(GeyserImpl geyser) {
if (geyser.getConfig().isPassthroughMotd() || geyser.getConfig().isPassthroughPlayerCounts()) { if (geyser.config().motd().passthroughMotd() || geyser.config().motd().passthroughPlayerCounts()) {
GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser);
// Ensure delay is not zero // 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.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 pingPassthrough;
} }
return null; return null;
@@ -74,9 +86,10 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
@Override @Override
public void run() { public void run() {
while (!geyser.isShuttingDown() && !geyser.isReloading()) {
try (Socket socket = new Socket()) { try (Socket socket = new Socket()) {
String address = geyser.getConfig().getRemote().address(); String address = geyser.config().java().address();
int port = geyser.getConfig().getRemote().port(); int port = geyser.config().java().port();
InetSocketAddress endpoint = new InetSocketAddress(address, port); InetSocketAddress endpoint = new InetSocketAddress(address, port);
socket.connect(endpoint, 5000); socket.connect(endpoint, 5000);
@@ -93,7 +106,7 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
byte[] buffer; byte[] buffer;
try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream())) { try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream())) {
if (geyser.getConfig().getRemote().isUseProxyProtocol()) { if (geyser.config().advanced().java().useHaproxyProtocol()) {
// HAProxy support // HAProxy support
// Based on https://github.com/netty/netty/blob/d8ad931488f6b942dabe28ecd6c399b4438da0a8/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessageEncoder.java#L78 // 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.write(HAPROXY_BINARY_PREFIX);
@@ -130,11 +143,11 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
} }
} }
this.pingInfo = GeyserImpl.JSON_MAPPER.readValue(buffer, GeyserPingInfo.class); this.pingInfo = JsonUtils.fromJson(buffer, GeyserPingInfo.class);
} catch (SocketTimeoutException | ConnectException ex) { } catch (SocketTimeoutException | ConnectException ex) {
this.pingInfo = null; this.pingInfo = null;
this.geyser.getLogger().debug("Connection timeout for ping passthrough."); this.geyser.getLogger().debug("Connection timeout for ping passthrough.");
} catch (JsonParseException | JsonMappingException ex) { } catch (JsonSyntaxException ex) {
this.geyser.getLogger().error("Failed to parse json when pinging server!", ex); this.geyser.getLogger().error("Failed to parse json when pinging server!", ex);
} catch (EOFException e) { } catch (EOFException e) {
this.pingInfo = null; this.pingInfo = null;
@@ -145,5 +158,12 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
} catch (IOException e) { } catch (IOException e) {
this.geyser.getLogger().error("IO error while trying to use legacy ping passthrough", e); this.geyser.getLogger().error("IO error while trying to use legacy ping passthrough", e);
} }
try {
Thread.sleep(interval);
} catch (InterruptedException ex) {
// no-op
}
}
} }
} }

View File

@@ -25,21 +25,25 @@
package org.geysermc.geyser.ping; package org.geysermc.geyser.ping;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.JsonDeserializationContext;
import com.fasterxml.jackson.annotation.JsonSetter; import com.google.gson.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.JsonAdapter;
import lombok.Data; import lombok.Data;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Type;
/** /**
* The structure of this class and its nested classes are specifically * The structure of this class and its nested classes are specifically
* designed for the format received by {@link GeyserLegacyPingPassthrough}. * designed for the format received by {@link GeyserLegacyPingPassthrough}.
*/ */
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserPingInfo { public class GeyserPingInfo {
@Nullable @Nullable
@JsonAdapter(DescriptionDeserializer.class)
private String description; private String description;
private Players players; private Players players;
@@ -58,13 +62,7 @@ public class GeyserPingInfo {
this.players = new Players(maxPlayers, onlinePlayers); this.players = new Players(maxPlayers, onlinePlayers);
} }
@JsonSetter("description")
void setDescription(JsonNode description) {
this.description = description.toString();
}
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Players { public static class Players {
private int max; private int max;
@@ -79,4 +77,14 @@ public class GeyserPingInfo {
this.online = online; this.online = online;
} }
} }
/**
* So GSON does not complain how we are treating Description - it will be converted to a proper Component later.
*/
private static final class DescriptionDeserializer implements JsonDeserializer<String> {
@Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return json.toString();
}
}
} }

View File

@@ -73,7 +73,7 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
} }
return true; return true;
} else { } else {
if (GeyserImpl.getInstance().getConfig().isDebugMode()) { if (GeyserImpl.getInstance().config().debugMode()) {
if (!IGNORED_PACKETS.contains(clazz)) { if (!IGNORED_PACKETS.contains(clazz)) {
GeyserImpl.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); GeyserImpl.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
} }

View File

@@ -25,14 +25,16 @@
package org.geysermc.geyser.registry.loader; package org.geysermc.geyser.registry.loader;
import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.annotations.SerializedName;
import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.reflect.TypeToken;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.util.JsonUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Object2IntMap<String>> { public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Object2IntMap<String>> {
@@ -43,12 +45,12 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Obj
// crashes the client. Therefore, we need to have a list of all valid Bedrock biome IDs with which we can use from. // crashes the client. Therefore, we need to have a list of all valid Bedrock biome IDs with which we can use from.
// The server sends the corresponding Java network IDs, so we don't need to worry about that now. // The server sends the corresponding Java network IDs, so we don't need to worry about that now.
// Reference variable for Jackson to read off of // Reference variable for Gson to read off of
TypeReference<Map<String, BiomeEntry>> biomeEntriesType = new TypeReference<>() { }; Type biomeEntriesType = new TypeToken<Map<String, BiomeEntry>>() { }.getType();
Map<String, BiomeEntry> biomeEntries; Map<String, BiomeEntry> biomeEntries;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/biomes.json")) { 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) { } catch (IOException e) {
throw new AssertionError("Unable to load Bedrock runtime biomes", e); throw new AssertionError("Unable to load Bedrock runtime biomes", e);
} }
@@ -66,7 +68,7 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader<String, Obj
/** /**
* The Bedrock network ID for this biome. * The Bedrock network ID for this biome.
*/ */
@JsonProperty("bedrock_id") @SerializedName("bedrock_id")
private int bedrockId; private int bedrockId;
} }
} }

View File

@@ -25,12 +25,12 @@
package org.geysermc.geyser.registry.loader; package org.geysermc.geyser.registry.loader;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map; import java.io.InputStreamReader;
import java.util.WeakHashMap;
/** /**
* An abstract registry loader for loading effects from a resource path. * An abstract registry loader for loading effects from a resource path.
@@ -38,21 +38,14 @@ import java.util.WeakHashMap;
* @param <T> the value * @param <T> the value
*/ */
public abstract class EffectRegistryLoader<T> implements RegistryLoader<String, T> { public abstract class EffectRegistryLoader<T> implements RegistryLoader<String, T> {
private static final Map<String, JsonNode> loadedFiles = new WeakHashMap<>();
public void loadFile(String input) { public JsonObject loadFile(String input) {
if (!loadedFiles.containsKey(input)) { try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input);
JsonNode effects; InputStreamReader reader = new InputStreamReader(stream)) {
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) { //noinspection deprecation
effects = GeyserImpl.JSON_MAPPER.readTree(stream); return new JsonParser().parse(reader).getAsJsonObject();
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError("Unable to load registrations for " + input, e); throw new AssertionError("Unable to load registrations for " + input, e);
} }
loadedFiles.put(input, effects);
}
}
public JsonNode get(String input) {
return loadedFiles.get(input);
} }
} }

View File

@@ -25,15 +25,15 @@
package org.geysermc.geyser.registry.loader; package org.geysermc.geyser.registry.loader;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonElement;
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.LevelEventType; import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.registry.type.ParticleMapping; 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.Locale;
import java.util.Map; import java.util.Map;
@@ -44,16 +44,13 @@ public class ParticleTypesRegistryLoader extends EffectRegistryLoader<Map<Partic
@Override @Override
public Map<ParticleType, ParticleMapping> load(String input) { public Map<ParticleType, ParticleMapping> load(String input) {
this.loadFile(input); JsonObject particlesJson = this.loadFile(input);
Iterator<Map.Entry<String, JsonNode>> particlesIterator = this.get(input).fields();
Map<ParticleType, ParticleMapping> particles = new Object2ObjectOpenHashMap<>(); Map<ParticleType, ParticleMapping> particles = new Object2ObjectOpenHashMap<>();
try { try {
while (particlesIterator.hasNext()) { for (Map.Entry<String, JsonElement> entry : particlesJson.entrySet()) {
Map.Entry<String, JsonNode> entry = particlesIterator.next();
String key = entry.getKey().toUpperCase(Locale.ROOT); String key = entry.getKey().toUpperCase(Locale.ROOT);
JsonNode bedrockId = entry.getValue().get("bedrockId"); JsonElement bedrockId = entry.getValue().getAsJsonObject().get("bedrockId");
JsonNode eventType = entry.getValue().get("eventType"); JsonElement eventType = entry.getValue().getAsJsonObject().get("eventType");
if (eventType == null && bedrockId == null) { if (eventType == null && bedrockId == null) {
GeyserImpl.getInstance().getLogger().debug("Skipping particle mapping " + key + " because no Bedrock equivalent exists."); GeyserImpl.getInstance().getLogger().debug("Skipping particle mapping " + key + " because no Bedrock equivalent exists.");
continue; continue;
@@ -63,16 +60,16 @@ public class ParticleTypesRegistryLoader extends EffectRegistryLoader<Map<Partic
if (eventType != null) { if (eventType != null) {
try { try {
// Check if we have a particle type mapping // Check if we have a particle type mapping
type = org.cloudburstmc.protocol.bedrock.data.ParticleType.valueOf(eventType.asText().toUpperCase(Locale.ROOT)); type = org.cloudburstmc.protocol.bedrock.data.ParticleType.valueOf(eventType.getAsString().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// No particle type; try level event // No particle type; try level event
type = LevelEvent.valueOf(eventType.asText().toUpperCase(Locale.ROOT)); type = LevelEvent.valueOf(eventType.getAsString().toUpperCase(Locale.ROOT));
} }
} }
particles.put(ParticleType.valueOf(key), new ParticleMapping( particles.put(ParticleType.valueOf(key), new ParticleMapping(
type, type,
bedrockId == null ? null : bedrockId.asText()) bedrockId == null ? null : bedrockId.getAsString())
); );
} }
} catch (Exception e) { } catch (Exception e) {

View File

@@ -124,20 +124,19 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks); GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
GeyserImpl.getInstance().eventBus().fire(event); GeyserImpl.getInstance().eventBus().fire(event);
GeyserDefineResourcePacksEventImpl defineEvent = new GeyserDefineResourcePacksEventImpl(packMap);
for (Path path : event.resourcePacks()) { for (Path path : event.resourcePacks()) {
try { try {
GeyserResourcePack pack = readPack(path).build(); defineEvent.register(readPack(path).build());
packMap.put(pack.uuid(), ResourcePackHolder.of(pack));
} catch (Exception e) { } catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path));
e.printStackTrace(); e.printStackTrace();
} }
} }
// Load all remote resource packs from the config before firing the new event // Load all remote resource packs from the config before firing the new event
// TODO configurate loadRemotePacks(defineEvent);
//packMap.putAll(loadRemotePacks());
GeyserDefineResourcePacksEventImpl defineEvent = new GeyserDefineResourcePacksEventImpl(packMap);
GeyserImpl.getInstance().eventBus().fire(defineEvent); GeyserImpl.getInstance().eventBus().fire(defineEvent);
// After loading the new resource packs: let's clean up the old url packs // After loading the new resource packs: let's clean up the old url packs
@@ -225,7 +224,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
} }
} }
private Map<UUID, ResourcePackHolder> loadRemotePacks() { private void loadRemotePacks(GeyserDefineResourcePacksEventImpl event) {
GeyserImpl instance = GeyserImpl.getInstance(); GeyserImpl instance = GeyserImpl.getInstance();
// Unable to make this a static variable, as the test would fail // Unable to make this a static variable, as the test would fail
final Path cachedDirectory = instance.getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs"); final Path cachedDirectory = instance.getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
@@ -235,19 +234,14 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
Files.createDirectories(cachedDirectory); Files.createDirectories(cachedDirectory);
} catch (IOException e) { } catch (IOException e) {
instance.getLogger().error("Could not create remote pack cache directory", e); instance.getLogger().error("Could not create remote pack cache directory", e);
return new Object2ObjectOpenHashMap<>(); return;
} }
} }
//List<String> remotePackUrls = instance.getConfig().getResourcePackUrls(); List<String> remotePackUrls = instance.config().advanced().resourcePackUrls();
List<String> remotePackUrls = List.of();
Map<UUID, ResourcePackHolder> packMap = new Object2ObjectOpenHashMap<>();
for (String url : remotePackUrls) { for (String url : remotePackUrls) {
try { try {
GeyserUrlPackCodec codec = new GeyserUrlPackCodec(url); event.register(new GeyserUrlPackCodec(url).create());
GeyserResourcePack pack = codec.create();
packMap.put(pack.uuid(), ResourcePackHolder.of(pack));
} catch (Throwable e) { } catch (Throwable e) {
instance.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url)); instance.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url));
instance.getLogger().error(e.getMessage()); instance.getLogger().error(e.getMessage());
@@ -256,8 +250,6 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
} }
} }
} }
return packMap;
} }
/** /**

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.registry.loader; package org.geysermc.geyser.registry.loader;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonElement;
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent; import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.cloudburstmc.protocol.bedrock.data.LevelEventType; import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
@@ -34,8 +34,8 @@ import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
import org.geysermc.geyser.translator.level.event.PlaySoundEventTranslator; import org.geysermc.geyser.translator.level.event.PlaySoundEventTranslator;
import org.geysermc.geyser.translator.level.event.SoundEventEventTranslator; import org.geysermc.geyser.translator.level.event.SoundEventEventTranslator;
import org.geysermc.geyser.translator.level.event.SoundLevelEventTranslator; import org.geysermc.geyser.translator.level.event.SoundLevelEventTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
/** /**
@@ -47,37 +47,36 @@ public class SoundEventsRegistryLoader extends EffectRegistryLoader<Map<LevelEve
public Map<LevelEvent, LevelEventTranslator> load(String input) { public Map<LevelEvent, LevelEventTranslator> load(String input) {
this.loadFile(input); this.loadFile(input);
Iterator<Map.Entry<String, JsonNode>> effectsIterator = this.get(input).fields(); JsonObject effectsJson = this.loadFile(input);
Map<LevelEvent, LevelEventTranslator> soundEffects = new Object2ObjectOpenHashMap<>(); Map<LevelEvent, LevelEventTranslator> soundEffects = new Object2ObjectOpenHashMap<>();
while (effectsIterator.hasNext()) { for (Map.Entry<String, JsonElement> entry : effectsJson.entrySet()) {
Map.Entry<String, JsonNode> entry = effectsIterator.next(); JsonObject node = entry.getValue().getAsJsonObject();
JsonNode node = entry.getValue();
try { try {
String type = node.get("type").asText(); String type = node.get("type").getAsString();
LevelEvent javaEffect = null; LevelEvent javaEffect = null;
LevelEventTranslator transformer = null; LevelEventTranslator transformer = null;
switch (type) { switch (type) {
case "soundLevel" -> { case "soundLevel" -> {
javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey()); 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()); LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").getAsString());
int data = node.has("data") ? node.get("data").intValue() : 0; int data = node.has("data") ? node.get("data").getAsInt() : 0;
transformer = new SoundLevelEventTranslator(levelEventType, data); transformer = new SoundLevelEventTranslator(levelEventType, data);
} }
case "soundEvent" -> { case "soundEvent" -> {
javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey()); 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()); 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").asText() : ""; String identifier = node.has("identifier") ? node.get("identifier").getAsString() : "";
int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1; int extraData = node.has("extraData") ? node.get("extraData").getAsInt() : -1;
transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData); transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData);
} }
case "playSound" -> { case "playSound" -> {
javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey()); javaEffect = org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
String name = node.get("name").asText(); String name = node.get("name").getAsString();
float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f; float volume = node.has("volume") ? node.get("volume").getAsFloat() : 1.0f;
boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue(); boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").getAsBoolean();
float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").floatValue() : 1.0f; float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").getAsFloat() : 1.0f;
float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").floatValue() : 0.0f; float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").getAsFloat() : 0.0f;
boolean relative = !node.has("relative") || node.get("relative").booleanValue(); boolean relative = !node.has("relative") || node.get("relative").getAsBoolean();
transformer = new PlaySoundEventTranslator(name, volume, pitchSub, pitchMul, pitchAdd, relative); transformer = new PlaySoundEventTranslator(name, volume, pitchSub, pitchMul, pitchAdd, relative);
} }
} }

View File

@@ -25,15 +25,16 @@
package org.geysermc.geyser.registry.loader; package org.geysermc.geyser.registry.loader;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonElement;
import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.registry.type.SoundMapping; import org.geysermc.geyser.registry.type.SoundMapping;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
/** /**
@@ -42,27 +43,27 @@ import java.util.Map;
public class SoundRegistryLoader implements RegistryLoader<String, Map<String, SoundMapping>> { public class SoundRegistryLoader implements RegistryLoader<String, Map<String, SoundMapping>> {
@Override @Override
public Map<String, SoundMapping> load(String input) { public Map<String, SoundMapping> load(String input) {
JsonNode soundsTree; JsonObject soundsJson;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input)) { try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(input);
soundsTree = GeyserImpl.JSON_MAPPER.readTree(stream); InputStreamReader isr = new InputStreamReader(stream)) {
//noinspection deprecation
soundsJson = new JsonParser().parse(isr).getAsJsonObject();
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError("Unable to load sound mappings", e); throw new AssertionError("Unable to load sound mappings", e);
} }
Map<String, SoundMapping> soundMappings = new HashMap<>(); Map<String, SoundMapping> soundMappings = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> soundsIterator = soundsTree.fields(); for (Map.Entry<String, JsonElement> entry : soundsJson.entrySet()) {
while (soundsIterator.hasNext()) { JsonObject brMap = entry.getValue().getAsJsonObject();
Map.Entry<String, JsonNode> next = soundsIterator.next(); String javaSound = entry.getKey();
JsonNode brMap = next.getValue();
String javaSound = next.getKey();
soundMappings.put(javaSound, new SoundMapping( soundMappings.put(javaSound, new SoundMapping(
javaSound, javaSound,
brMap.has("bedrock_mapping") && brMap.get("bedrock_mapping").isTextual() ? brMap.get("bedrock_mapping").asText() : null, brMap.has("bedrock_mapping") ? brMap.get("bedrock_mapping").getAsString() : null,
brMap.has("playsound_mapping") && brMap.get("playsound_mapping").isTextual() ? brMap.get("playsound_mapping").asText() : null, brMap.has("playsound_mapping") ? brMap.get("playsound_mapping").getAsString() : null,
brMap.has("extra_data") && brMap.get("extra_data").isInt() ? brMap.get("extra_data").asInt() : -1, brMap.has("extra_data") ? brMap.get("extra_data").getAsInt() : -1,
brMap.has("identifier") && brMap.get("identifier").isTextual() ? brMap.get("identifier").asText() : null, brMap.has("identifier") ? brMap.get("identifier").getAsString() : null,
brMap.has("level_event") && brMap.get("level_event").isBoolean() && brMap.get("level_event").asBoolean(), brMap.has("level_event") && brMap.get("level_event").getAsBoolean(),
brMap.has("pitch_adjust") && brMap.get("pitch_adjust").isNumber() ? brMap.get("pitch_adjust").floatValue() : 1.0f brMap.has("pitch_adjust") ? brMap.get("pitch_adjust").getAsFloat() : 1.0f
) )
); );
} }

View File

@@ -25,7 +25,8 @@
package org.geysermc.geyser.registry.mappings; 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.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.checkerframework.checker.nullness.qual.Nullable; 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;
import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1; import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -95,10 +97,10 @@ public class MappingsConfigReader {
} }
} }
public @Nullable JsonNode getMappingsRoot(Path file) { public @Nullable JsonObject getMappingsRoot(Path file) {
JsonNode mappingsRoot; JsonObject mappingsRoot;
try { try (FileReader reader = new FileReader(file.toFile())) {
mappingsRoot = GeyserImpl.JSON_MAPPER.readTree(file.toFile()); mappingsRoot = (JsonObject) new JsonParser().parse(reader);
} catch (IOException e) { } catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e); GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e);
return null; return null;
@@ -112,8 +114,8 @@ public class MappingsConfigReader {
return mappingsRoot; return mappingsRoot;
} }
public int getFormatVersion(JsonNode mappingsRoot, Path file) { public int getFormatVersion(JsonObject mappingsRoot, Path file) {
int formatVersion = mappingsRoot.get("format_version").asInt(); int formatVersion = mappingsRoot.get("format_version").getAsInt();
if (!this.mappingReaders.containsKey(formatVersion)) { if (!this.mappingReaders.containsKey(formatVersion)) {
GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion); GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion);
return -1; return -1;
@@ -122,7 +124,7 @@ public class MappingsConfigReader {
} }
public void readItemMappingsFromJson(Path file, BiConsumer<String, CustomItemData> consumer) { public void readItemMappingsFromJson(Path file, BiConsumer<String, CustomItemData> consumer) {
JsonNode mappingsRoot = getMappingsRoot(file); JsonObject mappingsRoot = getMappingsRoot(file);
if (mappingsRoot == null) { if (mappingsRoot == null) {
return; return;
@@ -138,7 +140,7 @@ public class MappingsConfigReader {
} }
public void readBlockMappingsFromJson(Path file, BiConsumer<String, CustomBlockMapping> consumer) { public void readBlockMappingsFromJson(Path file, BiConsumer<String, CustomBlockMapping> consumer) {
JsonNode mappingsRoot = getMappingsRoot(file); JsonObject mappingsRoot = getMappingsRoot(file);
if (mappingsRoot == null) { if (mappingsRoot == null) {
return; return;

View File

@@ -25,7 +25,7 @@
package org.geysermc.geyser.registry.mappings.versions; 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.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
@@ -36,14 +36,14 @@ import java.nio.file.Path;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
public abstract class MappingsReader { public abstract class MappingsReader {
public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer); public abstract void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomItemData> consumer);
public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer); public abstract void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer);
public abstract CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException; public abstract CustomItemData readItemMappingEntry(JsonObject node) throws InvalidCustomMappingsFileException;
public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonObject node) throws InvalidCustomMappingsFileException;
protected @Nullable CustomRenderOffsets fromJsonNode(JsonNode node) { protected @Nullable CustomRenderOffsets fromJsonObject(JsonObject node) {
if (node == null || !node.isObject()) { if (node == null) {
return null; return null;
} }
@@ -53,9 +53,8 @@ public abstract class MappingsReader {
); );
} }
protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonNode node, String hand) { protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonObject node, String hand) {
JsonNode tmpNode = node.get(hand); if (!(node.get(hand) instanceof JsonObject tmpNode)) {
if (tmpNode == null || !tmpNode.isObject()) {
return null; return null;
} }
@@ -65,9 +64,8 @@ public abstract class MappingsReader {
); );
} }
protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonNode node, String perspective) { protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonObject node, String perspective) {
JsonNode tmpNode = node.get(perspective); if (!(node.get(perspective) instanceof JsonObject tmpNode)) {
if (tmpNode == null || !tmpNode.isObject()) {
return null; return null;
} }
@@ -78,9 +76,8 @@ public abstract class MappingsReader {
); );
} }
protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) { protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonObject node, String offsetType) {
JsonNode tmpNode = node.get(offsetType); if (!(node.get(offsetType) instanceof JsonObject tmpNode)) {
if (tmpNode == null || !tmpNode.isObject()) {
return null; return null;
} }
@@ -89,9 +86,9 @@ public abstract class MappingsReader {
} }
return new CustomRenderOffsets.OffsetXYZ( return new CustomRenderOffsets.OffsetXYZ(
tmpNode.get("x").floatValue(), tmpNode.get("x").getAsFloat(),
tmpNode.get("y").floatValue(), tmpNode.get("y").getAsFloat(),
tmpNode.get("z").floatValue() tmpNode.get("z").getAsFloat()
); );
} }
} }

View File

@@ -25,8 +25,10 @@
package org.geysermc.geyser.registry.mappings.versions; package org.geysermc.geyser.registry.mappings.versions;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonArray;
import com.fasterxml.jackson.databind.node.ArrayNode; 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.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.checkerframework.checker.nullness.qual.Nullable; 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.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation; import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
import org.geysermc.geyser.api.block.custom.CustomBlockState; 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.BlockFilterType;
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face; 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.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.CreativeCategory;
@@ -57,7 +64,13 @@ import org.geysermc.geyser.util.MathUtils;
import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.geyser.util.MinecraftKey;
import java.nio.file.Path; 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.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -68,7 +81,7 @@ import java.util.stream.Collectors;
*/ */
public class MappingsReader_v1 extends MappingsReader { public class MappingsReader_v1 extends MappingsReader {
@Override @Override
public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) { public void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
this.readItemMappingsV1(file, mappingsRoot, consumer); this.readItemMappingsV1(file, mappingsRoot, consumer);
} }
@@ -76,24 +89,24 @@ public class MappingsReader_v1 extends MappingsReader {
* Read item block from a JSON node * Read item block from a JSON node
* *
* @param file The path to the file * @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 * @param consumer The consumer to accept the mappings
* @see #readBlockMappingsV1(Path, JsonNode, BiConsumer) * @see #readBlockMappingsV1(Path, JsonObject, BiConsumer)
*/ */
@Override @Override
public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) { public void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
this.readBlockMappingsV1(file, mappingsRoot, consumer); this.readBlockMappingsV1(file, mappingsRoot, consumer);
} }
public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) { public void readItemMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
JsonNode itemsNode = mappingsRoot.get("items"); JsonObject itemsNode = mappingsRoot.getAsJsonObject("items");
if (itemsNode != null && itemsNode.isObject()) { if (itemsNode != null) {
itemsNode.fields().forEachRemaining(entry -> { itemsNode.entrySet().forEach(entry -> {
if (entry.getValue().isArray()) { if (entry.getValue() instanceof JsonArray array) {
entry.getValue().forEach(data -> { array.forEach(data -> {
try { try {
CustomItemData customItemData = this.readItemMappingEntry(data); CustomItemData customItemData = this.readItemMappingEntry((JsonObject) data);
consumer.accept(entry.getKey(), customItemData); consumer.accept(entry.getKey(), customItemData);
} catch (InvalidCustomMappingsFileException e) { } catch (InvalidCustomMappingsFileException e) {
GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), 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 * Read block mappings from a JSON node
* *
* @param file The path to the file * @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 * @param consumer The consumer to accept the mappings
* @see #readBlockMappings(Path, JsonNode, BiConsumer) * @see #readBlockMappings(Path, JsonObject, BiConsumer)
*/ */
public void readBlockMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) { public void readBlockMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
JsonNode blocksNode = mappingsRoot.get("blocks"); if (mappingsRoot.get("blocks") instanceof JsonObject blocksNode) {
blocksNode.entrySet().forEach(entry -> {
if (blocksNode != null && blocksNode.isObject()) { if (entry.getValue() instanceof JsonObject jsonObject) {
blocksNode.fields().forEachRemaining(entry -> {
if (entry.getValue().isObject()) {
try { try {
String identifier = MinecraftKey.key(entry.getKey()).asString(); String identifier = MinecraftKey.key(entry.getKey()).asString();
CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue()); CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, jsonObject);
consumer.accept(identifier, customBlockMapping); consumer.accept(identifier, customBlockMapping);
} catch (Exception e) { } catch (Exception e) {
GeyserImpl.getInstance().getLogger().error("Error in registering blocks for custom mapping file: " + file.toString()); 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(); CustomItemOptions.Builder customItemOptions = CustomItemOptions.builder();
JsonNode customModelData = node.get("custom_model_data"); JsonElement customModelData = node.get("custom_model_data");
if (customModelData != null && customModelData.isInt()) { if (customModelData != null && customModelData.isJsonPrimitive()) {
customItemOptions.customModelData(customModelData.asInt()); customItemOptions.customModelData(customModelData.getAsInt());
} }
JsonNode damagePredicate = node.get("damage_predicate"); JsonElement damagePredicate = node.get("damage_predicate");
if (damagePredicate != null && damagePredicate.isInt()) { if (damagePredicate != null && damagePredicate.isJsonPrimitive()) {
customItemOptions.damagePredicate(damagePredicate.asInt()); customItemOptions.damagePredicate(damagePredicate.getAsInt());
} }
JsonNode unbreakable = node.get("unbreakable"); JsonElement unbreakable = node.get("unbreakable");
if (unbreakable != null && unbreakable.isBoolean()) { if (unbreakable != null && unbreakable.isJsonPrimitive()) {
customItemOptions.unbreakable(unbreakable.asBoolean()); customItemOptions.unbreakable(unbreakable.getAsBoolean());
} }
JsonNode defaultItem = node.get("default"); JsonElement defaultItem = node.get("default");
if (defaultItem != null && defaultItem.isBoolean()) { if (defaultItem != null && defaultItem.isJsonPrimitive()) {
customItemOptions.defaultItem(defaultItem.asBoolean()); customItemOptions.defaultItem(defaultItem.getAsBoolean());
} }
return customItemOptions.build(); return customItemOptions.build();
} }
@Override @Override
public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { public CustomItemData readItemMappingEntry(JsonObject node) throws InvalidCustomMappingsFileException {
if (node == null || !node.isObject()) { if (node == null) {
throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); throw new InvalidCustomMappingsFileException("Invalid item mappings entry");
} }
JsonNode name = node.get("name"); JsonElement name = node.get("name");
if (name == null || !name.isTextual() || name.asText().isEmpty()) { if (name == null || !name.isJsonPrimitive() || name.getAsString().isEmpty()) {
throw new InvalidCustomMappingsFileException("An item entry has no name"); throw new InvalidCustomMappingsFileException("An item entry has no name");
} }
CustomItemData.Builder customItemData = CustomItemData.builder() CustomItemData.Builder customItemData = CustomItemData.builder()
.name(name.asText()) .name(name.getAsString())
.customItemOptions(this.readItemCustomItemOptions(node)); .customItemOptions(this.readItemCustomItemOptions(node));
//The next entries are optional //The next entries are optional
if (node.has("display_name")) { if (node.has("display_name")) {
customItemData.displayName(node.get("display_name").asText()); customItemData.displayName(node.get("display_name").getAsString());
} }
if (node.has("icon")) { if (node.has("icon")) {
customItemData.icon(node.get("icon").asText()); customItemData.icon(node.get("icon").getAsString());
} }
if (node.has("creative_category")) { if (node.has("creative_category")) {
customItemData.creativeCategory(node.get("creative_category").asInt()); customItemData.creativeCategory(node.get("creative_category").getAsInt());
} }
if (node.has("creative_group")) { if (node.has("creative_group")) {
customItemData.creativeGroup(node.get("creative_group").asText()); customItemData.creativeGroup(node.get("creative_group").getAsString());
} }
if (node.has("allow_offhand")) { if (node.has("allow_offhand")) {
customItemData.allowOffhand(node.get("allow_offhand").asBoolean()); customItemData.allowOffhand(node.get("allow_offhand").getAsBoolean());
} }
if (node.has("display_handheld")) { if (node.has("display_handheld")) {
customItemData.displayHandheld(node.get("display_handheld").asBoolean()); customItemData.displayHandheld(node.get("display_handheld").getAsBoolean());
} }
if (node.has("texture_size")) { if (node.has("texture_size")) {
customItemData.textureSize(node.get("texture_size").asInt()); customItemData.textureSize(node.get("texture_size").getAsInt());
} }
if (node.has("render_offsets")) { if (node.has("render_offsets")) {
JsonNode tmpNode = node.get("render_offsets"); JsonObject tmpNode = node.getAsJsonObject("render_offsets");
customItemData.renderOffsets(fromJsonNode(tmpNode)); customItemData.renderOffsets(fromJsonObject(tmpNode));
} }
if (node.get("tags") instanceof ArrayNode tags) { if (node.get("tags") instanceof JsonArray tags) {
Set<String> tagsSet = new ObjectOpenHashSet<>(); Set<String> tagsSet = new ObjectOpenHashSet<>();
tags.forEach(tag -> tagsSet.add(tag.asText())); tags.forEach(tag -> tagsSet.add(tag.getAsString()));
customItemData.tags(tagsSet); 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 * Read a block mapping entry from a JSON node and Java identifier
* *
* @param identifier The Java identifier of the block * @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} * @return The {@link CustomBlockMapping} record to be read by {@link org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator}
* @throws InvalidCustomMappingsFileException If the JSON node is invalid * @throws InvalidCustomMappingsFileException If the JSON node is invalid
*/ */
@Override @Override
public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { public CustomBlockMapping readBlockMappingEntry(String identifier, JsonObject node) throws InvalidCustomMappingsFileException {
if (node == null || !node.isObject()) { if (node == null) {
throw new InvalidCustomMappingsFileException("Invalid block mappings entry:" + node); 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()) { if (name == null || name.isEmpty()) {
throw new InvalidCustomMappingsFileException("A block entry has no name"); 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; CreativeCategory creativeCategory = CreativeCategory.NONE;
if (node.has("creative_category")) { if (node.has("creative_category")) {
String categoryName = node.get("creative_category").asText(); String categoryName = node.get("creative_category").getAsString();
try { try {
creativeCategory = CreativeCategory.valueOf(categoryName.toUpperCase()); creativeCategory = CreativeCategory.valueOf(categoryName.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -249,11 +260,11 @@ public class MappingsReader_v1 extends MappingsReader {
String creativeGroup = ""; String creativeGroup = "";
if (node.has("creative_group")) { 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 // 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 // Create the data for the overall block
CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder() CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder()
@@ -273,12 +284,9 @@ public class MappingsReader_v1 extends MappingsReader {
Map<String, CustomBlockComponentsMapping> componentsMap = new LinkedHashMap<>(); Map<String, CustomBlockComponentsMapping> componentsMap = new LinkedHashMap<>();
JsonNode stateOverrides = node.get("state_overrides"); if (node.get("state_overrides") instanceof JsonObject stateOverrides) {
if (stateOverrides != null && stateOverrides.isObject()) {
// Load components for specific Java block states // Load components for specific Java block states
Iterator<Map.Entry<String, JsonNode>> fields = stateOverrides.fields(); for (Map.Entry<String, JsonElement> overrideEntry : stateOverrides.entrySet()) {
while (fields.hasNext()) {
Map.Entry<String, JsonNode> overrideEntry = fields.next();
String state = identifier + "[" + overrideEntry.getKey() + "]"; String state = identifier + "[" + overrideEntry.getKey() + "]";
if (!BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().containsKey(state)) { if (!BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().containsKey(state)) {
throw new InvalidCustomMappingsFileException("Unknown Java block state: " + state + " for state_overrides."); 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 * 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 stateKey the Java block state identifier
* @param name the name of the custom block * @param name the name of the custom block
* @return the {@link CustomBlockComponents} object * @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 // 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); int id = BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1);
BoxComponent boxComponent = createBoxComponent(id); BoxComponent boxComponent = createBoxComponent(id);
@@ -372,7 +380,7 @@ public class MappingsReader_v1 extends MappingsReader {
.collisionBox(boxComponent) .collisionBox(boxComponent)
.selectionBox(boxComponent); .selectionBox(boxComponent);
if (node == null) { if (!(element instanceof JsonObject node)) {
// No other components were defined // No other components were defined
return new CustomBlockComponentsMapping(builder.build(), extendedBoxComponent); 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 // We set this to max value by default so that we may dictate the correct destroy time ourselves
float destructibleByMining = Float.MAX_VALUE; float destructibleByMining = Float.MAX_VALUE;
if (node.has("destructible_by_mining")) { if (node.has("destructible_by_mining")) {
destructibleByMining = node.get("destructible_by_mining").floatValue(); destructibleByMining = node.get("destructible_by_mining").getAsFloat();
} }
builder.destructibleByMining(destructibleByMining); builder.destructibleByMining(destructibleByMining);
if (node.has("geometry")) { if (node.has("geometry")) {
if (node.get("geometry").isTextual()) { if (node.get("geometry").isJsonPrimitive()) {
builder.geometry(new GeyserGeometryComponent.Builder() builder.geometry(new GeyserGeometryComponent.Builder()
.identifier(node.get("geometry").asText()) .identifier(node.get("geometry").getAsString())
.build()); .build());
} else { } else {
JsonNode geometry = node.get("geometry"); JsonObject geometry = node.getAsJsonObject("geometry");
GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder(); GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder();
if (geometry.has("identifier")) { if (geometry.has("identifier")) {
geometryBuilder.identifier(geometry.get("identifier").asText()); geometryBuilder.identifier(geometry.get("identifier").getAsString());
} }
if (geometry.has("bone_visibility")) { if (geometry.has("bone_visibility")) {
JsonNode boneVisibility = geometry.get("bone_visibility"); if (geometry.get("bone_visibility") instanceof JsonObject boneVisibility) {
if (boneVisibility.isObject()) {
Map<String, String> boneVisibilityMap = new Object2ObjectOpenHashMap<>(); Map<String, String> boneVisibilityMap = new Object2ObjectOpenHashMap<>();
boneVisibility.fields().forEachRemaining(entry -> { boneVisibility.entrySet().forEach(entry -> {
String key = entry.getKey(); 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); boneVisibilityMap.put(key, value);
}); });
geometryBuilder.boneVisibility(boneVisibilityMap); geometryBuilder.boneVisibility(boneVisibilityMap);
@@ -427,30 +435,30 @@ public class MappingsReader_v1 extends MappingsReader {
String displayName = name; String displayName = name;
if (node.has("display_name")) { if (node.has("display_name")) {
displayName = node.get("display_name").asText(); displayName = node.get("display_name").getAsString();
} }
builder.displayName(displayName); builder.displayName(displayName);
if (node.has("friction")) { if (node.has("friction")) {
builder.friction(node.get("friction").floatValue()); builder.friction(node.get("friction").getAsFloat());
} }
if (node.has("light_emission")) { if (node.has("light_emission")) {
builder.lightEmission(node.get("light_emission").asInt()); builder.lightEmission(node.get("light_emission").getAsInt());
} }
if (node.has("light_dampening")) { if (node.has("light_dampening")) {
builder.lightDampening(node.get("light_dampening").asInt()); builder.lightDampening(node.get("light_dampening").getAsInt());
} }
boolean placeAir = true; boolean placeAir = true;
if (node.has("place_air")) { if (node.has("place_air")) {
placeAir = node.get("place_air").asBoolean(); placeAir = node.get("place_air").getAsBoolean();
} }
builder.placeAir(placeAir); builder.placeAir(placeAir);
if (node.has("transformation")) { if (node.has("transformation")) {
JsonNode transformation = node.get("transformation"); JsonObject transformation = node.getAsJsonObject("transformation");
int rotationX = 0; int rotationX = 0;
int rotationY = 0; int rotationY = 0;
@@ -463,22 +471,22 @@ public class MappingsReader_v1 extends MappingsReader {
float transformZ = 0; float transformZ = 0;
if (transformation.has("rotation")) { if (transformation.has("rotation")) {
JsonNode rotation = transformation.get("rotation"); JsonArray rotation = transformation.getAsJsonArray("rotation");
rotationX = rotation.get(0).asInt(); rotationX = rotation.get(0).getAsInt();
rotationY = rotation.get(1).asInt(); rotationY = rotation.get(1).getAsInt();
rotationZ = rotation.get(2).asInt(); rotationZ = rotation.get(2).getAsInt();
} }
if (transformation.has("scale")) { if (transformation.has("scale")) {
JsonNode scale = transformation.get("scale"); JsonArray scale = transformation.getAsJsonArray("scale");
scaleX = scale.get(0).floatValue(); scaleX = scale.get(0).getAsFloat();
scaleY = scale.get(1).floatValue(); scaleY = scale.get(1).getAsFloat();
scaleZ = scale.get(2).floatValue(); scaleZ = scale.get(2).getAsFloat();
} }
if (transformation.has("translation")) { if (transformation.has("translation")) {
JsonNode translation = transformation.get("translation"); JsonArray translation = transformation.getAsJsonArray("translation");
transformX = translation.get(0).floatValue(); transformX = translation.get(0).getAsFloat();
transformY = translation.get(1).floatValue(); transformY = translation.get(1).getAsFloat();
transformZ = translation.get(2).floatValue(); transformZ = translation.get(2).getAsFloat();
} }
builder.transformation(new TransformationComponent(rotationX, rotationY, rotationZ, scaleX, scaleY, scaleZ, transformX, transformY, transformZ)); 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")) { if (node.has("material_instances")) {
JsonNode materialInstances = node.get("material_instances"); if (node.get("material_instances") instanceof JsonObject materialInstances) {
if (materialInstances.isObject()) { materialInstances.entrySet().forEach(entry -> {
materialInstances.fields().forEachRemaining(entry -> {
String key = entry.getKey(); String key = entry.getKey();
JsonNode value = entry.getValue(); if (entry.getValue() instanceof JsonObject value) {
if (value.isObject()) {
MaterialInstance materialInstance = createMaterialInstanceComponent(value); MaterialInstance materialInstance = createMaterialInstanceComponent(value);
builder.materialInstance(key, materialInstance); builder.materialInstance(key, materialInstance);
} }
@@ -503,27 +509,21 @@ public class MappingsReader_v1 extends MappingsReader {
} }
} }
if (node.has("placement_filter")) { if (node.get("placement_filter") instanceof JsonObject placementFilter) {
JsonNode placementFilter = node.get("placement_filter"); if (placementFilter.get("conditions") instanceof JsonArray conditions) {
if (placementFilter.isObject()) {
if (placementFilter.has("conditions")) {
JsonNode conditions = placementFilter.get("conditions");
if (conditions.isArray()) {
List<PlacementConditions> filter = createPlacementFilterComponent(conditions); List<PlacementConditions> filter = createPlacementFilterComponent(conditions);
builder.placementFilter(filter); builder.placementFilter(filter);
} }
} }
}
}
// Tags can be applied so that blocks will match return true when queried for the tag // Tags can be applied so that blocks will match return true when queried for the tag
// Potentially useful for resource pack creators // Potentially useful for resource pack creators
// Ideally we could programmatically extract the tags here https://wiki.bedrock.dev/blocks/block-tags.html // 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 // 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 // However, its worth noting that vanilla tools do not currently honor these tags anyway
if (node.get("tags") instanceof ArrayNode tags) { if (node.get("tags") instanceof JsonArray tags) {
Set<String> tagsSet = new ObjectOpenHashSet<>(); Set<String> tagsSet = new ObjectOpenHashSet<>();
tags.forEach(tag -> tagsSet.add(tag.asText())); tags.forEach(tag -> tagsSet.add(tag.getAsString()));
builder.tags(tagsSet); builder.tags(tagsSet);
} }
@@ -613,21 +613,21 @@ public class MappingsReader_v1 extends MappingsReader {
/** /**
* Creates a {@link BoxComponent} from a JSON Node * Creates a {@link BoxComponent} from a JSON Node
* *
* @param node the JSON node * @param element the JSON node
* @return the {@link BoxComponent} * @return the {@link BoxComponent}
*/ */
private @Nullable BoxComponent createBoxComponent(JsonNode node) { private @Nullable BoxComponent createBoxComponent(JsonElement element) {
if (node != null && node.isObject()) { if (element instanceof JsonObject node) {
if (node.has("origin") && node.has("size")) { if (node.has("origin") && node.has("size")) {
JsonNode origin = node.get("origin"); JsonArray origin = node.getAsJsonArray("origin");
float originX = origin.get(0).floatValue(); float originX = origin.get(0).getAsFloat();
float originY = origin.get(1).floatValue(); float originY = origin.get(1).getAsFloat();
float originZ = origin.get(2).floatValue(); float originZ = origin.get(2).getAsFloat();
JsonNode size = node.get("size"); JsonArray size = node.getAsJsonArray("size");
float sizeX = size.get(0).floatValue(); float sizeX = size.get(0).getAsFloat();
float sizeY = size.get(1).floatValue(); float sizeY = size.get(1).getAsFloat();
float sizeZ = size.get(2).floatValue(); float sizeZ = size.get(2).getAsFloat();
return new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ); 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 * @param node the material instance node
* @return the {@link MaterialInstance} * @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 // Set default values, and use what the user provides if they have provided something
String texture = null; String texture = null;
if (node.has("texture")) { if (node.has("texture")) {
texture = node.get("texture").asText(); texture = node.get("texture").getAsString();
} }
String renderMethod = "opaque"; String renderMethod = "opaque";
if (node.has("render_method")) { if (node.has("render_method")) {
renderMethod = node.get("render_method").asText(); renderMethod = node.get("render_method").getAsString();
} }
boolean faceDimming = true; boolean faceDimming = true;
if (node.has("face_dimming")) { if (node.has("face_dimming")) {
faceDimming = node.get("face_dimming").asBoolean(); faceDimming = node.get("face_dimming").getAsBoolean();
} }
boolean ambientOcclusion = true; boolean ambientOcclusion = true;
if (node.has("ambient_occlusion")) { if (node.has("ambient_occlusion")) {
ambientOcclusion = node.get("ambient_occlusion").asBoolean(); ambientOcclusion = node.get("ambient_occlusion").getAsBoolean();
} }
return new GeyserMaterialInstance.Builder() return new GeyserMaterialInstance.Builder()
@@ -678,32 +678,33 @@ public class MappingsReader_v1 extends MappingsReader {
* @param node the conditions node * @param node the conditions node
* @return the list of {@link PlacementConditions} * @return the list of {@link PlacementConditions}
*/ */
private List<PlacementConditions> createPlacementFilterComponent(JsonNode node) { private List<PlacementConditions> createPlacementFilterComponent(JsonArray node) {
List<PlacementConditions> conditions = new ArrayList<>(); List<PlacementConditions> conditions = new ArrayList<>();
// The structure of the placement filter component is the most complex of the current components // 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 // Each condition effectively separated into two arrays: one of allowed faces, and one of blocks/block Molang queries
node.forEach(condition -> { node.forEach(json -> {
if (!(json instanceof JsonObject condition)) {
return;
}
Set<Face> faces = EnumSet.noneOf(Face.class); Set<Face> faces = EnumSet.noneOf(Face.class);
if (condition.has("allowed_faces")) { if (condition.has("allowed_faces")) {
JsonNode allowedFaces = condition.get("allowed_faces"); if (condition.get("allowed_faces") instanceof JsonArray allowedFaces) {
if (allowedFaces.isArray()) { allowedFaces.forEach(face -> faces.add(Face.valueOf(face.getAsString().toUpperCase())));
allowedFaces.forEach(face -> faces.add(Face.valueOf(face.asText().toUpperCase())));
} }
} }
LinkedHashMap<String, BlockFilterType> blockFilters = new LinkedHashMap<>(); LinkedHashMap<String, BlockFilterType> blockFilters = new LinkedHashMap<>();
if (condition.has("block_filter")) { if (condition.has("block_filter")) {
JsonNode blockFilter = condition.get("block_filter"); if (condition.get("block_filter") instanceof JsonArray blockFilter) {
if (blockFilter.isArray()) {
blockFilter.forEach(filter -> { blockFilter.forEach(filter -> {
if (filter.isObject()) { if (filter instanceof JsonObject jsonObject) {
if (filter.has("tags")) { if (jsonObject.has("tags")) {
JsonNode tags = filter.get("tags"); JsonElement tags = jsonObject.get("tags");
blockFilters.put(tags.asText(), BlockFilterType.TAG); blockFilters.put(tags.getAsString(), BlockFilterType.TAG);
} }
} else if (filter.isTextual()) { } else if (filter instanceof JsonPrimitive primitive && primitive.isString()) {
blockFilters.put(filter.asText(), BlockFilterType.BLOCK); blockFilters.put(filter.getAsString(), BlockFilterType.BLOCK);
} }
}); });
} }

View File

@@ -25,11 +25,12 @@
package org.geysermc.geyser.registry.populator; 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.ImmutableMap;
import com.google.common.collect.Interner; import com.google.common.collect.Interner;
import com.google.common.collect.Interners; 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.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; 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.populator.conversion.Conversion844_827;
import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock; import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
import org.geysermc.geyser.util.JsonUtils;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.InputStream; import java.io.InputStream;
@@ -441,21 +443,21 @@ public final class BlockRegistryPopulator {
BLOCKS_NBT = blocksNbt; BLOCKS_NBT = blocksNbt;
JAVA_BLOCKS_SIZE = blocksNbt.size(); JAVA_BLOCKS_SIZE = blocksNbt.size();
JsonNode blockInteractionsJson; JsonObject blockInteractionsJson;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/interactions.json")) { try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/interactions.json")) {
blockInteractionsJson = GeyserImpl.JSON_MAPPER.readTree(stream); blockInteractionsJson = JsonUtils.fromJson(stream);
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError("Unable to load Java block interaction mappings", e); throw new AssertionError("Unable to load Java block interaction mappings", e);
} }
BlockRegistries.INTERACTIVE.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("always_consumes"))); BlockRegistries.INTERACTIVE.set(toBlockStateSet(blockInteractionsJson.getAsJsonArray("always_consumes")));
BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("requires_may_build"))); 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()); BitSet blockStateSet = new BitSet(node.size());
for (JsonNode javaIdentifier : node) { for (JsonElement javaIdentifier : node) {
blockStateSet.set(BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.textValue())); blockStateSet.set(BlockRegistries.JAVA_BLOCK_STATE_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.getAsString()));
} }
return blockStateSet; return blockStateSet;
} }

View File

@@ -25,7 +25,9 @@
package org.geysermc.geyser.registry.populator; 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.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder; 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.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock; import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
import org.geysermc.geyser.util.JsonUtils;
import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.GeyserMappingItem;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -62,20 +65,21 @@ public class CreativeItemRegistryPopulator {
static List<CreativeItemGroup> readCreativeItemGroups(ItemRegistryPopulator.PaletteVersion palette, List<CreativeItemData> creativeItemData) { static List<CreativeItemGroup> readCreativeItemGroups(ItemRegistryPopulator.PaletteVersion palette, List<CreativeItemData> creativeItemData) {
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
JsonNode creativeItemEntries; JsonArray creativeItemEntries;
try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/creative_items.%s.json", palette.version()))) { 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) { } catch (Exception e) {
throw new AssertionError("Unable to load creative item groups", e); throw new AssertionError("Unable to load creative item groups", e);
} }
List<CreativeItemGroup> creativeItemGroups = new ArrayList<>(); List<CreativeItemGroup> creativeItemGroups = new ArrayList<>();
for (JsonNode creativeItemEntry : creativeItemEntries) { for (JsonElement creativeItemEntry : creativeItemEntries) {
CreativeItemCategory category = CreativeItemCategory.valueOf(creativeItemEntry.get("category").asText().toUpperCase(Locale.ROOT)); JsonObject creativeItemEntryObject = creativeItemEntry.getAsJsonObject();
String name = creativeItemEntry.get("name").asText(); CreativeItemCategory category = CreativeItemCategory.valueOf(creativeItemEntryObject.get("category").getAsString().toUpperCase(Locale.ROOT));
String name = creativeItemEntryObject.get("name").getAsString();
JsonNode icon = creativeItemEntry.get("icon"); JsonElement icon = creativeItemEntryObject.get("icon");
String identifier = icon.get("id").asText(); String identifier = icon.getAsJsonObject().get("id").getAsString();
ItemData itemData; ItemData itemData;
if (identifier.equals("minecraft:air")) { if (identifier.equals("minecraft:air")) {
@@ -98,32 +102,33 @@ public class CreativeItemRegistryPopulator {
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
// Load creative items // Load creative items
JsonNode creativeItemEntries; JsonArray creativeItemEntries;
try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/creative_items.%s.json", palette.version()))) { 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) { } catch (Exception e) {
throw new AssertionError("Unable to load creative items", e); throw new AssertionError("Unable to load creative items", e);
} }
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion()); BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
for (JsonNode itemNode : creativeItemEntries) { for (JsonElement itemNode : creativeItemEntries) {
ItemData.Builder itemBuilder = createItemData(itemNode, items, blockMappings, definitions); ItemData.Builder itemBuilder = createItemData((JsonObject) itemNode, items, blockMappings, definitions);
if (itemBuilder == null) { if (itemBuilder == null) {
continue; 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); itemConsumer.accept(itemBuilder, groupId);
} }
} }
private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, Map<String, GeyserMappingItem> items, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) { private static ItemData.@Nullable Builder createItemData(JsonObject itemNode, Map<String, GeyserMappingItem> items, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
int count = 1; int count = 1;
int damage = 0; int damage = 0;
NbtMap tag = null; NbtMap tag = null;
String identifier = itemNode.get("id").textValue(); String identifier = itemNode.get("id").getAsString();
for (BiPredicate<String, Integer> predicate : JAVA_ONLY_ITEM_FILTER) { for (BiPredicate<String, Integer> predicate : JAVA_ONLY_ITEM_FILTER) {
if (predicate.test(identifier, damage)) { if (predicate.test(identifier, damage)) {
return null; return null;
@@ -150,20 +155,20 @@ public class CreativeItemRegistryPopulator {
// } // }
} }
JsonNode damageNode = itemNode.get("damage"); JsonElement damageNode = itemNode.get("damage");
if (damageNode != null) { if (damageNode != null) {
damage = damageNode.asInt(); damage = damageNode.getAsInt();
} }
JsonNode countNode = itemNode.get("count"); JsonElement countNode = itemNode.get("count");
if (countNode != null) { if (countNode != null) {
count = countNode.asInt(); count = countNode.getAsInt();
} }
GeyserBedrockBlock blockDefinition = null; GeyserBedrockBlock blockDefinition = null;
JsonNode blockStateNode; JsonElement blockStateNode;
if ((blockStateNode = itemNode.get("block_state_b64")) != null) { 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); ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try { try {
NbtMap stateTag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); 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) { if (nbtNode != null) {
byte[] bytes = Base64.getDecoder().decode(nbtNode.asText()); byte[] bytes = Base64.getDecoder().decode(nbtNode.getAsString());
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try { try {
tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();

View File

@@ -104,7 +104,7 @@ public class CustomBlockRegistryPopulator {
* @param stage the stage to populate * @param stage the stage to populate
*/ */
public static void populate(Stage stage) { public static void populate(Stage stage) {
if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { if (!GeyserImpl.getInstance().config().gameplay().enableCustomContent()) {
return; return;
} }

View File

@@ -59,7 +59,7 @@ public class CustomSkullRegistryPopulator {
SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap()); BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { if (!GeyserImpl.getInstance().config().gameplay().enableCustomContent()) {
return; return;
} }

View File

@@ -25,9 +25,9 @@
package org.geysermc.geyser.registry.populator; package org.geysermc.geyser.registry.populator;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder; import com.google.common.collect.MultimapBuilder;
import com.google.gson.reflect.TypeToken;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 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.ItemMappings;
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
import org.geysermc.geyser.registry.type.PaletteItem; import org.geysermc.geyser.registry.type.PaletteItem;
import org.geysermc.geyser.util.JsonUtils;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -201,17 +203,17 @@ public class ItemRegistryPopulator {
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
TypeReference<Map<String, GeyserMappingItem>> mappingItemsType = new TypeReference<>() { }; Type mappingItemsType = new TypeToken<Map<String, GeyserMappingItem>>() { }.getType();
Map<String, GeyserMappingItem> items; Map<String, GeyserMappingItem> items;
try (InputStream stream = bootstrap.getResourceOrThrow("mappings/items.json")) { try (InputStream stream = bootstrap.getResourceOrThrow("mappings/items.json")) {
// Load item mappings from Java Edition to Bedrock Edition // Load item mappings from Java Edition to Bedrock Edition
items = GeyserImpl.JSON_MAPPER.readValue(stream, mappingItemsType); items = JsonUtils.fromJson(stream, mappingItemsType);
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError("Unable to load Java runtime item IDs", 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 // 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 // (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 */ /* Load item palette */
for (PaletteVersion palette : paletteVersions) { for (PaletteVersion palette : paletteVersions) {
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {}; Type paletteEntriesType = new TypeToken<List<PaletteItem>>() { }.getType();
List<PaletteItem> itemEntries; List<PaletteItem> itemEntries;
try (InputStream stream = bootstrap.getResourceOrThrow(String.format("bedrock/runtime_item_states.%s.json", palette.version()))) { 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) { } catch (Exception e) {
throw new AssertionError("Unable to load Bedrock runtime item IDs", e); throw new AssertionError("Unable to load Bedrock runtime item IDs", e);
} }

View File

@@ -25,7 +25,7 @@
package org.geysermc.geyser.registry.type; package org.geysermc.geyser.registry.type;
import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
@@ -43,13 +43,13 @@ import lombok.With;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class GeyserMappingItem { public class GeyserMappingItem {
@JsonProperty("bedrock_identifier") String bedrockIdentifier; @SerializedName("bedrock_identifier") String bedrockIdentifier;
@JsonProperty("bedrock_data") int bedrockData; @SerializedName("bedrock_data") int bedrockData;
Integer firstBlockRuntimeId; Integer firstBlockRuntimeId;
Integer lastBlockRuntimeId; Integer lastBlockRuntimeId;
@JsonProperty("tool_type") String toolType; @SerializedName("tool_type") String toolType;
@JsonProperty("armor_type") String armorType; @SerializedName("armor_type") String armorType;
@JsonProperty("protection_value") int protectionValue; @SerializedName("protection_value") int protectionValue;
@JsonProperty("is_edible") boolean edible = false; @SerializedName("is_edible") boolean edible = false;
@JsonProperty("is_entity_placer") boolean entityPlacer = false; @SerializedName("is_entity_placer") boolean entityPlacer = false;
} }

View File

@@ -80,7 +80,9 @@ import static org.geysermc.geyser.scoreboard.UpdateType.REMOVE;
*/ */
public final class Scoreboard { public final class Scoreboard {
private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true")); 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 GeyserSession session;
private final GeyserLogger logger; private final GeyserLogger logger;

View File

@@ -28,7 +28,7 @@ package org.geysermc.geyser.scoreboard;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.geysermc.geyser.GeyserImpl; 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.GeyserSession;
import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.session.cache.WorldCache;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
@@ -46,9 +46,9 @@ public final class ScoreboardUpdater extends Thread {
private static final boolean DEBUG_ENABLED; private static final boolean DEBUG_ENABLED;
static { static {
GeyserConfiguration config = GeyserImpl.getInstance().getConfig(); GeyserConfig config = GeyserImpl.getInstance().config();
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.advanced().scoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
DEBUG_ENABLED = config.isDebugMode(); DEBUG_ENABLED = config.debugMode();
} }
private final GeyserImpl geyser = GeyserImpl.getInstance(); private final GeyserImpl geyser = GeyserImpl.getInstance();

View File

@@ -25,7 +25,6 @@
package org.geysermc.geyser.session; package org.geysermc.geyser.session;
import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.EventLoop; 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.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.configuration.GeyserConfig;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.GeyserEntityData; import org.geysermc.geyser.entity.GeyserEntityData;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
@@ -256,8 +254,6 @@ import java.util.concurrent.atomic.AtomicInteger;
@Getter @Getter
public class GeyserSession implements GeyserConnection, GeyserCommandSource { public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private static final Gson GSON = new Gson();
private final GeyserImpl geyser; private final GeyserImpl geyser;
private final UpstreamSession upstream; private final UpstreamSession upstream;
private DownstreamSession downstream; 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. * A cache of IDs from ClientboundKeepAlivePackets that have been sent to the Bedrock client, but haven't been returned to the server.
* Only used if {@link GeyserConfiguration#isForwardPlayerPing()} is enabled. * Only used if {@link GeyserConfig.GameplayConfig#forwardPlayerPing()} is enabled.
*/ */
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>(); private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
@@ -753,6 +749,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Accessors(fluent = true) @Accessors(fluent = true)
private boolean hasAcceptedCodeOfConduct = false; private boolean hasAcceptedCodeOfConduct = false;
@Accessors(fluent = true)
@Setter
private boolean integratedPackActive = false;
private final Set<InputLocksFlag> inputLocksSet = EnumSet.noneOf(InputLocksFlag.class); private final Set<InputLocksFlag> inputLocksSet = EnumSet.noneOf(InputLocksFlag.class);
private boolean inputLockDirty; private boolean inputLockDirty;
@@ -799,12 +799,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.spawned = false; this.spawned = false;
this.loggedIn = false; this.loggedIn = false;
if (geyser.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) {
this.emotes = new HashSet<>(); this.emotes = new HashSet<>();
geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes())); geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes()));
} else {
this.emotes = null;
}
this.remoteServer = geyser.defaultRemoteServer(); this.remoteServer = geyser.defaultRemoteServer();
} }
@@ -870,7 +866,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
upstream.sendPacket(playStatusPacket); upstream.sendPacket(playStatusPacket);
SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket(); SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket();
setCommandsEnabledPacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled()); setCommandsEnabledPacket.setCommandsEnabled(!geyser.config().gameplay().xboxAchievementsEnabled());
upstream.sendPacket(setCommandsEnabledPacket); upstream.sendPacket(setCommandsEnabledPacket);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
@@ -929,7 +925,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
MinecraftProfile mcProfile; MinecraftProfile mcProfile;
MinecraftToken mcToken; MinecraftToken mcToken;
try { 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 if (parsedAuthChain.has("mcProfile")) { // Old Minecraft v4 auth chain
parsedAuthChain = MinecraftAuth4To5Migrator.migrateJavaSave(parsedAuthChain, GeyserImpl.OAUTH_CONFIG); parsedAuthChain = MinecraftAuth4To5Migrator.migrateJavaSave(parsedAuthChain, GeyserImpl.OAUTH_CONFIG);
} }
@@ -946,7 +942,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
new GameProfile(mcProfile.getId(), mcProfile.getName()), new GameProfile(mcProfile.getId(), mcProfile.getName()),
mcToken.getToken() mcToken.getToken()
); );
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(JavaAuthManager.toJson(authManager))); geyser.saveAuthChain(bedrockUsername(), GeyserImpl.GSON.toJson(JavaAuthManager.toJson(authManager)));
return Boolean.TRUE; return Boolean.TRUE;
}).whenComplete((successful, ex) -> { }).whenComplete((successful, ex) -> {
if (this.closed) { if (this.closed) {
@@ -1039,7 +1035,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
} }
// Save our auth chain for later use // 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; return true;
}).getNow(false); }).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. // Disable automatic creation of a new TcpClientSession when transferring - we don't use that functionality.
this.downstream.getSession().setFlag(MinecraftConstants.FOLLOW_TRANSFERS, false); 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()); downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
} }
if (geyser.getConfig().isForwardPlayerPing()) { if (geyser.config().gameplay().forwardPlayerPing()) {
// Let Geyser handle sending the keep alive // Let Geyser handle sending the keep alive
downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
} }
@@ -1140,7 +1136,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
} else { } else {
// Downstream's disconnect will fire an event that prints a log message // Downstream's disconnect will fire an event that prints a log message
// Otherwise, we print a message here // Otherwise, we print a message here
String address = geyser.getConfig().isLogPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : "<IP address withheld>"; String address = geyser.config().logPlayerIpAddresses() ? upstream.getAddress().getAddress().toString() : "<IP address withheld>";
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, MessageTranslator.convertMessage(reason))); 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.setLevelGameType(GameType.SURVIVAL);
startGamePacket.setDifficulty(1); startGamePacket.setDifficulty(1);
startGamePacket.setDefaultSpawn(Vector3i.ZERO); startGamePacket.setDefaultSpawn(Vector3i.ZERO);
startGamePacket.setAchievementsDisabled(!geyser.getConfig().isXboxAchievementsEnabled()); startGamePacket.setAchievementsDisabled(!geyser.config().gameplay().xboxAchievementsEnabled());
startGamePacket.setCurrentTick(-1); startGamePacket.setCurrentTick(-1);
startGamePacket.setEduEditionOffers(0); startGamePacket.setEduEditionOffers(0);
startGamePacket.setEduFeaturesEnabled(false); startGamePacket.setEduFeaturesEnabled(false);
@@ -1750,7 +1746,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
startGamePacket.setBroadcastingToLan(true); startGamePacket.setBroadcastingToLan(true);
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled()); startGamePacket.setCommandsEnabled(!geyser.config().gameplay().xboxAchievementsEnabled());
startGamePacket.setTexturePacksRequired(false); startGamePacket.setTexturePacksRequired(false);
startGamePacket.setBonusChestEnabled(false); startGamePacket.setBonusChestEnabled(false);
startGamePacket.setStartingWithMap(false); startGamePacket.setStartingWithMap(false);
@@ -1768,7 +1764,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
startGamePacket.setEducationProductionId(""); startGamePacket.setEducationProductionId("");
startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty()); startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty());
String serverName = geyser.getConfig().getBedrock().serverName(); String serverName = geyser.config().gameplay().serverName();
startGamePacket.setLevelId(serverName); startGamePacket.setLevelId(serverName);
startGamePacket.setLevelName(serverName); startGamePacket.setLevelName(serverName);
@@ -1914,7 +1910,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
public void sendDownstreamPacket(Packet packet, ProtocolState intendedState) { public void sendDownstreamPacket(Packet packet, ProtocolState intendedState) {
// protocol can be null when we're not yet logged in (online auth) // protocol can be null when we're not yet logged in (online auth)
if (protocol == null) { if (protocol == null) {
if (geyser.getConfig().isDebugMode()) { if (geyser.config().debugMode()) {
geyser.getLogger().debug("Tried to send downstream packet with no downstream session!"); geyser.getLogger().debug("Tried to send downstream packet with no downstream session!");
Thread.dumpStack(); Thread.dumpStack();
} }
@@ -1940,7 +1936,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
if (channel == null) { if (channel == null) {
// Channel is only null before the connection has initialized // Channel is only null before the connection has initialized
geyser.getLogger().warning("Tried to send a packet to the Java server too early!"); geyser.getLogger().warning("Tried to send a packet to the Java server too early!");
if (geyser.getConfig().isDebugMode()) { if (geyser.config().debugMode()) {
Thread.dumpStack(); Thread.dumpStack();
} }
return; return;
@@ -2431,7 +2427,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private void softEnumPacket(String name, SoftEnumUpdateType type, String enums) { private void softEnumPacket(String name, SoftEnumUpdateType type, String enums) {
// There is no need to send command enums if command suggestions are disabled // 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; return;
} }
UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket(); UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket();

View File

@@ -110,7 +110,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
} }
String address; String address;
if (geyser.getConfig().getRemote().isForwardHost()) { if (geyser.config().java().forwardHostname()) {
address = session.joinAddress(); address = session.joinAddress();
} else { } else {
address = intentionPacket.getHostname(); address = intentionPacket.getHostname();
@@ -172,7 +172,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale); customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale);
// Explain that they may be looking for Floodgate. // Explain that they may be looking for Floodgate.
geyser.getLogger().warning(GeyserLocale.getLocaleStringLog( geyser.getLogger().warning(GeyserLocale.getLocaleStringLog(
geyser.getPlatformType() == PlatformType.STANDALONE ? geyser.platformType() == PlatformType.STANDALONE ?
"geyser.network.remote.floodgate_explanation_standalone" "geyser.network.remote.floodgate_explanation_standalone"
: "geyser.network.remote.floodgate_explanation_plugin", : "geyser.network.remote.floodgate_explanation_plugin",
Constants.FLOODGATE_DOWNLOAD_LOCATION Constants.FLOODGATE_DOWNLOAD_LOCATION
@@ -180,7 +180,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
} else { } else {
// Likely that Floodgate is not configured correctly. // Likely that Floodgate is not configured correctly.
customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.floodgate_login_error", locale); 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")); geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.remote.floodgate_login_error_standalone"));
} }
} }
@@ -203,7 +203,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
} else { } else {
GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause); GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause);
} }
if (geyser.getConfig().isDebugMode()) { if (geyser.config().debugMode()) {
cause.printStackTrace(); cause.printStackTrace();
} }
} }
@@ -233,7 +233,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
(event.getPacketClass() != null ? "(" + event.getPacketClass().getSimpleName() + ") " : "") + (event.getPacketClass() != null ? "(" + event.getPacketClass().getSimpleName() + ") " : "") +
event.getCause().getMessage()) event.getCause().getMessage())
); );
if (geyser.getConfig().isDebugMode()) if (geyser.config().debugMode())
event.getCause().printStackTrace(); event.getCause().printStackTrace();
event.setSuppress(true); event.setSuppress(true);
} }

View File

@@ -55,7 +55,7 @@ public final class SessionDisconnectListener {
String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale()); String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale());
if (testForOutdatedServer(disconnectReason)) { if (testForOutdatedServer(disconnectReason)) {
String locale = session.locale(); 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) ? String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY || platform == PlatformType.VIAPROXY) ?
"geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server"; "geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server";
event.disconnectReason(GeyserLocale.getPlayerLocaleString(outdatedType, locale, GameProtocol.getJavaVersions().get(0)) + '\n' event.disconnectReason(GeyserLocale.getPlayerLocaleString(outdatedType, locale, GameProtocol.getJavaVersions().get(0)) + '\n'

View File

@@ -25,93 +25,97 @@
package org.geysermc.geyser.session.auth; package org.geysermc.geyser.session.auth;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.gson.JsonDeserializationContext;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.JsonDeserializer;
import com.fasterxml.jackson.annotation.JsonProperty; 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.Getter;
import lombok.Setter; import lombok.Setter;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.UiProfile; import org.geysermc.floodgate.util.UiProfile;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter @Getter
public final class BedrockClientData { public final class BedrockClientData {
@JsonProperty(value = "GameVersion") @SerializedName(value = "GameVersion")
private String gameVersion; private String gameVersion;
@JsonProperty(value = "ServerAddress") @SerializedName(value = "ServerAddress")
private String serverAddress; private String serverAddress;
@JsonProperty(value = "ThirdPartyName") @SerializedName(value = "ThirdPartyName")
private String username; private String username;
@JsonProperty(value = "LanguageCode") @SerializedName(value = "LanguageCode")
private String languageCode; private String languageCode;
@JsonProperty(value = "SkinId") @SerializedName(value = "SkinId")
private String skinId; private String skinId;
@JsonProperty(value = "SkinData") @SerializedName(value = "SkinData")
private String skinData; private String skinData;
@JsonProperty(value = "SkinImageHeight") @SerializedName(value = "SkinImageHeight")
private int skinImageHeight; private int skinImageHeight;
@JsonProperty(value = "SkinImageWidth") @SerializedName(value = "SkinImageWidth")
private int skinImageWidth; private int skinImageWidth;
@JsonProperty(value = "CapeId") @SerializedName(value = "CapeId")
private String capeId; private String capeId;
@JsonProperty(value = "CapeData") @SerializedName(value = "CapeData")
@JsonAdapter(value = StringToByteDeserializer.class)
private byte[] capeData; private byte[] capeData;
@JsonProperty(value = "CapeImageHeight") @SerializedName(value = "CapeImageHeight")
private int capeImageHeight; private int capeImageHeight;
@JsonProperty(value = "CapeImageWidth") @SerializedName(value = "CapeImageWidth")
private int capeImageWidth; private int capeImageWidth;
@JsonProperty(value = "CapeOnClassicSkin") @SerializedName(value = "CapeOnClassicSkin")
private boolean capeOnClassicSkin; private boolean capeOnClassicSkin;
@JsonProperty(value = "SkinResourcePatch") @SerializedName(value = "SkinResourcePatch")
private String geometryName; private String geometryName;
@JsonProperty(value = "SkinGeometryData") @SerializedName(value = "SkinGeometryData")
private String geometryData; private String geometryData;
@JsonProperty(value = "PersonaSkin") @SerializedName(value = "PersonaSkin")
private boolean personaSkin; private boolean personaSkin;
@JsonProperty(value = "PremiumSkin") @SerializedName(value = "PremiumSkin")
private boolean premiumSkin; private boolean premiumSkin;
@JsonProperty(value = "DeviceId") @SerializedName(value = "DeviceId")
private String deviceId; private String deviceId;
@JsonProperty(value = "DeviceModel") @SerializedName(value = "DeviceModel")
private String deviceModel; private String deviceModel;
@JsonProperty(value = "DeviceOS") @SerializedName(value = "DeviceOS")
private DeviceOs deviceOs; private DeviceOs deviceOs;
@JsonProperty(value = "UIProfile") @SerializedName(value = "UIProfile")
private UiProfile uiProfile; private UiProfile uiProfile;
@JsonProperty(value = "GuiScale") @SerializedName(value = "GuiScale")
private int guiScale; private int guiScale;
@JsonProperty(value = "CurrentInputMode") @SerializedName(value = "CurrentInputMode")
private InputMode currentInputMode; private InputMode currentInputMode;
@JsonProperty(value = "DefaultInputMode") @SerializedName(value = "DefaultInputMode")
private InputMode defaultInputMode; private InputMode defaultInputMode;
@JsonProperty("PlatformOnlineId") @SerializedName("PlatformOnlineId")
private String platformOnlineId; private String platformOnlineId;
@JsonProperty(value = "PlatformOfflineId") @SerializedName(value = "PlatformOfflineId")
private String platformOfflineId; private String platformOfflineId;
@JsonProperty(value = "SelfSignedId") @SerializedName(value = "SelfSignedId")
private UUID selfSignedId; private UUID selfSignedId;
@JsonProperty(value = "ClientRandomId") @SerializedName(value = "ClientRandomId")
private long clientRandomId; private long clientRandomId;
@JsonProperty(value = "ArmSize") @SerializedName(value = "ArmSize")
private String armSize; private String armSize;
@JsonProperty(value = "SkinAnimationData") @SerializedName(value = "SkinAnimationData")
private String skinAnimationData; private String skinAnimationData;
@JsonProperty(value = "SkinColor") @SerializedName(value = "SkinColor")
private String skinColor; private String skinColor;
@JsonProperty(value = "ThirdPartyNameOnly") @SerializedName(value = "ThirdPartyNameOnly")
private boolean thirdPartyNameOnly; private boolean thirdPartyNameOnly;
@JsonProperty(value = "PlayFabId") @SerializedName(value = "PlayFabId")
private String playFabId; private String playFabId;
@JsonIgnore
@Setter @Setter
private String originalString = null; private transient String originalString = null;
public DeviceOs getDeviceOs() { public DeviceOs getDeviceOs() {
return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN;
@@ -128,4 +132,11 @@ public final class BedrockClientData {
public UiProfile getUiProfile() { public UiProfile getUiProfile() {
return uiProfile != null ? uiProfile : UiProfile.CLASSIC; return uiProfile != null ? uiProfile : UiProfile.CLASSIC;
} }
private static final class StringToByteDeserializer implements JsonDeserializer<byte[]> {
@Override
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return json.getAsString().getBytes(StandardCharsets.UTF_8);
}
}
} }

View File

@@ -68,7 +68,7 @@ public final class BundleCache {
if (itemStack.is(session, ItemTag.BUNDLES)) { if (itemStack.is(session, ItemTag.BUNDLES)) {
if (itemStack.getBundleData() != null) { if (itemStack.getBundleData() != null) {
session.getGeyser().getLogger().warning("Stack has bundle data already! It should not!"); 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("Player: " + session.javaUsername());
session.getGeyser().getLogger().debug("Stack: " + itemStack); session.getGeyser().getLogger().debug("Stack: " + itemStack);
} }

View File

@@ -27,7 +27,7 @@ package org.geysermc.geyser.session.cache;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; 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.session.GeyserSession;
import org.geysermc.geyser.util.CooldownUtils; import org.geysermc.geyser.util.CooldownUtils;
@@ -54,15 +54,16 @@ public class PreferencesCache {
private boolean prefersCustomSkulls; 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 @Setter
private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown(); private CooldownUtils.CooldownType cooldownPreference;
public PreferencesCache(GeyserSession session) { public PreferencesCache(GeyserSession session) {
this.session = session; this.session = session;
prefersCustomSkulls = session.getGeyser().getConfig().isAllowCustomSkulls(); prefersCustomSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0;
cooldownPreference = session.getGeyser().config().gameplay().showCooldown();
} }
/** /**
@@ -71,10 +72,10 @@ public class PreferencesCache {
* If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply: <br> * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply: <br>
* <br> * <br>
* {@link GeyserSession#isReducedDebugInfo()} is enabled * {@link GeyserSession#isReducedDebugInfo()} is enabled
* {@link GeyserConfiguration#isShowCoordinates()} is disabled * {@link GeyserConfig.GameplayConfig#showCoordinates()} is disabled
*/ */
public void updateShowCoordinates() { public void updateShowCoordinates() {
allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().getConfig().isShowCoordinates(); allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().config().gameplay().showCoordinates();
session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); 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. * @return true if the session prefers custom skulls, and the config allows them.
*/ */
public boolean showCustomSkulls() { public boolean showCustomSkulls() {
return prefersCustomSkulls && session.getGeyser().getConfig().isAllowCustomSkulls(); return prefersCustomSkulls && session.getGeyser().config().gameplay().maxVisibleCustomSkulls() != 0;
} }
} }

View File

@@ -67,11 +67,11 @@ public class SkullCache {
public SkullCache(GeyserSession session) { public SkullCache(GeyserSession session) {
this.session = session; this.session = session;
this.maxVisibleSkulls = session.getGeyser().getConfig().getMaxVisibleCustomSkulls(); this.maxVisibleSkulls = session.getGeyser().config().gameplay().maxVisibleCustomSkulls();
this.cullingEnabled = this.maxVisibleSkulls != -1; this.cullingEnabled = this.maxVisibleSkulls != -1;
// Normal skulls are not rendered beyond 64 blocks // 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; this.skullRenderDistanceSquared = distance * distance;
} }
@@ -99,7 +99,7 @@ public class SkullCache {
} }
} catch (IOException e) { } catch (IOException e) {
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty); 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(); e.printStackTrace();
} }
} }

View File

@@ -25,11 +25,9 @@
package org.geysermc.geyser.skin; package org.geysermc.geyser.skin;
import com.fasterxml.jackson.core.JsonProcessingException; import com.google.gson.JsonArray;
import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonObject;
import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonSyntaxException;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter; import lombok.Getter;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.floodgate.util.WebsocketEventType; import org.geysermc.floodgate.util.WebsocketEventType;
@@ -37,6 +35,7 @@ import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.JsonUtils;
import org.geysermc.geyser.util.PluginMessageUtils; import org.geysermc.geyser.util.PluginMessageUtils;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshake;
@@ -52,7 +51,6 @@ import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public final class FloodgateSkinUploader { public final class FloodgateSkinUploader {
private final ObjectMapper JACKSON = new ObjectMapper();
private final List<String> skinQueue = new ArrayList<>(); private final List<String> skinQueue = new ArrayList<>();
private final GeyserLogger logger; private final GeyserLogger logger;
@@ -79,15 +77,14 @@ public final class FloodgateSkinUploader {
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
// The reason why I don't like Jackson
try { try {
JsonNode node = JACKSON.readTree(message); JsonObject node = JsonUtils.parseJson(message);
if (node.has("error")) { if (node.has("error")) {
logger.error("Got an error: " + node.get("error").asText()); logger.error("Got an error: " + node.get("error").getAsString());
return; return;
} }
int typeId = node.get("event_id").asInt(); int typeId = node.get("event_id").getAsInt();
WebsocketEventType type = WebsocketEventType.fromId(typeId); WebsocketEventType type = WebsocketEventType.fromId(typeId);
if (type == null) { if (type == null) {
logger.warning(String.format( logger.warning(String.format(
@@ -98,11 +95,11 @@ public final class FloodgateSkinUploader {
switch (type) { switch (type) {
case SUBSCRIBER_CREATED: case SUBSCRIBER_CREATED:
id = node.get("id").asInt(); id = node.get("id").getAsInt();
verifyCode = node.get("verify_code").asText(); verifyCode = node.get("verify_code").getAsString();
break; break;
case SUBSCRIBER_COUNT: case SUBSCRIBER_COUNT:
subscribersCount = node.get("subscribers_count").asInt(); subscribersCount = node.get("subscribers_count").getAsInt();
break; break;
case SKIN_UPLOADED: case SKIN_UPLOADED:
// if Geyser is the only subscriber we have send it to the server manually // if Geyser is the only subscriber we have send it to the server manually
@@ -111,19 +108,19 @@ public final class FloodgateSkinUploader {
break; break;
} }
String xuid = node.get("xuid").asText(); String xuid = node.get("xuid").getAsString();
GeyserSession session = geyser.connectionByXuid(xuid); GeyserSession session = geyser.connectionByXuid(xuid);
if (session != null) { if (session != null) {
if (!node.get("success").asBoolean()) { if (!node.get("success").getAsBoolean()) {
logger.info("Failed to upload skin for " + session.bedrockUsername()); logger.info("Failed to upload skin for " + session.bedrockUsername());
return; return;
} }
JsonNode data = node.get("data"); JsonObject data = node.getAsJsonObject("data");
String value = data.get("value").asText(); String value = data.get("value").getAsString();
String signature = data.get("signature").asText(); String signature = data.get("signature").getAsString();
byte[] bytes = (value + '\0' + signature) byte[] bytes = (value + '\0' + signature)
.getBytes(StandardCharsets.UTF_8); .getBytes(StandardCharsets.UTF_8);
@@ -131,8 +128,8 @@ public final class FloodgateSkinUploader {
} }
break; break;
case LOG_MESSAGE: case LOG_MESSAGE:
String logMessage = node.get("message").asText(); String logMessage = node.get("message").getAsString();
switch (node.get("priority").asInt()) { switch (node.get("priority").getAsInt()) {
case -1 -> logger.debug("Got a message from skin uploader: " + logMessage); case -1 -> logger.debug("Got a message from skin uploader: " + logMessage);
case 0 -> logger.info("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); case 1 -> logger.error("Got a message from skin uploader: " + logMessage);
@@ -150,20 +147,19 @@ public final class FloodgateSkinUploader {
@Override @Override
public void onClose(int code, String reason, boolean remote) { public void onClose(int code, String reason, boolean remote) {
if (reason != null && !reason.isEmpty()) { if (reason != null && !reason.isEmpty()) {
// The reason why I don't like Jackson
try { try {
JsonNode node = JACKSON.readTree(reason); JsonObject node = JsonUtils.parseJson(reason);
// info means that the uploader itself did nothing wrong // info means that the uploader itself did nothing wrong
if (node.has("info")) { 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); logger.debug("Got disconnected from the skin uploader: " + info);
} }
// error means that the uploader did something wrong // error means that the uploader did something wrong
if (node.has("error")) { 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); logger.info("Got disconnected from the skin uploader: " + error);
} }
} catch (JsonProcessingException ignored) { } catch (JsonSyntaxException ignored) {
// ignore invalid json // ignore invalid json
} catch (Exception e) { } catch (Exception e) {
logger.error("Error while handling onClose", e); logger.error("Error while handling onClose", e);
@@ -198,24 +194,17 @@ public final class FloodgateSkinUploader {
return; return;
} }
ObjectNode node = JACKSON.createObjectNode(); JsonObject node = new JsonObject();
if (chainData != null) { if (chainData != null) {
ArrayNode chainDataNode = JACKSON.createArrayNode(); JsonArray chainDataNode = new JsonArray();
chainData.forEach(chainDataNode::add); chainData.forEach(chainDataNode::add);
node.set("chain_data", chainDataNode); node.add("chain_data", chainDataNode);
} else { } 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 = node.toString();
String jsonString;
try {
jsonString = JACKSON.writeValueAsString(node);
} catch (Exception e) {
logger.error("Failed to upload skin", e);
return;
}
if (client.isOpen()) { if (client.isOpen()) {
client.send(jsonString); client.send(jsonString);

Some files were not shown because too many files have changed in this diff Show More