From 00016ba4e1fa4098862aeebc8a66cc1979d7d7ff Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Wed, 7 May 2025 23:49:33 +0100 Subject: [PATCH 01/11] Validate handshake packet length early --- .../proxy/connection/MinecraftConnection.java | 6 ++ .../backend/BackendPlaySessionHandler.java | 2 + .../backend/ConfigSessionHandler.java | 2 + .../network/BackendChannelInitializer.java | 2 +- .../network/ServerChannelInitializer.java | 2 +- .../netty/MinecraftVarintFrameDecoder.java | 81 +++++++++++++++++++ .../protocol/packet/HandshakePacket.java | 12 +++ .../server/VelocityRegisteredServer.java | 2 +- 8 files changed, 106 insertions(+), 3 deletions(-) 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 128d5c37..7c89f659 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 @@ -47,6 +47,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; @@ -149,6 +150,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/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/netty/MinecraftVarintFrameDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java index 84f35381..8215a787 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,10 +19,16 @@ 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; /** @@ -30,10 +36,31 @@ import java.util.List; */ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { + 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 +89,38 @@ 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 = ProtocolUtils.readVarInt(in); + 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) { @@ -141,4 +200,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/HandshakePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java index 2edaed9e..493dfa5d 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 270; + } } 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() From 063065b21adfdbdf7ff9b65fc03c65a28f904129 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Thu, 8 May 2025 10:55:16 +0200 Subject: [PATCH 02/11] fix: adventure 4.21.0 adaptation (#1569) --- .../java/com/velocitypowered/proxy/protocol/ProtocolUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 c94f1ed1..f637b4b9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -64,6 +64,7 @@ public enum ProtocolUtils { // before 1.16 .value(JSONOptions.EMIT_RGB, Boolean.FALSE) .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) @@ -79,7 +80,7 @@ public enum ProtocolUtils { // 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_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) From 1ad1f3b215a27bb0d05941b9d643eb7344737684 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 8 May 2025 12:30:55 +0200 Subject: [PATCH 03/11] Use pooled netty allocator instead of default adaptive allocator (#1570) --- .../velocitypowered/proxy/network/ConnectionManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 7b724f61..777cb39a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -29,6 +29,7 @@ import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver; import com.velocitypowered.proxy.protocol.netty.GameSpyQueryHandler; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -116,6 +117,11 @@ public final class ConnectionManager { bootstrap.group(this.bossGroup, this.workerGroup); } + // Restore allocator used before Netty 4.2 due to oom issues with the adaptive allocator + if (System.getProperty("io.netty.allocator.type") == null) { + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + } + final int binds = server.getConfiguration().isEnableReusePort() ? ((MultithreadEventExecutorGroup) this.workerGroup).executorCount() : 1; From dc659538d34393036a3a155d98aae6496bed3825 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Fri, 9 May 2025 20:19:29 +0100 Subject: [PATCH 04/11] Set netty allocator earlier and more globally --- proxy/src/main/java/com/velocitypowered/proxy/Velocity.java | 5 +++++ .../velocitypowered/proxy/network/ConnectionManager.java | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) 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/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 777cb39a..7b724f61 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -29,7 +29,6 @@ import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver; import com.velocitypowered.proxy.protocol.netty.GameSpyQueryHandler; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -117,11 +116,6 @@ public final class ConnectionManager { bootstrap.group(this.bossGroup, this.workerGroup); } - // Restore allocator used before Netty 4.2 due to oom issues with the adaptive allocator - if (System.getProperty("io.netty.allocator.type") == null) { - bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); - } - final int binds = server.getConfiguration().isEnableReusePort() ? ((MultithreadEventExecutorGroup) this.workerGroup).executorCount() : 1; From e13c8c340f242d270b16ec6931d1ba94a9e8f1f3 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Fri, 9 May 2025 22:17:04 +0100 Subject: [PATCH 05/11] Increase limit to account for strings being strings --- .../velocitypowered/proxy/protocol/packet/HandshakePacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 493dfa5d..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 @@ -116,6 +116,6 @@ public class HandshakePacket implements MinecraftPacket { @Override public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 270; + return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3); } } From 678c7aa3a42aaf64b8b1b7df75e787cbec9fe2ad Mon Sep 17 00:00:00 2001 From: Kezz Date: Wed, 21 May 2025 18:06:26 +0100 Subject: [PATCH 06/11] Modern-ify Adventure uses and fix bug in TranslatableMapper (#1575) * fix: Don't ignore the player's locale in message translation * feature: Use PointersSupplier to save constructing a Pointers instance for every player * fix: Don't use a custom implementation of Identity for players We don't need to carry about this object for every player. * chore: Stop using deprecated TranslationRegistry * fix: Simplify TranslatableMapper and fix bugs - The fallback string is not intended to be translated, so don't do that. - Check if the string can be translated in the default locale before using the closest mapper as devs may have their own strings. - Remove the hardcoded check for TranslationRegistry instance as devs (and us now) can use non-TranslationRegistry translator instances. --- .../velocitypowered/proxy/VelocityServer.java | 7 +-- .../connection/client/ConnectedPlayer.java | 44 +++++++++---------- .../proxy/util/TranslatableMapper.java | 25 ++--------- 3 files changed, 29 insertions(+), 47 deletions(-) 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/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 22e1dc9c..e1f9df8e 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; @@ -152,7 +153,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, 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/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)); } } } From 8fea43d6ba2271b71635bb15431aedc30e929316 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 22 May 2025 13:12:50 +0100 Subject: [PATCH 07/11] Add some means to quickly overlook deframing issues --- .../protocol/netty/MinecraftVarintFrameDecoder.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 8215a787..2d3a2de4 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 @@ -30,12 +30,15 @@ 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."); @@ -131,6 +134,15 @@ 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()); + } + } + /** * Reads a VarInt from the buffer of up to 21 bits in size. * From 5eb83760cd24eedb4b26dc6765a8ebbe20902355 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 22 May 2025 14:26:48 +0100 Subject: [PATCH 08/11] Attempt to improve partial read situations during early connections --- .../proxy/protocol/netty/MinecraftVarintFrameDecoder.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 2d3a2de4..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 @@ -98,7 +98,12 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION); final int index = in.readerIndex(); - final int packetId = ProtocolUtils.readVarInt(in); + 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); @@ -141,6 +146,7 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { .withThrowable(cause) .log("Exception caught while decoding frame for {}", ctx.channel().remoteAddress()); } + super.exceptionCaught(ctx, cause); } /** From 5e20ec19ff05258d64fe0662d01e18ec20df5b9f Mon Sep 17 00:00:00 2001 From: Alex <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Fri, 23 May 2025 15:23:38 +0200 Subject: [PATCH 09/11] Stabilize and expose suggestions API (#1406) * Expose suggestions API * Improve javadoc of suggestions api --- .../api/command/CommandManager.java | 23 +++++++++++++++++++ .../proxy/command/VelocityCommandManager.java | 17 ++------------ 2 files changed, 25 insertions(+), 15 deletions(-) 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/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"); From 8c8162dbf6f37d5f096d69c69e23977bd1ac3957 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 24 May 2025 18:28:39 +0100 Subject: [PATCH 10/11] Discard known packs if we don't have a target --- proxy/build.gradle.kts | 1 + .../proxy/connection/client/ClientConfigSessionHandler.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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/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; From 21ecd344baedb5c88d7f8464e16431ce77fdbd91 Mon Sep 17 00:00:00 2001 From: AR <1516775492@qq.com> Date: Mon, 26 May 2025 04:24:21 +0800 Subject: [PATCH 11/11] Fix HoverEvent.showEntity() in protocol versions prior to 1.21.5 (#1578) --- .../java/com/velocitypowered/proxy/protocol/ProtocolUtils.java | 2 ++ 1 file changed, 2 insertions(+) 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 f637b4b9..3fa52378 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -81,6 +81,7 @@ public enum ProtocolUtils { .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) // before 1.20.3 .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) @@ -97,6 +98,7 @@ public enum ProtocolUtils { .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)