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..94293ebc1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -45,7 +45,9 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { private MinecraftSessionHandler sessionHandler; private int protocolVersion; private MinecraftConnectionAssociation association; + private boolean isLegacyForge; private final VelocityServer server; + private boolean canSendLegacyFMLResetPacket = false; public MinecraftConnection(Channel channel, VelocityServer server) { this.channel = channel; @@ -222,4 +224,20 @@ 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; + } + + public boolean canSendLegacyFMLResetPacket() { + return canSendLegacyFMLResetPacket; + } + + public void setCanSendLegacyFMLResetPacket(boolean canSendLegacyFMLResetPacket) { + this.canSendLegacyFMLResetPacket = isLegacyForge && canSendLegacyFMLResetPacket; + } } 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..968feccfe 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; @@ -68,12 +69,26 @@ 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 if + // we haven't done so already. + 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) { connection.getPlayer().getConnection().write(pm); } - } else { + } else if (connection.hasCompletedJoin()) { // Just forward the packet on. We don't have anything to handle at this time. connection.getPlayer().getConnection().write(packet); } @@ -87,7 +102,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { connection.getMinecraftConnection().close(); return; } - connection.getPlayer().getConnection().write(buf.retain()); + + if (connection.hasCompletedJoin()) { + connection.getPlayer().getConnection().write(buf.retain()); + } } @Override @@ -98,13 +116,14 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { private boolean canForwardPluginMessage(PluginMessage message) { ClientPlaySessionHandler playerHandler = (ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler(); - boolean isMCMessage; + boolean isMCOrFMLMessage; if (connection.getMinecraftConnection().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) { - isMCMessage = message.getChannel().startsWith("MC|"); + String channel = message.getChannel(); + isMCOrFMLMessage = channel.startsWith("MC|") || channel.startsWith(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL); } else { - isMCMessage = message.getChannel().startsWith("minecraft:"); + isMCOrFMLMessage = message.getChannel().startsWith("minecraft:"); } - return isMCMessage || playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) || + return isMCOrFMLMessage || playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) || server.getChannelRegistrar().registered(message.getChannel()); } } 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..6d37a8b65 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; @@ -107,6 +109,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); if (forwardingMode == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createBungeeForwardingAddress()); + } else if (proxyPlayer.getConnection().isLegacyForge()) { + handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); } else { handshake.setServerAddress(serverInfo.getAddress().getHostString()); } @@ -154,4 +158,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..b68311236 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 @@ -61,6 +61,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } player.setPing(System.currentTimeMillis() - lastPingSent); resetPingData(); + player.getConnectedServer().getMinecraftConnection().write(packet); + return; } if (packet instanceof ClientSettings) { @@ -122,12 +124,16 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } // If we don't want to handle this packet, just forward it on. - player.getConnectedServer().getMinecraftConnection().write(packet); + if (player.getConnectedServer().hasCompletedJoin()) { + player.getConnectedServer().getMinecraftConnection().write(packet); + } } @Override public void handleUnknown(ByteBuf buf) { - player.getConnectedServer().getMinecraftConnection().write(buf.retain()); + if (player.getConnectedServer().hasCompletedJoin()) { + player.getConnectedServer().getMinecraftConnection().write(buf.retain()); + } } @Override @@ -197,6 +203,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Flush everything player.getConnection().flush(); player.getConnectedServer().getMinecraftConnection().flush(); + player.getConnectedServer().setHasCompletedJoin(true); + player.getConnection().setCanSendLegacyFMLResetPacket(true); } public List getServerBossBars() { @@ -235,6 +243,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..605a376d3 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,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { this.connectedServer = serverConnection; } + public void sendLegacyForgeHandshakeResetPacket() { + if (connection.canSendLegacyFMLResetPacket()) { + PluginMessage resetPacket = new PluginMessage(); + resetPacket.setChannel(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL); + resetPacket.setData(VelocityConstants.FORGE_LEGACY_HANDSHAKE_RESET_DATA); + connection.write(resetPacket); + connection.setCanSendLegacyFMLResetPacket(false); + } + } + 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 -> {