From 0f959d9cb3a3f5c3a2e1c91570b9e2e054d2cc21 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sat, 30 May 2026 15:14:01 +0200 Subject: [PATCH 1/2] Snoop the backend's player list header/footer so the proxy's tracked values stay in sync with what the player actually sees --- .../backend/BackendPlaySessionHandler.java | 10 +++++ .../connection/client/ConnectedPlayer.java | 11 +++++ .../proxy/protocol/StateRegistry.java | 44 +++++++++---------- .../packet/HeaderAndFooterPacket.java | 8 ++-- 4 files changed, 47 insertions(+), 26 deletions(-) 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 dfe230e318..f1bb2c9560 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 @@ -55,6 +55,7 @@ import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; +import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; @@ -338,6 +339,15 @@ public boolean handle(TabCompleteResponsePacket packet) { return true; } + @Override + public boolean handle(HeaderAndFooterPacket packet) { + // Snoop the backend's player list header/footer so the proxy's tracked values stay in sync + // with what the player actually sees. + serverConn.getPlayer().setPlayerListHeaderAndFooterSilent( + packet.getHeader().getComponent(), packet.getFooter().getComponent()); + return false; // forward + } + @Override public boolean handle(LegacyPlayerListItemPacket packet) { serverConn.getPlayer().getTabList().processLegacy(packet); 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 47d59a7186..c0fdcb5067 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 @@ -479,6 +479,17 @@ public Component getPlayerListFooter() { return this.playerListFooter; } + /** + * Updates the tracked player list header and footer without sending a packet. + * + * @param header the new player list header + * @param footer the new player list footer + */ + public void setPlayerListHeaderAndFooterSilent(final Component header, final Component footer) { + this.playerListHeader = header; + this.playerListFooter = footer; + } + @Override public void sendPlayerListHeader(@NonNull final Component header) { this.sendPlayerListHeaderAndFooter(header, this.playerListFooter); 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 2971087685..eb7a412fcd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -623,28 +623,28 @@ public enum StateRegistry { clientbound.register( HeaderAndFooterPacket.class, HeaderAndFooterPacket::new, - map(0x47, MINECRAFT_1_8, true), - map(0x48, MINECRAFT_1_9, true), - map(0x47, MINECRAFT_1_9_4, true), - map(0x49, MINECRAFT_1_12, true), - map(0x4A, MINECRAFT_1_12_1, true), - map(0x4E, MINECRAFT_1_13, true), - map(0x53, MINECRAFT_1_14, true), - map(0x54, MINECRAFT_1_15, true), - map(0x53, MINECRAFT_1_16, true), - map(0x5E, MINECRAFT_1_17, true), - map(0x5F, MINECRAFT_1_18, true), - map(0x60, MINECRAFT_1_19, true), - map(0x63, MINECRAFT_1_19_1, true), - map(0x61, MINECRAFT_1_19_3, true), - map(0x65, MINECRAFT_1_19_4, true), - map(0x68, MINECRAFT_1_20_2, true), - map(0x6A, MINECRAFT_1_20_3, true), - map(0x6D, MINECRAFT_1_20_5, true), - map(0x74, MINECRAFT_1_21_2, true), - map(0x73, MINECRAFT_1_21_5, true), - map(0x78, MINECRAFT_1_21_9, true), - map(0x7A, MINECRAFT_26_1, true)); + map(0x47, MINECRAFT_1_8, false), + map(0x48, MINECRAFT_1_9, false), + map(0x47, MINECRAFT_1_9_4, false), + map(0x49, MINECRAFT_1_12, false), + map(0x4A, MINECRAFT_1_12_1, false), + map(0x4E, MINECRAFT_1_13, false), + map(0x53, MINECRAFT_1_14, false), + map(0x54, MINECRAFT_1_15, false), + map(0x53, MINECRAFT_1_16, false), + map(0x5E, MINECRAFT_1_17, false), + map(0x5F, MINECRAFT_1_18, false), + map(0x60, MINECRAFT_1_19, false), + map(0x63, MINECRAFT_1_19_1, false), + map(0x61, MINECRAFT_1_19_3, false), + map(0x65, MINECRAFT_1_19_4, false), + map(0x68, MINECRAFT_1_20_2, false), + map(0x6A, MINECRAFT_1_20_3, false), + map(0x6D, MINECRAFT_1_20_5, false), + map(0x74, MINECRAFT_1_21_2, false), + map(0x73, MINECRAFT_1_21_5, false), + map(0x78, MINECRAFT_1_21_9, false), + map(0x7A, MINECRAFT_26_1, false)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java index 339d026f8f..a52d2a3153 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java @@ -28,11 +28,10 @@ public class HeaderAndFooterPacket implements MinecraftPacket { - private final ComponentHolder header; - private final ComponentHolder footer; + private ComponentHolder header; + private ComponentHolder footer; public HeaderAndFooterPacket() { - throw new UnsupportedOperationException("Decode is not implemented"); } public HeaderAndFooterPacket(ComponentHolder header, ComponentHolder footer) { @@ -50,7 +49,8 @@ public ComponentHolder getFooter() { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - throw new UnsupportedOperationException("Decode is not implemented"); + header = ComponentHolder.read(buf, version); + footer = ComponentHolder.read(buf, version); } @Override From 517a396b79fddbd7393843a77b0b7c77b24876f7 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sun, 31 May 2026 13:55:09 +0200 Subject: [PATCH 2/2] Minor changes --- .../connection/backend/BackendPlaySessionHandler.java | 5 +++-- .../proxy/connection/client/ConnectedPlayer.java | 9 ++------- 2 files changed, 5 insertions(+), 9 deletions(-) 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 f1bb2c9560..96d7b4ffe3 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 @@ -344,8 +344,9 @@ public boolean handle(HeaderAndFooterPacket packet) { // Snoop the backend's player list header/footer so the proxy's tracked values stay in sync // with what the player actually sees. serverConn.getPlayer().setPlayerListHeaderAndFooterSilent( - packet.getHeader().getComponent(), packet.getFooter().getComponent()); - return false; // forward + packet.getHeader().getComponent(), + packet.getFooter().getComponent()); + return false; } @Override 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 c0fdcb5067..de11c42fbd 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 @@ -479,13 +479,8 @@ public Component getPlayerListFooter() { return this.playerListFooter; } - /** - * Updates the tracked player list header and footer without sending a packet. - * - * @param header the new player list header - * @param footer the new player list footer - */ - public void setPlayerListHeaderAndFooterSilent(final Component header, final Component footer) { + public void setPlayerListHeaderAndFooterSilent(@NonNull final Component header, + @NonNull final Component footer) { this.playerListHeader = header; this.playerListFooter = footer; }