diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index ad389373..c36ca927 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -7,8 +7,10 @@ package com.velocitypowered.api.command; +import com.mojang.brigadier.suggestion.Suggestions; import com.velocitypowered.api.event.command.CommandExecuteEvent; import java.util.Collection; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.Nullable; @@ -116,6 +118,27 @@ public interface CommandManager { */ CompletableFuture executeImmediatelyAsync(CommandSource source, String cmdLine); + /** + * Asynchronously collects suggestions to fill in the given command {@code cmdLine}. + * Returns only the raw completion suggestions without tooltips. + * + * @param source the source to execute the command for + * @param cmdLine the partially completed command + * @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty + */ + CompletableFuture> offerSuggestions(CommandSource source, String cmdLine); + + /** + * Asynchronously collects suggestions to fill in the given command {@code cmdLine}. + * Returns the brigadier {@link Suggestions} with tooltips for each result. + * + * @param source the source to execute the command for + * @param cmdLine the partially completed command + * @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly + * empty + */ + CompletableFuture offerBrigadierSuggestions(CommandSource source, String cmdLine); + /** * Returns an immutable collection of the case-insensitive aliases registered * on this manager. diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index 346a43c7..5715b021 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -90,7 +90,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_21(767, "1.21", "1.21.1"), MINECRAFT_1_21_2(768, "1.21.2", "1.21.3"), MINECRAFT_1_21_4(769, "1.21.4"), - MINECRAFT_1_21_5(770, /*1073742067,*/ "1.21.5"); + MINECRAFT_1_21_5(770, "1.21.5"), + MINECRAFT_1_21_6(771, "1.21.6"); private static final int SNAPSHOT_BIT = 30; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05731bb2..0f9ed2a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ configurate3 = "3.7.3" configurate4 = "4.1.2" flare = "2.0.1" -log4j = "2.24.1" -netty = "4.2.0.Final" +log4j = "2.24.3" +netty = "4.2.1.Final" [plugins] indra-publishing = "net.kyori.indra.publishing:2.0.6" @@ -11,10 +11,10 @@ shadow = "io.github.goooler.shadow:8.1.5" spotless = "com.diffplug.spotless:6.25.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.19.0" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.19.0" +adventure-bom = "net.kyori:adventure-bom:4.21.0" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.21.0" adventure-facet = "net.kyori:adventure-platform-facet:4.3.4" -asm = "org.ow2.asm:asm:9.7.1" +asm = "org.ow2.asm:asm:9.8" auto-service = "com.google.auto.service:auto-service:1.0.1" auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.0.1" brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT" @@ -33,7 +33,7 @@ disruptor = "com.lmax:disruptor:4.0.0" fastutil = "it.unimi.dsi:fastutil:8.5.15" flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" } -jline = "org.jline:jline-terminal-jansi:3.27.1" +jline = "org.jline:jline-terminal-jansi:3.30.2" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" junit = "org.junit.jupiter:junit-jupiter:5.10.2" jspecify = "org.jspecify:jspecify:0.3.0" @@ -56,7 +56,7 @@ netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" } netty-transport-native-iouring = { module = "io.netty:netty-transport-native-io_uring", version.ref = "netty" } nightconfig = "com.electronwill.night-config:toml:3.6.7" -slf4j = "org.slf4j:slf4j-api:2.0.12" +slf4j = "org.slf4j:slf4j-api:2.0.17" snakeyaml = "org.yaml:snakeyaml:1.33" spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3" terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0" diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index b3c5892f..f345f232 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -100,6 +100,7 @@ tasks { runShadow { workingDir = file("run").also(File::mkdirs) standardInput = System.`in` + jvmArgs("-Dvelocity.packet-decode-logging=true") } named("run") { workingDir = file("run").also(File::mkdirs) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index 0ec8622d..a6fc850b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -47,6 +47,11 @@ public class Velocity { System.setProperty("io.netty.native.workdir", System.getProperty("velocity.natives-tmpdir")); } + // Restore allocator used before Netty 4.2 due to oom issues with the adaptive allocator + if (System.getProperty("io.netty.allocator.type") == null) { + System.setProperty("io.netty.allocator.type", "pooled"); + } + // Disable the resource leak detector by default as it reduces performance. Allow the user to // override this if desired. if (!VelocityProperties.hasProperty("io.netty.leakDetection.level")) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index e616fb4d..c3e8d472 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -81,6 +81,7 @@ import java.net.http.HttpClient; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyPair; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -104,7 +105,7 @@ import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.translation.GlobalTranslator; -import net.kyori.adventure.translation.TranslationRegistry; +import net.kyori.adventure.translation.TranslationStore; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bstats.MetricsBase; @@ -337,8 +338,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { } private void registerTranslations() { - final TranslationRegistry translationRegistry = TranslationRegistry - .create(Key.key("velocity", "translations")); + final TranslationStore.StringBased translationRegistry = + TranslationStore.messageFormat(Key.key("velocity", "translations")); translationRegistry.defaultLocale(Locale.US); try { ResourceUtils.visitResources(VelocityServer.class, path -> { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index b4b0e850..21f82f34 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -300,27 +300,14 @@ public class VelocityCommandManager implements CommandManager { ); } - /** - * Returns suggestions to fill in the given command. - * - * @param source the source to execute the command for - * @param cmdLine the partially completed command - * @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty - */ + @Override public CompletableFuture> offerSuggestions(final CommandSource source, final String cmdLine) { return offerBrigadierSuggestions(source, cmdLine) .thenApply(suggestions -> Lists.transform(suggestions.getList(), Suggestion::getText)); } - /** - * Returns suggestions to fill in the given command. - * - * @param source the source to execute the command for - * @param cmdLine the partially completed command - * @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly - * empty - */ + @Override public CompletableFuture offerBrigadierSuggestions( final CommandSource source, final String cmdLine) { Preconditions.checkNotNull(source, "source"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index 3f34a54c..3d7ed448 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -46,6 +46,7 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler; import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler; @@ -368,6 +369,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { ensureInEventLoop(); this.state = state; + final MinecraftVarintFrameDecoder frameDecoder = this.channel.pipeline() + .get(MinecraftVarintFrameDecoder.class); + if (frameDecoder != null) { + frameDecoder.setState(state); + } // If the connection is LEGACY (<1.6), the decoder and encoder are not set. final MinecraftEncoder minecraftEncoder = this.channel.pipeline() .get(MinecraftEncoder.class); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 98b1b3e6..7b3e733f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -49,6 +49,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; @@ -151,6 +152,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { MinecraftConnection smc = serverConn.ensureConnected(); smc.setAutoReading(false); // Even when not auto reading messages are still decoded. Decode them with the correct state + smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.CONFIG); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.CONFIG); serverConn.getPlayer().switchToConfigState(); return true; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 1860093d..fb0e48d1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -40,6 +40,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; @@ -232,6 +233,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { final ConnectedPlayer player = serverConn.getPlayer(); final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); + smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.PLAY); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); //noinspection DataFlowIssue configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index a7143462..dc033f57 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -170,7 +170,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(KnownPacksPacket packet) { callConfigurationEvent().thenRun(() -> { - player.getConnectionInFlightOrConnectedServer().ensureConnected().write(packet); + VelocityServerConnection targetServer = + player.getConnectionInFlightOrConnectedServer(); + if (targetServer != null) { + targetServer.ensureConnected().write(packet); + } }).exceptionally(ex -> { logger.error("Error forwarding known packs response to backend:", ex); return null; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 22e1dc9c..1fb3884b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -123,6 +123,7 @@ import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.platform.facet.FacetPointers; import net.kyori.adventure.platform.facet.FacetPointers.Type; import net.kyori.adventure.pointer.Pointers; +import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.resource.ResourcePackInfoLike; import net.kyori.adventure.resource.ResourcePackRequest; import net.kyori.adventure.resource.ResourcePackRequestLike; @@ -145,14 +146,23 @@ import org.jetbrains.annotations.NotNull; public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable, VelocityInboundConnection { - public static final int MAX_CLIENTSIDE_PLUGIN_CHANNELS = 1024; + public static final int MAX_CLIENTSIDE_PLUGIN_CHANNELS = Integer.getInteger("velocity.max-clientside-plugin-channels", 1024); private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build(); static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; private static final ComponentLogger logger = ComponentLogger.logger(ConnectedPlayer.class); - private final Identity identity = new IdentityImpl(); + private static final @NotNull PointersSupplier POINTERS_SUPPLIER = + PointersSupplier.builder() + .resolving(Identity.UUID, Player::getUniqueId) + .resolving(Identity.NAME, Player::getUsername) + .resolving(Identity.DISPLAY_NAME, player -> Component.text(player.getUsername())) + .resolving(Identity.LOCALE, Player::getEffectiveLocale) + .resolving(PermissionChecker.POINTER, Player::getPermissionChecker) + .resolving(FacetPointers.TYPE, player -> Type.PLAYER) + .build(); + /** * The actual Minecraft connection. This is actually a wrapper object around the Netty channel. */ @@ -181,14 +191,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, private final ResourcePackHandler resourcePackHandler; private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this); - private final @NotNull Pointers pointers = - Player.super.pointers().toBuilder() - .withDynamic(Identity.UUID, this::getUniqueId) - .withDynamic(Identity.NAME, this::getUsername) - .withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername())) - .withDynamic(Identity.LOCALE, this::getEffectiveLocale) - .withStatic(PermissionChecker.POINTER, getPermissionChecker()) - .withStatic(FacetPointers.TYPE, Type.PLAYER).build(); private @Nullable String clientBrand; private @Nullable Locale effectiveLocale; private final @Nullable IdentifiedKey playerKey; @@ -257,7 +259,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public @NonNull Identity identity() { - return this.identity; + return Identity.identity(this.getUniqueId()); } @Override @@ -363,7 +365,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public @NotNull Pointers pointers() { - return this.pointers; + return POINTERS_SUPPLIER.view(this); } @Override @@ -396,14 +398,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } /** - * Translates the message in the user's locale. + * Translates the message in the user's locale, falling back to the default locale if not set. * * @param message the message to translate * @return the translated message */ public Component translateMessage(Component message) { - Locale locale = ClosestLocaleMatcher.INSTANCE - .lookupClosest(getEffectiveLocale() == null ? Locale.getDefault() : getEffectiveLocale()); + Locale locale = this.getEffectiveLocale(); + if (locale == null && settings != null) { + locale = settings.getLocale(); + } + if (locale == null) { + locale = Locale.getDefault(); + } + locale = ClosestLocaleMatcher.INSTANCE.lookupClosest(locale); return GlobalTranslator.render(message, locale); } @@ -1361,14 +1369,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, return playerKey; } - private class IdentityImpl implements Identity { - - @Override - public @NonNull UUID uuid() { - return ConnectedPlayer.this.getUniqueId(); - } - } - @Override public ProtocolState getProtocolState() { return connection.getState().toProtocolState(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java b/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java index d60c6477..236394fb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java @@ -51,7 +51,7 @@ public class BackendChannelInitializer extends ChannelInitializer { @Override protected void initChannel(Channel ch) { ch.pipeline() - .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.CLIENTBOUND)) .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.MILLISECONDS)) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java index ef8e6b1c..0c22dcce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java @@ -58,7 +58,7 @@ public class ServerChannelInitializer extends ChannelInitializer { protected void initChannel(final Channel ch) { ch.pipeline() .addLast(LEGACY_PING_DECODER, new LegacyPingDecoder()) - .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.SERVERBOUND)) .addLast(READ_TIMEOUT, new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(), TimeUnit.MILLISECONDS)) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index ca63c1e5..3fa52378 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -47,7 +47,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONOptions; import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer; -import net.kyori.option.OptionState; +import net.kyori.option.OptionSchema; /** * Utilities for writing and reading data in the Minecraft protocol. @@ -60,10 +60,11 @@ public enum ProtocolUtils { .downsampleColors() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( - OptionState.optionState() + OptionSchema.globalSchema().stateBuilder() // before 1.16 .value(JSONOptions.EMIT_RGB, Boolean.FALSE) - .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.LEGACY_ONLY) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.VALUE_FIELD) + .value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE) // before 1.20.3 .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) @@ -75,10 +76,12 @@ public enum ProtocolUtils { GsonComponentSerializer.builder() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( - OptionState.optionState() + OptionSchema.globalSchema().stateBuilder() // after 1.16 .value(JSONOptions.EMIT_RGB, Boolean.TRUE) - .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE) + .value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE) + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true) // before 1.20.3 .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) @@ -86,17 +89,37 @@ public enum ProtocolUtils { .build() ) .build(); + private static final GsonComponentSerializer PRE_1_21_5_SERIALIZER = + GsonComponentSerializer.builder() + .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) + .options( + OptionSchema.globalSchema().stateBuilder() + // after 1.16 + .value(JSONOptions.EMIT_RGB, Boolean.TRUE) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE) + .value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE) + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true) + // after 1.20.3 + .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE) + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE) + .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE) + .build() + ) + .build(); private static final GsonComponentSerializer MODERN_SERIALIZER = GsonComponentSerializer.builder() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .options( - OptionState.optionState() + OptionSchema.globalSchema().stateBuilder() // after 1.16 .value(JSONOptions.EMIT_RGB, Boolean.TRUE) - .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY) + .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.SNAKE_CASE) + .value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.SNAKE_CASE) // after 1.20.3 .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE) + // after 1.21.5 + .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, Boolean.FALSE) .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE) .build() ) @@ -713,9 +736,12 @@ public enum ProtocolUtils { * @return the appropriate {@link GsonComponentSerializer} */ public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) { return MODERN_SERIALIZER; } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + return PRE_1_21_5_SERIALIZER; + } if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { return PRE_1_20_3_SERIALIZER; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 47f7ae85..27abbfed 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -40,6 +40,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -257,7 +258,8 @@ public enum StateRegistry { map(0x09, MINECRAFT_1_19_4, false), map(0x0A, MINECRAFT_1_20_2, false), map(0x0B, MINECRAFT_1_20_5, false), - map(0x0D, MINECRAFT_1_21_2, false)); + map(0x0D, MINECRAFT_1_21_2, false), + map(0x0E, MINECRAFT_1_21_6, false)); serverbound.register( LegacyChatPacket.class, LegacyChatPacket::new, @@ -270,7 +272,8 @@ public enum StateRegistry { ChatAcknowledgementPacket.class, ChatAcknowledgementPacket::new, map(0x03, MINECRAFT_1_19_3, false), - map(0x04, MINECRAFT_1_21_2, false)); + map(0x04, MINECRAFT_1_21_2, false), + map(0x05, MINECRAFT_1_21_6, false)); serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new, map(0x03, MINECRAFT_1_19, false), map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); @@ -280,16 +283,19 @@ public enum StateRegistry { serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new, map(0x04, MINECRAFT_1_19_3, false), map(0x05, MINECRAFT_1_20_5, false), - map(0x06, MINECRAFT_1_21_2, false)); + map(0x06, MINECRAFT_1_21_2, false), + map(0x07, MINECRAFT_1_21_6, false)); serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new, map(0x04, MINECRAFT_1_20_5, false), - map(0x05, MINECRAFT_1_21_2, false)); + map(0x05, MINECRAFT_1_21_2, false), + map(0x06, MINECRAFT_1_21_6, false)); serverbound.register( SessionPlayerChatPacket.class, SessionPlayerChatPacket::new, map(0x05, MINECRAFT_1_19_3, false), map(0x06, MINECRAFT_1_20_5, false), - map(0x07, MINECRAFT_1_21_2, false)); + map(0x07, MINECRAFT_1_21_2, false), + map(0x08, MINECRAFT_1_21_6, false)); serverbound.register( ClientSettingsPacket.class, ClientSettingsPacket::new, @@ -304,11 +310,13 @@ public enum StateRegistry { map(0x08, MINECRAFT_1_19_4, false), map(0x09, MINECRAFT_1_20_2, false), map(0x0A, MINECRAFT_1_20_5, false), - map(0x0C, MINECRAFT_1_21_2, false)); + map(0x0C, MINECRAFT_1_21_2, false), + map(0x0D, MINECRAFT_1_21_6, false)); serverbound.register( ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, map(0x11, MINECRAFT_1_20_5, false), - map(0x13, MINECRAFT_1_21_2, false)); + map(0x13, MINECRAFT_1_21_2, false), + map(0x14, MINECRAFT_1_21_6, false)); serverbound.register( PluginMessagePacket.class, PluginMessagePacket::new, @@ -326,7 +334,8 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_20_2, false), map(0x10, MINECRAFT_1_20_3, false), map(0x12, MINECRAFT_1_20_5, false), - map(0x14, MINECRAFT_1_21_2, false)); + map(0x14, MINECRAFT_1_21_2, false), + map(0x15, MINECRAFT_1_21_6, false)); serverbound.register( KeepAlivePacket.class, KeepAlivePacket::new, @@ -345,7 +354,8 @@ public enum StateRegistry { map(0x14, MINECRAFT_1_20_2, false), map(0x15, MINECRAFT_1_20_3, false), map(0x18, MINECRAFT_1_20_5, false), - map(0x1A, MINECRAFT_1_21_2, false)); + map(0x1A, MINECRAFT_1_21_2, false), + map(0x1B, MINECRAFT_1_21_6, false)); serverbound.register( ResourcePackResponsePacket.class, ResourcePackResponsePacket::new, @@ -362,12 +372,14 @@ public enum StateRegistry { map(0x28, MINECRAFT_1_20_3, false), map(0x2B, MINECRAFT_1_20_5, false), map(0x2D, MINECRAFT_1_21_2, false), - map(0x2F, MINECRAFT_1_21_4, false)); + map(0x2F, MINECRAFT_1_21_4, false), + map(0x30, MINECRAFT_1_21_6, false)); serverbound.register( FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, map(0x0B, MINECRAFT_1_20_2, false), map(0x0C, MINECRAFT_1_20_5, false), - map(0x0E, MINECRAFT_1_21_2, false)); + map(0x0E, MINECRAFT_1_21_2, false), + map(0x0F, MINECRAFT_1_21_6, false)); clientbound.register( BossBarPacket.class, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java index 5321b6ac..1fe38e50 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java @@ -39,6 +39,7 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { private static final int UNCOMPRESSED_CAP = Boolean.getBoolean("velocity.increased-compression-cap") ? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE; + private static final boolean SKIP_COMPRESSION_VALIDATION = Boolean.getBoolean("velocity.skip-uncompressed-packet-size-validation"); private int threshold; private final VelocityCompressor compressor; @@ -52,9 +53,11 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { int claimedUncompressedSize = ProtocolUtils.readVarInt(in); if (claimedUncompressedSize == 0) { - int actualUncompressedSize = in.readableBytes(); - checkFrame(actualUncompressedSize < threshold, "Actual uncompressed size %s is greater than" - + " threshold %s", actualUncompressedSize, threshold); + if (!SKIP_COMPRESSION_VALIDATION) { + int actualUncompressedSize = in.readableBytes(); + checkFrame(actualUncompressedSize < threshold, "Actual uncompressed size %s is greater than" + + " threshold %s", actualUncompressedSize, threshold); + } // This message is not compressed. out.add(in.retain()); return; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index d75cb46c..5389de73 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -135,7 +135,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { private String getExtraConnectionDetail(int packetId) { return "Direction " + direction + " Protocol " + registry.version + " State " + state - + " ID " + Integer.toHexString(packetId); + + " ID 0x" + Integer.toHexString(packetId); } public void setProtocolVersion(ProtocolVersion protocolVersion) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java index 84f35381..5450390b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java @@ -19,21 +19,51 @@ package com.velocitypowered.proxy.protocol.netty; import static io.netty.util.ByteProcessor.FIND_NON_NUL; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.util.except.QuietDecoderException; +import com.velocitypowered.proxy.util.except.QuietRuntimeException; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.CorruptedFrameException; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding. */ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { + private static final Logger LOGGER = LogManager.getLogger(MinecraftVarintFrameDecoder.class); + private static final QuietRuntimeException FRAME_DECODER_FAILED = + new QuietRuntimeException("A packet frame decoder failed. For more information, launch " + + "Velocity with -Dvelocity.packet-decode-logging=true to see more."); private static final QuietDecoderException BAD_PACKET_LENGTH = new QuietDecoderException("Bad packet length"); private static final QuietDecoderException VARINT_TOO_BIG = new QuietDecoderException("VarInt too big"); + private static final QuietDecoderException UNKNOWN_PACKET = + new QuietDecoderException("Unknown packet"); + + private final ProtocolUtils.Direction direction; + private final StateRegistry.PacketRegistry.ProtocolRegistry registry; + private StateRegistry state; + + /** + * Creates a new {@code MinecraftVarintFrameDecoder} decoding packets from the specified {@code Direction}. + * + * @param direction the direction from which we decode from + */ + public MinecraftVarintFrameDecoder(ProtocolUtils.Direction direction) { + this.direction = direction; + this.registry = StateRegistry.HANDSHAKE.getProtocolRegistry( + direction, ProtocolVersion.MINIMUM_VERSION); + this.state = StateRegistry.HANDSHAKE; + } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) @@ -62,6 +92,43 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { throw BAD_PACKET_LENGTH; } + if (length > 0) { + if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) { + StateRegistry.PacketRegistry.ProtocolRegistry registry = + state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION); + + final int index = in.readerIndex(); + final int packetId = readRawVarInt21(in); + // Index hasn't changed, we've read nothing + if (index == in.readerIndex()) { + in.resetReaderIndex(); + return; + } + final int payloadLength = length - ProtocolUtils.varIntBytes(packetId); + + MinecraftPacket packet = registry.createPacket(packetId); + + // We handle every packet in this phase, if you said something we don't know, something is really wrong + if (packet == null) { + throw UNKNOWN_PACKET; + } + + // We 'technically' have the incoming bytes of a payload here, and so, these can actually parse + // the packet if needed, so, we'll take advantage of the existing methods + int expectedMinLen = packet.expectedMinLength(in, direction, registry.version); + int expectedMaxLen = packet.expectedMaxLength(in, direction, registry.version); + if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) { + throw handleOverflow(packet, expectedMaxLen, in.readableBytes()); + } + if (payloadLength < expectedMinLen) { + throw handleUnderflow(packet, expectedMaxLen, in.readableBytes()); + } + + + in.readerIndex(index); + } + } + // note that zero-length packets are ignored if (length > 0) { if (in.readableBytes() < length) { @@ -72,6 +139,16 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { } } + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (MinecraftDecoder.DEBUG) { + LOGGER.atWarn() + .withThrowable(cause) + .log("Exception caught while decoding frame for {}", ctx.channel().remoteAddress()); + } + super.exceptionCaught(ctx, cause); + } + /** * Reads a VarInt from the buffer of up to 21 bits in size. * @@ -141,4 +218,26 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { } return result | (tmp & 0x7F) << 14; } + + private Exception handleOverflow(MinecraftPacket packet, int expected, int actual) { + if (MinecraftDecoder.DEBUG) { + return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too " + + "big (expected " + expected + " bytes, got " + actual + " bytes)"); + } else { + return FRAME_DECODER_FAILED; + } + } + + private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) { + if (MinecraftDecoder.DEBUG) { + return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too " + + "small (expected " + expected + " bytes, got " + actual + " bytes)"); + } else { + return FRAME_DECODER_FAILED; + } + } + + public void setState(StateRegistry stateRegistry) { + this.state = stateRegistry; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java index 1687f902..08fe23b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java @@ -50,12 +50,14 @@ import java.util.Deque; import java.util.Iterator; import java.util.Queue; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; public class AvailableCommandsPacket implements MinecraftPacket { private static final Command PLACEHOLDER_COMMAND = source -> 0; + private static final Predicate PLACEHOLDER_REQUIREMENT = source -> true; private static final byte NODE_TYPE_ROOT = 0x00; private static final byte NODE_TYPE_LITERAL = 0x01; @@ -65,6 +67,7 @@ public class AvailableCommandsPacket implements MinecraftPacket { private static final byte FLAG_EXECUTABLE = 0x04; private static final byte FLAG_IS_REDIRECT = 0x08; private static final byte FLAG_HAS_SUGGESTIONS = 0x10; + private static final byte FLAG_IS_RESTRICTED = 0x20; private @MonotonicNonNull RootCommandNode rootNode; @@ -146,6 +149,9 @@ public class AvailableCommandsPacket implements MinecraftPacket { if (node.getCommand() != null) { flags |= FLAG_EXECUTABLE; } + if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) { + flags |= FLAG_IS_RESTRICTED; + } if (node instanceof LiteralCommandNode) { flags |= NODE_TYPE_LITERAL; @@ -289,6 +295,11 @@ public class AvailableCommandsPacket implements MinecraftPacket { args.executes(PLACEHOLDER_COMMAND); } + // If restricted, add empty requirement + if ((flags & FLAG_IS_RESTRICTED) != 0) { + args.requires(PLACEHOLDER_REQUIREMENT); + } + this.built = args.build(); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java index 2edaed9e..9eb7a93e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java @@ -106,4 +106,16 @@ public class HandshakePacket implements MinecraftPacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + @Override + public int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return 7; + } + + @Override + public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 065de7b4..197fddf3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -23,6 +23,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; @@ -164,6 +165,7 @@ public class ArgumentPropertyRegistry { return i; } } + throw new IllegalArgumentException("Argument type identifier " + id + " unknown."); } else { String identifier = ProtocolUtils.readString(buf); for (ArgumentIdentifier i : byIdentifier.keySet()) { @@ -207,68 +209,79 @@ public class ArgumentPropertyRegistry { empty(id("minecraft:item_stack", mapSet(MINECRAFT_1_19, 14))); empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15))); empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16))); - empty(id("minecraft:component", mapSet(MINECRAFT_1_19, 17))); - empty(id("minecraft:style", mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3 - empty(id("minecraft:message", mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18))); - empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14 - empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14 - empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21))); - empty(id("minecraft:objective", mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22))); - empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23))); - empty(id("minecraft:operation", mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24))); - empty(id("minecraft:particle", mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25))); - empty(id("minecraft:angle", mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 - empty(id("minecraft:rotation", mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27))); - empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28))); - empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), ByteArgumentPropertySerializer.BYTE); - empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30))); - empty(id("minecraft:team", mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31))); - empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32))); - empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5 - empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34), mapSet(MINECRAFT_1_19, 33))); + empty(id("minecraft:component", mapSet(MINECRAFT_1_21_6, 18), mapSet(MINECRAFT_1_19, 17))); + empty(id("minecraft:style", mapSet(MINECRAFT_1_21_6, 19), mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3 + empty(id("minecraft:message", mapSet(MINECRAFT_1_21_6, 20), mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18))); + empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_21_6, 21), mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14 + empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_21_6, 22), mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14 + empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_21_6, 23), mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21))); + empty(id("minecraft:objective", mapSet(MINECRAFT_1_21_6, 24), mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22))); + empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_21_6, 25), mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23))); + empty(id("minecraft:operation", mapSet(MINECRAFT_1_21_6, 26), mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24))); + empty(id("minecraft:particle", mapSet(MINECRAFT_1_21_6, 27), mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25))); + empty(id("minecraft:angle", mapSet(MINECRAFT_1_21_6, 28), mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 + empty(id("minecraft:rotation", mapSet(MINECRAFT_1_21_6, 29), mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27))); + empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_21_6, 30), mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28))); + empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_21_6, 31), mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), + ByteArgumentPropertySerializer.BYTE); + empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_21_6, 32), mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30))); + empty(id("minecraft:team", mapSet(MINECRAFT_1_21_6, 33), mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31))); + empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_21_6, 34), mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32))); + empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_21_6, 35), mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5 + empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_21_6, 36), mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34), + mapSet(MINECRAFT_1_19, 33))); empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34))); - empty(id("minecraft:function", mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35), mapSet(MINECRAFT_1_19_3, 34), - mapSet(MINECRAFT_1_19, 35))); - empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36), mapSet(MINECRAFT_1_19_3, 35), - mapSet(MINECRAFT_1_19, 36))); - empty(id("minecraft:int_range", mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37), mapSet(MINECRAFT_1_19_3, 36), - mapSet(MINECRAFT_1_19, 37))); - empty(id("minecraft:float_range", mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38), mapSet(MINECRAFT_1_19_3, 37), - mapSet(MINECRAFT_1_19, 38))); + empty(id("minecraft:function", mapSet(MINECRAFT_1_21_6, 37), mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35), + mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35))); + empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_21_6, 38), mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36), + mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36))); + empty(id("minecraft:int_range", mapSet(MINECRAFT_1_21_6, 39), mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37), + mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37))); + empty(id("minecraft:float_range", mapSet(MINECRAFT_1_21_6, 40), mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38), + mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38))); empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39))); empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40))); - empty(id("minecraft:dimension", mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39), mapSet(MINECRAFT_1_19_3, 38), - mapSet(MINECRAFT_1_19, 41))); - empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40), mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 + empty(id("minecraft:dimension", mapSet(MINECRAFT_1_21_6, 41), mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39), + mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41))); + empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_21_6, 42), mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40), + mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 - empty(id("minecraft:time", mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41), mapSet(MINECRAFT_1_19_3, 40), - mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14 + empty(id("minecraft:time", mapSet(MINECRAFT_1_21_6, 43), mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41), + mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14 - register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42), mapSet(MINECRAFT_1_19_3, 41), - mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); - register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43), mapSet(MINECRAFT_1_19_3, 42)), + register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_21_6, 44), mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42), + mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); + register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_21_6, 45), mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43), + mapSet(MINECRAFT_1_19_3, 42)), RegistryKeyArgumentList.ResourceOrTagKey.class, RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY); - register(id("minecraft:resource", mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44), mapSet(MINECRAFT_1_19_3, 43), - mapSet(MINECRAFT_1_19, 44)), + register(id("minecraft:resource", mapSet(MINECRAFT_1_21_6, 46), mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44), + mapSet(MINECRAFT_1_19_3, 43), mapSet(MINECRAFT_1_19, 44)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); - register(id("minecraft:resource_key", mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)), + register(id("minecraft:resource_key", mapSet(MINECRAFT_1_21_6, 47), mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), + mapSet(MINECRAFT_1_19_3, 44)), RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY); - register(id("minecraft:resource_selector", mapSet(MINECRAFT_1_21_5, 47)), + register(id("minecraft:resource_selector", mapSet(MINECRAFT_1_21_6, 48), mapSet(MINECRAFT_1_21_5, 47)), RegistryKeyArgumentList.ResourceSelector.class, RegistryKeyArgumentList.ResourceSelector.Serializer.REGISTRY); - empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_21_5, 48), mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19 - empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_21_5, 49), mapSet(MINECRAFT_1_20_5, 48), mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19 - empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 + empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_21_6, 49), mapSet(MINECRAFT_1_21_5, 48), mapSet(MINECRAFT_1_20_5, 47), + mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19 + empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_21_6, 50), mapSet(MINECRAFT_1_21_5, 49), mapSet(MINECRAFT_1_20_5, 48), + mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19 + empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49), + mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 - empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), - mapSet(MINECRAFT_1_19, 47))); // added in 1.16 + empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), + mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16 - empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50))); - empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51))); - empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52))); + empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50))); + empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51))); + empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52))); + + empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6 + empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6 // Crossstitch support register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java index 3a7d60cb..b3fb0de4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java @@ -36,7 +36,7 @@ public class KnownPacksPacket implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { final int packCount = ProtocolUtils.readVarInt(buf); - if (packCount > MAX_LENGTH_PACKS) { + if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) { throw TOO_MANY_PACKS; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index 697135e3..e48881f3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -114,7 +114,7 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud server.createBootstrap(loop).handler(new ChannelInitializer<>() { @Override protected void initChannel(Channel ch) { - ch.pipeline().addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + ch.pipeline().addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.CLIENTBOUND)) .addLast(READ_TIMEOUT, new ReadTimeoutHandler( pingOptions.getTimeout() == 0 ? server.getConfiguration().getReadTimeout() diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java index edc06761..6f30d3d1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java @@ -24,9 +24,6 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.flattener.ComponentFlattener; import net.kyori.adventure.translation.GlobalTranslator; -import net.kyori.adventure.translation.TranslationRegistry; -import net.kyori.adventure.translation.Translator; -import org.jetbrains.annotations.Nullable; /** * Velocity Translation Mapper. @@ -43,25 +40,9 @@ public enum TranslatableMapper implements BiConsumer componentConsumer ) { - for (final Translator source : GlobalTranslator.translator().sources()) { - if (source instanceof TranslationRegistry registry - && registry.contains(translatableComponent.key())) { - componentConsumer.accept(GlobalTranslator.render(translatableComponent, - ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault()))); - return; - } - } - final @Nullable String fallback = translatableComponent.fallback(); - if (fallback == null) { - return; - } - for (final Translator source : GlobalTranslator.translator().sources()) { - if (source instanceof TranslationRegistry registry && registry.contains(fallback)) { - componentConsumer.accept( - GlobalTranslator.render(Component.translatable(fallback), - ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault()))); - return; - } + final Locale locale = ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault()); + if (GlobalTranslator.translator().canTranslate(translatableComponent.key(), locale)) { + componentConsumer.accept(GlobalTranslator.render(translatableComponent, locale)); } } }