diff --git a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java index 5fd716436..91300d1ce 100644 --- a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -57,7 +57,7 @@ public final class GameProfile { '}'; } - public final class Property { + public final static class Property { private final String name; private final String value; private final String signature; 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 6612a9af8..3cef702ac 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -45,6 +45,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { private MinecraftSessionHandler sessionHandler; private int protocolVersion; private MinecraftConnectionAssociation association; + private boolean isLegacyForge; private final VelocityServer server; public MinecraftConnection(Channel channel, VelocityServer server) { @@ -222,4 +223,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { public void setAssociation(MinecraftConnectionAssociation association) { this.association = association; } + + public boolean isLegacyForge() { + return isLegacyForge; + } + + public void setLegacyForge(boolean isForge) { + this.isLegacyForge = isForge; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java index f95053e7d..b6956336b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java @@ -6,4 +6,8 @@ public class VelocityConstants { } public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; + + public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; + + public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 }; } 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 60ed678c0..e013c0b05 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 @@ -4,6 +4,7 @@ import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.proxy.messages.ChannelSide; import com.velocitypowered.api.proxy.messages.MessageHandler; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -46,6 +47,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { connection.getPlayer().handleConnectionException(connection.getServerInfo(), original); } else if (packet instanceof JoinGame) { playerHandler.handleBackendJoinGame((JoinGame) packet); + connection.setHasCompletedJoin(true); } else if (packet instanceof BossBar) { BossBar bossBar = (BossBar) packet; switch (bossBar.getAction()) { @@ -68,6 +70,19 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return; } + if (!connection.hasCompletedJoin() && pm.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { + if (!connection.isModded()) { + connection.setModded(true); + + // We must always reset the handshake before a modded connection is established. + connection.getPlayer().sendLegacyForgeHandshakeResetPacket(); + } + + // Always forward these messages during login + connection.getPlayer().getConnection().write(pm); + return; + } + MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection, ChannelSide.FROM_SERVER, pm); if (status == MessageHandler.ForwardStatus.FORWARD) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index fba410951..4d011110c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -84,6 +84,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler { connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer())); } else { // The previous server connection should become obsolete. + // Before we remove it, if the server we are departing is modded, we must always reset the client state. + if (existingConnection.isModded()) { + connection.getPlayer().sendLegacyForgeHandshakeResetPacket(); + } existingConnection.disconnect(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 367e33463..a387661da 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -43,6 +43,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, private final ConnectedPlayer proxyPlayer; private final VelocityServer server; private MinecraftConnection minecraftConnection; + private boolean isModded = false; + private boolean hasCompletedJoin = false; public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { this.serverInfo = target; @@ -154,4 +156,20 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, message.setData(data); minecraftConnection.write(message); } + + public boolean isModded() { + return isModded; + } + + public void setModded(boolean modded) { + isModded = modded; + } + + public boolean hasCompletedJoin() { + return hasCompletedJoin; + } + + public void setHasCompletedJoin(boolean hasCompletedJoin) { + this.hasCompletedJoin = hasCompletedJoin; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 9c36766ef..3a65e591d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -235,6 +235,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return; } + if (player.getConnectedServer().isModded() && !player.getConnectedServer().hasCompletedJoin()) { + // Ensure that the messages are forwarded + player.getConnectedServer().getMinecraftConnection().write(packet); + return; + } + MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player, ChannelSide.FROM_CLIENT, packet); if (status == MessageHandler.ForwardStatus.FORWARD) { 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 f418854a5..b3973aaec 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 @@ -15,6 +15,7 @@ import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; +import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.api.util.GameProfile; @@ -294,6 +295,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { this.connectedServer = serverConnection; } + public void sendLegacyForgeHandshakeResetPacket() { + PluginMessage resetPacket = new PluginMessage(); + resetPacket.setChannel(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL); + resetPacket.setData(VelocityConstants.FORGE_LEGACY_HANDSHAKE_RESET_DATA); + connection.write(resetPacket); + } + public void close(TextComponent reason) { connection.closeWith(Disconnect.create(reason)); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index 5a5abaf67..b6b2dafe5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -70,9 +70,12 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { return; } - // Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_ reject Forge, - // although Velocity does not yet support Forge. - if (handshake.getServerAddress().contains("\0") && !handshake.getServerAddress().endsWith("\0FML\0")) { + // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13) and store that in the connection + boolean isForge = handshake.getServerAddress().endsWith("\0FML\0"); + connection.setLegacyForge(isForge); + + // Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_ reject Forge + if (handshake.getServerAddress().contains("\0") && !isForge) { connection.closeWith(Disconnect.create(TextComponent.of("Running Velocity behind Velocity is unsupported."))); return; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index df990b50e..ff728178f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -8,6 +8,7 @@ import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -31,7 +32,9 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; @@ -176,6 +179,12 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } private void initializePlayer(GameProfile profile, boolean onlineMode) { + if (inbound.isLegacyForge() && server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { + // We want to add the FML token to the properties + List properties = new ArrayList<>(profile.getProperties()); + properties.add(new GameProfile.Property("forgeClient", "true", "")); + profile = new GameProfile(profile.getId(), profile.getName(), properties); + } GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(apiInbound, profile, onlineMode); server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> {