diff --git a/ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java index f9ba68302..ccdaeda4e 100644 --- a/ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/BlockEntityProcessor.java @@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; @SupportedAnnotationTypes("*") -@SupportedSourceVersion(SourceVersion.RELEASE_16) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class BlockEntityProcessor extends ClassProcessor { public BlockEntityProcessor() { super("org.geysermc.geyser.translator.level.block.entity.BlockEntity"); diff --git a/ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java index 84e2e2ffd..30d94b7f5 100644 --- a/ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/CollisionRemapperProcessor.java @@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; @SupportedAnnotationTypes("*") -@SupportedSourceVersion(SourceVersion.RELEASE_16) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class CollisionRemapperProcessor extends ClassProcessor { public CollisionRemapperProcessor() { super("org.geysermc.geyser.translator.collision.CollisionRemapper"); diff --git a/ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java index 9b99d679b..d538a9ca1 100644 --- a/ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/PacketTranslatorProcessor.java @@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; @SupportedAnnotationTypes("*") -@SupportedSourceVersion(SourceVersion.RELEASE_16) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class PacketTranslatorProcessor extends ClassProcessor { public PacketTranslatorProcessor() { super("org.geysermc.geyser.translator.protocol.Translator"); diff --git a/ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java b/ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java index c35c0ee4e..30ceef2c6 100644 --- a/ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java +++ b/ap/src/main/java/org/geysermc/geyser/processor/SoundHandlerProcessor.java @@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; @SupportedAnnotationTypes("*") -@SupportedSourceVersion(SourceVersion.RELEASE_16) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class SoundHandlerProcessor extends ClassProcessor { public SoundHandlerProcessor() { super("org.geysermc.geyser.translator.sound.SoundTranslator"); diff --git a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java index 0d6007c1c..a9327d0db 100644 --- a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -29,12 +29,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.Geyser; import org.geysermc.api.GeyserApiBase; +import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.PlatformType; import java.nio.file.Path; @@ -134,6 +136,30 @@ public interface GeyserApi extends GeyserApiBase { @NonNull PlatformType platformType(); + /** + * Gets the version of Java Minecraft that is supported. + * + * @return the supported version of Java Minecraft + */ + @NonNull + MinecraftVersion supportedJavaVersion(); + + /** + * Gets a list of Bedrock Minecraft versions that are supported. + * + * @return the list of supported Bedrock Minecraft versions + */ + @NonNull + List supportedBedrockVersions(); + + /** + * Gets the {@link CommandSource} for the console. + * + * @return the console command source + */ + @NonNull + CommandSource consoleCommandSource(); + /** * Gets the current {@link GeyserApiBase} instance. * diff --git a/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java b/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java index 61fe286aa..af35d7ad1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java @@ -50,6 +50,14 @@ public interface BedrockListener { */ int port(); + /** + * Gets the broadcast port that's sent to Bedrock clients with the motd. + * This is the port that Bedrock clients will connect with. It usually does not differ from the listening port. + * + * @return the broadcast port + */ + int broadcastPort(); + /** * Gets the primary MOTD shown to Bedrock players if a ping passthrough setting is not enabled. *

diff --git a/api/src/main/java/org/geysermc/geyser/api/util/MinecraftVersion.java b/api/src/main/java/org/geysermc/geyser/api/util/MinecraftVersion.java new file mode 100644 index 000000000..34a4b59af --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/util/MinecraftVersion.java @@ -0,0 +1,49 @@ +/* + * 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.api.util; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Represents a Minecraft version. + */ +public interface MinecraftVersion { + + /** + * Gets the Minecraft version as a String. + * Example: "1.20.2", or "1.20.40/1.20.41" + * + * @return the version string + */ + @NonNull String versionString(); + + /** + * Gets the protocol version of this Minecraft version. + * + * @return the protocol version + */ + int protocolVersion(); +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java index 01bc0bea4..ad31131bd 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java @@ -150,7 +150,7 @@ public class GeyserSpigotInjector extends GeyserInjector { childHandler = (ChannelInitializer) childHandlerField.get(handler); // ViaVersion non-Paper-injector workaround so we aren't double-injecting if (isViaVersion && childHandler instanceof BukkitChannelInitializer) { - childHandler = ((BukkitChannelInitializer) childHandler).getOriginal(); + childHandler = ((BukkitChannelInitializer) childHandler).original(); } break; } catch (Exception e) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 74f359e02..42f0d17f4 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -197,7 +197,11 @@ public class GeyserSpigotWorldManager extends WorldManager { @Override public boolean hasPermission(GeyserSession session, String permission) { - return Objects.requireNonNull(Bukkit.getPlayer(session.getPlayerEntity().getUsername())).hasPermission(permission); + Player player = Bukkit.getPlayer(session.javaUuid()); + if (player != null) { + return player.hasPermission(permission); + } + return false; } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java index e787e7355..99c759767 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java @@ -47,8 +47,14 @@ public class GeyserVelocityCompressionDisabler extends ChannelDuplexHandler { Method setCompressionMethod = null; try { - compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression"); - loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess"); + try { + compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompressionPacket"); + loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket"); + } catch (Exception ignored) { + // Velocity renamed packet classes in https://github.com/PaperMC/Velocity/commit/2ac8751337befd04f4663575f5d752c748384110 + compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression"); + loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess"); + } compressionEnabledEvent = Class.forName("com.velocitypowered.proxy.protocol.VelocityConnectionEvent") .getDeclaredField("COMPRESSION_ENABLED").get(null); setCompressionMethod = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection") diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index aef2288d6..e9ea08260 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -42,7 +42,11 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.api.Geyser; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.util.MinecraftVersion; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; import org.geysermc.erosion.packet.Packets; @@ -60,14 +64,15 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; 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.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.erosion.UnixSocketClientListener; import org.geysermc.geyser.event.GeyserEventBus; import org.geysermc.geyser.extension.GeyserExtensionManager; +import org.geysermc.geyser.impl.MinecraftVersionImpl; import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.GeyserServer; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -111,8 +116,8 @@ public class GeyserImpl implements GeyserApi { .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); public static final String NAME = "Geyser"; - public static final String GIT_VERSION = "${gitVersion}"; // A fallback for running in IDEs - public static final String VERSION = "${version}"; // A fallback for running in IDEs + public static final String GIT_VERSION = "${gitVersion}"; + public static final String VERSION = "${version}"; public static final String BUILD_NUMBER = "${buildNumber}"; public static final String BRANCH = "${branch}"; @@ -309,6 +314,22 @@ public class GeyserImpl implements GeyserApi { } } + String broadcastPort = System.getProperty("geyserBroadcastPort", ""); + if (!broadcastPort.isEmpty()) { + int parsedPort; + try { + parsedPort = Integer.parseInt(broadcastPort); + if (parsedPort < 1 || parsedPort > 65535) { + throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!"); + } + } catch (NumberFormatException e) { + logger.error(String.format("Invalid broadcast port: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")")); + parsedPort = config.getBedrock().port(); + } + config.getBedrock().setBroadcastPort(parsedPort); + logger.info("Broadcast port set from system property: " + parsedPort); + } + boolean floodgatePresent = bootstrap.testFloodgatePluginPresent(); if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) { logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " @@ -683,6 +704,25 @@ public class GeyserImpl implements GeyserApi { return platformType; } + @Override + public @NonNull MinecraftVersion supportedJavaVersion() { + return new MinecraftVersionImpl(GameProtocol.getJavaMinecraftVersion(), GameProtocol.getJavaProtocolVersion()); + } + + @Override + public @NonNull List supportedBedrockVersions() { + ArrayList versions = new ArrayList<>(); + for (BedrockCodec codec : GameProtocol.SUPPORTED_BEDROCK_CODECS) { + versions.add(new MinecraftVersionImpl(codec.getMinecraftVersion(), codec.getProtocolVersion())); + } + return Collections.unmodifiableList(versions); + } + + @Override + public @NonNull CommandSource consoleCommandSource() { + return getLogger(); + } + public int buildNumber() { if (!this.isProductionEnvironment()) { return 0; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index ad51826c3..981c97595 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -31,6 +31,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoopbackUtil; @@ -84,7 +85,7 @@ public class ConnectionTestCommand extends GeyserCommand { return; } } else { - port = 19132; + port = geyser.getConfig().getBedrock().broadcastPort(); } String ip = fullAddress[0]; @@ -112,30 +113,41 @@ public class ConnectionTestCommand extends GeyserCommand { return; } - // Issue: do the ports not line up? - if (port != geyser.getConfig().getBedrock().port()) { - if (fullAddress.length == 2) { - sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration (" - + geyser.getConfig().getBedrock().port() + ")"); - sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config."); - if (geyser.getConfig().getBedrock().isCloneRemotePort()) { - sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead."); + GeyserConfiguration config = geyser.getConfig(); + + // Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing + if (config.getBedrock().broadcastPort() == config.getBedrock().port()) { + if (port != config.getBedrock().port()) { + if (fullAddress.length == 2) { + sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration (" + + config.getBedrock().port() + ")"); + sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config."); + if (config.getBedrock().isCloneRemotePort()) { + sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead."); + } + } else { + sender.sendMessage("You did not specify the port to check (add it with \":\"), " + + "and the default port 19132 does not match the port in your Geyser configuration (" + + config.getBedrock().port() + ")!"); + sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`."); } - } else { - sender.sendMessage("You did not specify the port to check (add it with \":\"), " + - "and the default port 19132 does not match the port in your Geyser configuration (" - + geyser.getConfig().getBedrock().port() + ")!"); - sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`."); + } + } else { + if (config.getBedrock().broadcastPort() != port) { + sender.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration (" + + config.getBedrock().broadcastPort() + "). "); + sender.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on."); + sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config."); } } // Issue: is the `bedrock` `address` in the config different? - if (!geyser.getConfig().getBedrock().address().equals("0.0.0.0")) { + if (!config.getBedrock().address().equals("0.0.0.0")) { sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional."); } // Issue: did someone turn on enable-proxy-protocol, and they didn't mean it? - if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { + if (config.getBedrock().isEnableProxyProtocol()) { sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + "Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled."); } @@ -166,7 +178,7 @@ public class ConnectionTestCommand extends GeyserCommand { String connectionTestMotd = "Geyser Connection Test " + randomStr; CONNECTION_TEST_MOTD = connectionTestMotd; - sender.sendMessage("Testing server connection now. Please wait..."); + sender.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait..."); JsonNode output; try { String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index ecbbd91e1..6f05221e2 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -124,6 +124,8 @@ public interface GeyserConfiguration { void setPort(int port); + void setBroadcastPort(int broadcastPort); + boolean isCloneRemotePort(); int getCompressionLevel(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index bcf94f931..b0db58481 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -176,6 +176,15 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration 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; diff --git a/core/src/main/java/org/geysermc/geyser/impl/MinecraftVersionImpl.java b/core/src/main/java/org/geysermc/geyser/impl/MinecraftVersionImpl.java new file mode 100644 index 000000000..121d33c64 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/MinecraftVersionImpl.java @@ -0,0 +1,31 @@ +/* + * 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.impl; + +import org.geysermc.geyser.api.util.MinecraftVersion; + +public record MinecraftVersionImpl(String versionString, int protocolVersion) implements MinecraftVersion { +} diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 11702e78a..e665a22ef 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -67,6 +67,7 @@ public final class BlockStateValues { private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap(); private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap(); private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap(); + private static final IntSet UPPER_DOORS = new IntOpenHashSet(); public static final int JAVA_AIR_ID = 0; @@ -219,6 +220,10 @@ public final class BlockStateValues { if (javaId.contains("_cauldron") && !javaId.contains("water_")) { NON_WATER_CAULDRONS.add(javaBlockState); } + + if (javaId.contains("_door[") && javaId.contains("half=upper")) { + UPPER_DOORS.add(javaBlockState); + } } /** @@ -498,6 +503,16 @@ public final class BlockStateValues { return WATER_LEVEL.getOrDefault(state, -1); } + /** + * Check if a block is the upper half of a door. + * + * @param state BlockState of the block + * @return True if the block is the upper half of a door + */ + public static boolean isUpperDoor(int state) { + return UPPER_DOORS.contains(state); + } + /** * Get the height of water from the block state * This is used in FishingHookEntity to create splash sounds when the hook hits the water. In addition, diff --git a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java index 5076dbeb2..cf794261b 100644 --- a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -285,6 +285,11 @@ public class LoggingPacketHandler implements BedrockPacketHandler { return defaultHandler(packet); } + @Override + public PacketSignal handle(SubChunkRequestPacket packet) { + return defaultHandler(packet); + } + @Override public PacketSignal handle(SubClientLoginPacket packet) { return defaultHandler(packet); diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 8d8a44118..4806cc7cf 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -28,6 +28,8 @@ package org.geysermc.geyser.network; import io.netty.buffer.Unpooled; import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; +import org.cloudburstmc.protocol.bedrock.codec.compat.BedrockCompat; +import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622; import org.cloudburstmc.protocol.bedrock.data.ExperimentData; import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm; import org.cloudburstmc.protocol.bedrock.data.ResourcePackType; @@ -95,6 +97,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.disconnect(disconnectMessage); return false; } else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + if (protocolVersion < Bedrock_v622.CODEC.getProtocolVersion()) { + // https://github.com/GeyserMC/Geyser/issues/4378 + session.getUpstream().getSession().setCodec(BedrockCompat.CODEC_LEGACY); + } session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); return false; } else { diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index 20340826f..401a7f2cf 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -102,6 +102,11 @@ public final class GeyserServer { private ChannelFuture bootstrapFuture; + /** + * The port to broadcast in the pong. This can be different from the port the server is bound to, e.g. due to port forwarding. + */ + private final int broadcastPort; + public GeyserServer(GeyserImpl geyser, int threadCount) { this.geyser = geyser; this.group = TRANSPORT.eventLoopGroupFactory().apply(threadCount); @@ -115,6 +120,13 @@ public final class GeyserServer { } else { this.proxiedAddresses = null; } + + // It's set to 0 only if no system property or manual config value was set + if (geyser.getConfig().getBedrock().broadcastPort() == 0) { + geyser.getConfig().getBedrock().setBroadcastPort(geyser.getConfig().getBedrock().port()); + } + + this.broadcastPort = geyser.getConfig().getBedrock().broadcastPort(); } public CompletableFuture bind(InetSocketAddress address) { @@ -243,8 +255,8 @@ public final class GeyserServer { .nintendoLimited(false) .protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers. - .ipv4Port(this.geyser.getConfig().getBedrock().port()) - .ipv6Port(this.geyser.getConfig().getBedrock().port()) + .ipv4Port(this.broadcastPort) + .ipv6Port(this.broadcastPort) .serverId(bootstrapFuture.channel().config().getOption(RakChannelOption.RAK_GUID)); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 28ad5887c..26e206458 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1633,6 +1633,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * @param intendedState the state the client should be in */ public void sendDownstreamPacket(Packet packet, ProtocolState intendedState) { + // protocol can be null when we're not yet logged in (online auth) + if (protocol == null) { + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().debug("Tried to send downstream packet with no downstream session!"); + Thread.dumpStack(); + } + return; + } + if (protocol.getState() != intendedState) { geyser.getLogger().debug("Tried to send " + packet.getClass().getSimpleName() + " packet while not in " + intendedState.name() + " state"); return; diff --git a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java index f6e37787d..692a05110 100644 --- a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java @@ -196,7 +196,6 @@ public class MinecraftLocale { Map.Entry entry = localeIterator.next(); langMap.put(entry.getKey(), entry.getValue().asText()); } - localeStream.close(); return langMap; } catch (FileNotFoundException e){ throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage())); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java index fb02d7e3e..aa7a2e40f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java @@ -42,6 +42,11 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData(); + if (stoneCuttingData.getIngredient().getOptions().length == 0) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + GeyserImpl.getInstance().getLogger().debug("Received broken stone cutter recipe: " + stoneCuttingData + " " + + recipe.getIdentifier() + " " + Registries.JAVA_ITEMS.get().get(stoneCuttingData.getResult().getId()).javaIdentifier()); + } + continue; + } ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0]; List data = unsortedStonecutterData.get(ingredient.getId()); if (data == null) { diff --git a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java index 114a7b6de..cc21e2d0a 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -215,6 +215,14 @@ public class ChunkUtils { break; //No block will be a part of two classes } } + + if (BlockStateValues.isUpperDoor(blockState)) { + // Update the lower door block as Bedrock client doesn't like door to be closed from the top + // See https://github.com/GeyserMC/Geyser/issues/4358 + Vector3i belowDoorPosition = position.sub(0, 1, 0); + int belowDoorBlockState = session.getGeyser().getWorldManager().getBlockAt(session, belowDoorPosition.getX(), belowDoorPosition.getY(), belowDoorPosition.getZ()); + updateBlock(session, belowDoorBlockState, belowDoorPosition); + } } public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ, boolean forceUpdate) { diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 68753c009..583e15c23 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -270,7 +270,7 @@ public final class EntityUtils { return switch (gamemode) { case CREATIVE -> GameType.CREATIVE; case ADVENTURE -> GameType.ADVENTURE; - case SPECTATOR -> GameType.SPECTATOR; + case SPECTATOR -> GameType.SURVIVAL_VIEWER; default -> GameType.SURVIVAL; }; } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 6f282dbf5..912d51459 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -30,6 +30,9 @@ bedrock: # How much to compress network traffic to the Bedrock client. The higher the number, the more CPU usage used, but # the smaller the bandwidth used. Does not have any effect below -1 or above 9. Set to -1 to disable. compression-level: 6 + # The port to broadcast to Bedrock clients with the MOTD that they should use to connect to the server. + # DO NOT uncomment and change this unless Geyser runs on a different internal port than the one that is used to connect. + # broadcast-port: 19132 # Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy # in front of your Geyser instance. enable-proxy-protocol: false diff --git a/gradle.properties b/gradle.properties index b6425f76d..93db310ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ org.gradle.vfs.watch=false group=org.geysermc id=geyser -version=2.2.0-SNAPSHOT +version=2.2.1-SNAPSHOT description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers. \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c71aa715f..72e34e2c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ protocol-connection = "3.0.0.Beta1-20231206.150507-113" raknet = "1.0.0.CR1-20231206.145325-12" blockstateupdater="1.20.50-20231106.161340-1" mcauthlib = "d9d773e" -mcprotocollib = "1.20.4-2-20231219.074138-2" +mcprotocollib = "1.20.4-2-20240116.220521-7" adventure = "4.14.0" adventure-platform = "4.3.0" junit = "5.9.2" @@ -23,7 +23,7 @@ log4j = "2.20.0" jline = "3.21.0" terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" -viaversion = "4.0.0" +viaversion = "4.9.2" adapters = "1.11-SNAPSHOT" commodore = "2.2" bungeecord = "a7c6ede"