From 63674a4a91987bb4b442fb0069c01fa51eed61ba Mon Sep 17 00:00:00 2001 From: UserNugget Date: Wed, 18 Mar 2026 23:17:42 +0300 Subject: [PATCH 1/2] Log about writability changes if enabled Sometimes it can catch packet abuse --- .../login/confirmation/LoginConfirmHandler.java | 14 ++++++++++++++ .../limboapi/server/LimboSessionHandlerImpl.java | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java b/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java index adee13ac..26d8b95a 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java @@ -37,6 +37,9 @@ public class LoginConfirmHandler implements MinecraftSessionHandler { + private static final boolean BACKPRESSURE_LOG = + Boolean.getBoolean("velocity.log-server-backpressure"); + private static final MethodHandle TEARDOWN_METHOD; private final LimboAPI plugin; @@ -108,6 +111,17 @@ public void handleUnknown(ByteBuf buf) { this.connection.close(true); } + @Override + public void writabilityChanged() { + if (BACKPRESSURE_LOG) { + if (this.connection.getChannel().isWritable()) { + LimboAPI.getLogger().info("{} is writable, will auto-read", this.player); + } else { + LimboAPI.getLogger().info("{} is not writable, not auto-reading", this.player); + } + } + } + @Override public void disconnected() { try { diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java b/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java index ea8e668e..61e76037 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java @@ -68,6 +68,9 @@ public class LimboSessionHandlerImpl implements MinecraftSessionHandler { + private static final boolean BACKPRESSURE_LOG = + Boolean.getBoolean("velocity.log-server-backpressure"); + private static final MethodHandle TEARDOWN_METHOD; private final LimboAPI plugin; @@ -380,6 +383,17 @@ public void handleGeneric(MinecraftPacket packet) { this.callback.onGeneric(packet); } + @Override + public void writabilityChanged() { + if (BACKPRESSURE_LOG) { + if (this.player.getConnection().getChannel().isWritable()) { + LimboAPI.getLogger().info("{} is writable, will auto-read", this.player); + } else { + LimboAPI.getLogger().info("{} is not writable, not auto-reading", this.player); + } + } + } + private void kickTooBigPacket(String type, int length) { this.player.getConnection().closeWith(this.plugin.getPackets().getTooBigPacket()); From 9014c8aa826e77fe963a25aa2f34be58d76b8384 Mon Sep 17 00:00:00 2001 From: UserNugget Date: Wed, 18 Mar 2026 23:41:56 +0300 Subject: [PATCH 2/2] Velocity b582+ support --- .../java/net/elytrium/limboapi/LimboAPI.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java b/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java index 3da3e35e..1ce5db3c 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java +++ b/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java @@ -40,6 +40,7 @@ import com.velocitypowered.proxy.protocol.StateRegistry; 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.MinecraftVarintLengthEncoder; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -49,6 +50,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; @@ -137,6 +140,8 @@ public class LimboAPI implements LimboFactory { public static final ConcurrentHashMap INITIAL_ID = new ConcurrentHashMap<>(); + private static final MethodHandle STATE_FIELD; + private final VelocityServer server; private final Metrics.Factory metricsFactory; private final File configFile; @@ -443,12 +448,14 @@ public void inject3rdParty(Player player, MinecraftConnection connection, Channe public void setState(MinecraftConnection connection, StateRegistry stateRegistry) { connection.setState(stateRegistry); this.setEncoderState(connection, stateRegistry); + this.fixDecoderState(connection, stateRegistry); } public void setActiveSessionHandler(MinecraftConnection connection, StateRegistry stateRegistry, MinecraftSessionHandler sessionHandler) { connection.setActiveSessionHandler(stateRegistry, sessionHandler); this.setEncoderState(connection, stateRegistry); + this.fixDecoderState(connection, stateRegistry); } public void setEncoderState(MinecraftConnection connection, StateRegistry state) { @@ -474,6 +481,20 @@ public void setEncoderState(MinecraftConnection connection, StateRegistry state) } } + public void fixDecoderState(MinecraftConnection connection, StateRegistry state) { + if (state.name() == null) { // custom state + MinecraftDecoder decoder = connection.getChannel().pipeline().get(MinecraftDecoder.class); + if (decoder != null) { + try { + // Let decoder know what we're in PLAY state, or it will kick the player. + STATE_FIELD.invokeExact(decoder, StateRegistry.PLAY); + } catch (Throwable throwable) { + LimboAPI.getLogger().error("Failed to fixup decoder", throwable); + } + } + } + } + public void deject3rdParty(ChannelPipeline pipeline) { this.preparedPacketFactory.deject(pipeline); } @@ -693,4 +714,13 @@ private static void setSerializer(Serializer serializer) { public static Serializer getSerializer() { return SERIALIZER; } + + static { + try { + STATE_FIELD = MethodHandles.privateLookupIn(MinecraftDecoder.class, MethodHandles.lookup()) + .findSetter(MinecraftDecoder.class, "state", StateRegistry.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } }