From 0310f88dfeb85f6b5cb29bfc436c3d4acdaef96e Mon Sep 17 00:00:00 2001 From: p0t4t0sandwich Date: Tue, 28 Apr 2026 16:14:27 -0600 Subject: [PATCH 1/5] Fix missing Legacy Forge handshake token when using BungeeGuard forwarding --- .../connection/forge/legacy/LegacyForgeConnectionType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConnectionType.java index 45d19844e5..5337606071 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConnectionType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConnectionType.java @@ -38,11 +38,11 @@ public LegacyForgeConnectionType() { @Override public GameProfile addGameProfileTokensIfRequired(GameProfile original, PlayerInfoForwarding forwardingType) { - // We can't forward the FML token to the server when we are running in legacy forwarding mode, + // We can't forward the FML token to the server when we are running in legacy (or bungeeguard) forwarding mode, // since both use the "hostname" field in the handshake. We add a special property to the // profile instead, which will be ignored by non-Forge servers and can be intercepted by a // Forge coremod, such as SpongeForge. - if (forwardingType == PlayerInfoForwarding.LEGACY) { + if (forwardingType == PlayerInfoForwarding.LEGACY || forwardingType == PlayerInfoForwarding.BUNGEEGUARD) { return original.addProperty(IS_FORGE_CLIENT_PROPERTY); } From ee732ae25360b7cc4564f6d6bcadf581e518c5a6 Mon Sep 17 00:00:00 2001 From: p0t4t0sandwich Date: Tue, 28 Apr 2026 16:15:44 -0600 Subject: [PATCH 2/5] Adjust ModernForgeConnectionType so that it adds the Forge token to the player properties, similar to LegacyForgeConnectionType, and fix netVersion typo --- .../modern/ModernForgeConnectionType.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java index d58e4eae05..c9dba48d08 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java @@ -19,6 +19,8 @@ import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.MODERN_FORGE_TOKEN; +import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; import com.velocitypowered.proxy.connection.client.ClientConnectionPhases; import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl; @@ -47,19 +49,33 @@ public ModernForgeConnectionType(String hostName) { * @return returns the final correct hostname */ public String getModernToken() { - int natVersion = 0; + int netVersion = 0; int idx = hostName.indexOf('\0'); if (idx != -1) { for (var pt : hostName.split("\0")) { if (pt.startsWith(MODERN_FORGE_TOKEN)) { if (pt.length() > MODERN_FORGE_TOKEN.length()) { - natVersion = Integer.parseInt( + netVersion = Integer.parseInt( pt.substring(MODERN_FORGE_TOKEN.length())); } } } } - return natVersion == 0 ? "\0" + MODERN_FORGE_TOKEN : "\0" - + MODERN_FORGE_TOKEN + natVersion; + return netVersion == 0 ? "\0" + MODERN_FORGE_TOKEN : "\0" + + MODERN_FORGE_TOKEN + netVersion; + } + + @Override + public GameProfile addGameProfileTokensIfRequired(GameProfile original, + PlayerInfoForwarding forwardingType) { + // We can't forward the FORGE token to the server when we are running in legacy (or bungeeguard) forwarding mode, + // since both use the "hostname" field in the handshake. We add a special property to the + // profile instead, which will be ignored by non-Forge servers and can be intercepted by a + // Forge coremod, such as ProxyCompatibleForge. + if (forwardingType == PlayerInfoForwarding.LEGACY || forwardingType == PlayerInfoForwarding.BUNGEEGUARD) { + return original.addProperty(new GameProfile.Property("forgeClient", this.getModernToken(), "")); + } + + return original; } } From 4161e7b962439780350389efcf6732262762ea81 Mon Sep 17 00:00:00 2001 From: p0t4t0sandwich Date: Wed, 29 Apr 2026 21:48:39 -0600 Subject: [PATCH 3/5] Modify ModernForgeConnectionType so the implementation is in-line with how Waterfall handles extra data, meaning backend consumers could use the same logic to handle both modern and legacy data. --- .../forge/modern/ModernForgeConnectionType.java | 12 ++++++++++-- .../forge/modern/ModernForgeConstants.java | 11 +++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java index c9dba48d08..6060529384 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java @@ -17,6 +17,8 @@ package com.velocitypowered.proxy.connection.forge.modern; +import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.EXTRA_DATA_PROPERTY; +import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.IS_FORGE_CLIENT_PROPERTY; import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.MODERN_FORGE_TOKEN; import com.velocitypowered.api.util.GameProfile; @@ -71,9 +73,15 @@ public GameProfile addGameProfileTokensIfRequired(GameProfile original, // We can't forward the FORGE token to the server when we are running in legacy (or bungeeguard) forwarding mode, // since both use the "hostname" field in the handshake. We add a special property to the // profile instead, which will be ignored by non-Forge servers and can be intercepted by a - // Forge coremod, such as ProxyCompatibleForge. + // Forge coremod, such as SpongeForge, BungeeForge, or ProxyCompatibleForge. if (forwardingType == PlayerInfoForwarding.LEGACY || forwardingType == PlayerInfoForwarding.BUNGEEGUARD) { - return original.addProperty(new GameProfile.Property("forgeClient", this.getModernToken(), "")); + final String[] split = hostName.split("\0", 2); + if (split.length < 2) { // This shouldn't occur; just a sanity check + return original.addProperty(IS_FORGE_CLIENT_PROPERTY); + } + final String extraData = "\1" + split[1].replaceAll("\0", "\1"); + return original.addProperty(IS_FORGE_CLIENT_PROPERTY) + .addProperty(EXTRA_DATA_PROPERTY.apply(extraData)); } return original; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java index 15add83bcc..141a63eb60 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java @@ -17,9 +17,20 @@ package com.velocitypowered.proxy.connection.forge.modern; +import com.velocitypowered.api.util.GameProfile; +import java.util.function.Function; + /** * Constants for use with Modern Forge systems. */ public class ModernForgeConstants { public static final String MODERN_FORGE_TOKEN = "FORGE"; + public static final String FORGE_CLIENT = "forgeClient"; + public static final String EXTRA_DATA = "extraData"; + + public static final GameProfile.Property IS_FORGE_CLIENT_PROPERTY = + new GameProfile.Property(FORGE_CLIENT, "true", ""); + + public static final Function EXTRA_DATA_PROPERTY = + (data) -> new GameProfile.Property(EXTRA_DATA, data, ""); } From 9c8d6f20198959b526db00b03a6ed36c749ec080 Mon Sep 17 00:00:00 2001 From: p0t4t0sandwich Date: Fri, 1 May 2026 22:42:37 -0600 Subject: [PATCH 4/5] Change things so the modern Forge connection provides a "modernForgeClient" property, as to not confuse existing backend implementations. --- .../forge/modern/ModernForgeConnectionType.java | 12 ++++-------- .../forge/modern/ModernForgeConstants.java | 8 ++++---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java index 6060529384..2896c9f3c0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java @@ -18,7 +18,7 @@ package com.velocitypowered.proxy.connection.forge.modern; import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.EXTRA_DATA_PROPERTY; -import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.IS_FORGE_CLIENT_PROPERTY; +import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.IS_MODERN_FORGE_CLIENT_PROPERTY; import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.MODERN_FORGE_TOKEN; import com.velocitypowered.api.util.GameProfile; @@ -75,13 +75,9 @@ public GameProfile addGameProfileTokensIfRequired(GameProfile original, // profile instead, which will be ignored by non-Forge servers and can be intercepted by a // Forge coremod, such as SpongeForge, BungeeForge, or ProxyCompatibleForge. if (forwardingType == PlayerInfoForwarding.LEGACY || forwardingType == PlayerInfoForwarding.BUNGEEGUARD) { - final String[] split = hostName.split("\0", 2); - if (split.length < 2) { // This shouldn't occur; just a sanity check - return original.addProperty(IS_FORGE_CLIENT_PROPERTY); - } - final String extraData = "\1" + split[1].replaceAll("\0", "\1"); - return original.addProperty(IS_FORGE_CLIENT_PROPERTY) - .addProperty(EXTRA_DATA_PROPERTY.apply(extraData)); + return original.addProperty(IS_MODERN_FORGE_CLIENT_PROPERTY) + .addProperty(EXTRA_DATA_PROPERTY.apply( + this.getModernToken().replaceAll("\0", "\1"))); } return original; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java index 141a63eb60..07196a330f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java @@ -25,12 +25,12 @@ */ public class ModernForgeConstants { public static final String MODERN_FORGE_TOKEN = "FORGE"; - public static final String FORGE_CLIENT = "forgeClient"; + public static final String MODERN_FORGE_CLIENT = "modernForgeClient"; public static final String EXTRA_DATA = "extraData"; - public static final GameProfile.Property IS_FORGE_CLIENT_PROPERTY = - new GameProfile.Property(FORGE_CLIENT, "true", ""); + public static final GameProfile.Property IS_MODERN_FORGE_CLIENT_PROPERTY = + new GameProfile.Property(MODERN_FORGE_CLIENT, "true", ""); public static final Function EXTRA_DATA_PROPERTY = - (data) -> new GameProfile.Property(EXTRA_DATA, data, ""); + (extraData) -> new GameProfile.Property(EXTRA_DATA, extraData, ""); } From 7994a00137206a3ea062615dbff86fce67470c49 Mon Sep 17 00:00:00 2001 From: p0t4t0sandwich Date: Sat, 2 May 2026 00:07:45 -0600 Subject: [PATCH 5/5] Added/reworded comments and a couple slight tweaks to getModernToken --- .../forge/modern/ModernForgeConnectionType.java | 8 ++++---- .../forge/modern/ModernForgeConstants.java | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java index 2896c9f3c0..01f992643f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java @@ -28,7 +28,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl; /** - * Contains extra logic. + * Contains extra logic to handle Forge 1.20.2+ clients. */ public class ModernForgeConnectionType extends ConnectionTypeImpl { @@ -48,7 +48,7 @@ public ModernForgeConnectionType(String hostName) { /** * Align the acquisition logic with the internal code of Forge. * - * @return returns the final correct hostname + * @return returns the modern Forge token with the Net Version. */ public String getModernToken() { int netVersion = 0; @@ -59,12 +59,12 @@ public String getModernToken() { if (pt.length() > MODERN_FORGE_TOKEN.length()) { netVersion = Integer.parseInt( pt.substring(MODERN_FORGE_TOKEN.length())); + break; } } } } - return netVersion == 0 ? "\0" + MODERN_FORGE_TOKEN : "\0" - + MODERN_FORGE_TOKEN + netVersion; + return MODERN_FORGE_TOKEN + (netVersion == 0 ? "" : netVersion); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java index 07196a330f..8067660bf7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java @@ -24,13 +24,23 @@ * Constants for use with Modern Forge systems. */ public class ModernForgeConstants { - public static final String MODERN_FORGE_TOKEN = "FORGE"; - public static final String MODERN_FORGE_CLIENT = "modernForgeClient"; - public static final String EXTRA_DATA = "extraData"; + /** + * Clients attempting to connect to 1.20.2+ Forge servers will have this token appended to the + * hostname in the initial handshake packet. + */ + public static final String MODERN_FORGE_TOKEN = "\0FORGE"; + /** + * Property used to identify a modern Forge client, used in tandem with "extraData". + */ + public static final String MODERN_FORGE_CLIENT = "modernForgeClient"; public static final GameProfile.Property IS_MODERN_FORGE_CLIENT_PROPERTY = new GameProfile.Property(MODERN_FORGE_CLIENT, "true", ""); + /** + * Property used to forward the Forge marker with the Net Version to the backend server. + */ + public static final String EXTRA_DATA = "extraData"; public static final Function EXTRA_DATA_PROPERTY = (extraData) -> new GameProfile.Property(EXTRA_DATA, extraData, ""); }