diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java deleted file mode 100644 index 81cb4e504..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.velocitypowered.proxy.connection; - -import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.proxy.config.PlayerInfoForwarding; -import com.velocitypowered.proxy.connection.backend.BackendConnectionPhase; -import com.velocitypowered.proxy.connection.client.ClientConnectionPhase; - -/** - * The types of connection that may be selected. - */ -public interface ConnectionType { - - /** - * The initial {@link ClientConnectionPhase} for this connection type. - * - * @return The {@link ClientConnectionPhase} - */ - ClientConnectionPhase getInitialClientPhase(); - - /** - * The initial {@link BackendConnectionPhase} for this connection type. - * - * @return The {@link BackendConnectionPhase} - */ - BackendConnectionPhase getInitialBackendPhase(); - - /** - * Adds properties to the {@link GameProfile} if required. If any properties - * are added, the returned {@link GameProfile} will be a copy. - * - * @param original The original {@link GameProfile} - * @param forwardingType The Velocity {@link PlayerInfoForwarding} - * @return The {@link GameProfile} with the properties added in. - */ - GameProfile addGameProfileTokensIfRequired(GameProfile original, - PlayerInfoForwarding forwardingType); - - /** - * Tests whether the hostname is the handshake packet is valid. - * - * @param address The address to check - * @return true if valid. - */ - default boolean checkServerAddressIsValid(String address) { - return !address.contains("\0"); - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java deleted file mode 100644 index baf97c90f..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.velocitypowered.proxy.connection; - -import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; -import com.velocitypowered.proxy.connection.client.ClientConnectionPhases; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConnectionType; -import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl; - -/** - * The connection types supported by Velocity. - */ -public final class ConnectionTypes { - - /** - * Indicates that the connection has yet to reach the - * point where we have a definitive answer as to what - * type of connection we have. - */ - public static final ConnectionType UNDETERMINED = - new ConnectionTypeImpl(ClientConnectionPhases.VANILLA, BackendConnectionPhases.UNKNOWN); - - /** - * Indicates that the connection is a Vanilla connection. - */ - public static final ConnectionType VANILLA = - new ConnectionTypeImpl(ClientConnectionPhases.VANILLA, BackendConnectionPhases.VANILLA); - - /** - * Indicates that the connection is a 1.8-1.12 Forge - * connection. - */ - public static final ConnectionType LEGACY_FORGE = new LegacyForgeConnectionType(); - - private ConnectionTypes() { - throw new AssertionError(); - } -} 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 da437dbdb..ec0e99597 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -56,8 +56,9 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { private ProtocolVersion protocolVersion; private ProtocolVersion nextProtocolVersion; private @Nullable MinecraftConnectionAssociation association; + private boolean isLegacyForge; private final VelocityServer server; - private ConnectionType connectionType = ConnectionTypes.UNDETERMINED; + private boolean canSendLegacyFmlResetPacket = false; public MinecraftConnection(Channel channel, VelocityServer server) { this.channel = channel; @@ -278,6 +279,22 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { 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; + } + public ProtocolVersion getNextProtocolVersion() { return this.nextProtocolVersion; } @@ -285,20 +302,4 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { public void setNextProtocolVersion(ProtocolVersion nextProtocolVersion) { this.nextProtocolVersion = nextProtocolVersion; } - - /** - * Gets the detected {@link ConnectionType} - * @return The {@link ConnectionType} - */ - public ConnectionType getType() { - return connectionType; - } - - /** - * Sets the detected {@link ConnectionType} - * @param connectionType The {@link ConnectionType} - */ - public void setType(ConnectionType connectionType) { - this.connectionType = connectionType; - } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendConnectionPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendConnectionPhase.java deleted file mode 100644 index 30c453124..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendConnectionPhase.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.velocitypowered.proxy.connection.backend; - -import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeBackendPhase; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; - -/** - * Provides connection phase specific actions. - * - *

Note that Forge phases are found in the enum - * {@link LegacyForgeHandshakeBackendPhase}.

- */ -public interface BackendConnectionPhase { - - /** - * Handle a plugin message in the context of - * this phase. - * - * @param message The message to handle - * @return true if handled, false otherwise. - */ - default boolean handle(VelocityServerConnection server, - ConnectedPlayer player, - PluginMessage message) { - return false; - } - - /** - * Indicates whether the connection is considered complete - * @return true if so - */ - default boolean consideredComplete() { - return true; - } - - /** - * Fired when the provided server connection is about to be terminated - * because the provided player is connecting to a new server. - * - * @param serverConnection The server the player is disconnecting from - * @param player The player - */ - default void onDepartForNewServer(VelocityServerConnection serverConnection, - ConnectedPlayer player) { - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendConnectionPhases.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendConnectionPhases.java deleted file mode 100644 index 8780f8ef3..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendConnectionPhases.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.velocitypowered.proxy.connection.backend; - -import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeBackendPhase; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; - -/** - * Contains Vanilla {@link BackendConnectionPhase}s. - * - *

See {@link LegacyForgeHandshakeBackendPhase} for Legacy Forge - * versions

- */ -public final class BackendConnectionPhases { - - /** - * The backend connection is vanilla. - */ - public static final BackendConnectionPhase VANILLA = new BackendConnectionPhase() {}; - - /** - * The backend connection is unknown at this time. - */ - public static final BackendConnectionPhase UNKNOWN = new BackendConnectionPhase() { - @Override - public boolean consideredComplete() { - return false; - } - - @Override - public boolean handle(VelocityServerConnection serverConn, - ConnectedPlayer player, - PluginMessage message) { - // The connection may be legacy forge. If so, the Forge handler will deal with this - // for us. Otherwise, we have nothing to do. - return LegacyForgeHandshakeBackendPhase.NOT_STARTED.handle(serverConn, player, message); - } - }; - - private BackendConnectionPhases() { - throw new AssertionError(); - } -} 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 d83f7989d..14b1431df 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 @@ -7,7 +7,7 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants; +import com.velocitypowered.proxy.connection.forge.ForgeConstants; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.packet.BossBar; @@ -94,9 +94,18 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return true; } - if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) { - // Handled. - return true; + if (!serverConn.hasCompletedJoin() && packet.getChannel() + .equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { + if (!serverConn.isLegacyForge()) { + serverConn.setLegacyForge(true); + + // We must always reset the handshake before a modded connection is established if + // we haven't done so already. + serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket(); + } + + // Always forward these messages during login. + return false; } ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); @@ -164,7 +173,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { if (mc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_12_2) <= 0) { String channel = message.getChannel(); minecraftOrFmlMessage = channel.startsWith("MC|") || channel - .startsWith(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL); + .startsWith(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL); } else { minecraftOrFmlMessage = message.getChannel().startsWith("minecraft:"); } 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 977d1a95c..914578ee0 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 @@ -117,15 +117,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { serverConn.getPlayer().getConnection() .setSessionHandler(new ClientPlaySessionHandler(server, serverConn.getPlayer())); } else { - // For Legacy Forge - existingConnection.getPhase().onDepartForNewServer(serverConn, serverConn.getPlayer()); + // If the server we are departing is modded, we must always reset the client's handshake. + if (existingConnection.isLegacyForge()) { + serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket(); + } // Shut down the existing server connection. serverConn.getPlayer().setConnectedServer(null); existingConnection.disconnect(); - - // Send keep alive to try to avoid timeouts - serverConn.getPlayer().sendKeepAlive(); } smc.getChannel().config().setAutoRead(false); 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 3720b221f..21bd12dd0 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 @@ -17,12 +17,10 @@ import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; -import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; @@ -46,9 +44,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, private final ConnectedPlayer proxyPlayer; private final VelocityServer server; private @Nullable MinecraftConnection connection; + private boolean legacyForge = false; private boolean hasCompletedJoin = false; private boolean gracefulDisconnect = false; - private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private long lastPingId; private long lastPingSent; @@ -95,10 +93,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, // Kick off the connection process connection.setSessionHandler( new LoginSessionHandler(server, VelocityServerConnection.this, result)); - - // Set the connection phase, which may, for future forge (or whatever), be determined - // at this point already - connectionPhase = connection.getType().getInitialBackendPhase(); startHandshake(); } else { // We need to remember to reset the in-flight connection to allow connect() to work @@ -139,8 +133,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion()); if (forwardingMode == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createLegacyForwardingAddress()); - } else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) { - handshake.setServerAddress(handshake.getServerAddress() + LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN); + } else if (proxyPlayer.getConnection().isLegacyForge()) { + handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); } else { handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); } @@ -204,17 +198,20 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, return true; } - public void completeJoin() { - if (!hasCompletedJoin) { - hasCompletedJoin = true; - if (connectionPhase == BackendConnectionPhases.UNKNOWN) { - // Now we know - connectionPhase = BackendConnectionPhases.VANILLA; - if (connection != null) { - connection.setType(ConnectionTypes.VANILLA); - } - } - } + public boolean isLegacyForge() { + return legacyForge; + } + + void setLegacyForge(boolean modded) { + legacyForge = modded; + } + + public boolean hasCompletedJoin() { + return hasCompletedJoin; + } + + public void setHasCompletedJoin(boolean hasCompletedJoin) { + this.hasCompletedJoin = hasCompletedJoin; } boolean isGracefulDisconnect() { @@ -248,35 +245,4 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, return connection != null && !connection.isClosed() && !gracefulDisconnect && proxyPlayer.isActive(); } - - /** - * Gets the current "phase" of the connection, mostly used for tracking - * modded negotiation for legacy forge servers and provides methods - * for performing phase specific actions. - * - * @return The {@link BackendConnectionPhase} - */ - public BackendConnectionPhase getPhase() { - return connectionPhase; - } - - /** - * Sets the current "phase" of the connection. See {@link #getPhase()} - * - * @param connectionPhase The {@link BackendConnectionPhase} - */ - public void setConnectionPhase(BackendConnectionPhase connectionPhase) { - this.connectionPhase = connectionPhase; - } - - /** - * Gets whether the {@link com.velocitypowered.proxy.protocol.packet.JoinGame} - * packet has been sent by this server. - * - * @return Whether the join has been completed. - */ - public boolean hasCompletedJoin() { - return hasCompletedJoin; - } - } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConnectionPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConnectionPhase.java deleted file mode 100644 index b5177e73e..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConnectionPhase.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.velocitypowered.proxy.connection.client; - -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeClientPhase; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; - -/** - * Provides connection phase specific actions. - * - *

Note that Forge phases are found in the enum - * {@link LegacyForgeHandshakeClientPhase}.

- */ -public interface ClientConnectionPhase { - - /** - * Handle a plugin message in the context of - * this phase. - * - * @param player The player - * @param handler The {@link ClientPlaySessionHandler} that is handling - * packets - * @param message The message to handle - * @return true if handled, false otherwise. - */ - default boolean handle(ConnectedPlayer player, - ClientPlaySessionHandler handler, - PluginMessage message) { - return false; - } - - /** - * Instruct Velocity to reset the connection phase - * back to its default for the connection type. - * - * @param player The player - */ - default void resetConnectionPhase(ConnectedPlayer player) { - } - - /** - * Perform actions just as the player joins the - * server. - * - * @param player The player - */ - default void onFirstJoin(ConnectedPlayer player) { - } - - /** - * Indicates whether the connection is considered complete. - * @return true if so - */ - default boolean consideredComplete() { - return true; - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConnectionPhases.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConnectionPhases.java deleted file mode 100644 index 874e0f4b5..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConnectionPhases.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.velocitypowered.proxy.connection.client; - -/** - * The vanilla {@link ClientConnectionPhase}s. - * - *

See {@link com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeClientPhase} - * for Legacy Forge phases

- */ -public final class ClientConnectionPhases { - - /** - * The client is connecting with a vanilla client (as far as we can tell). - */ - public static final ClientConnectionPhase VANILLA = new ClientConnectionPhase() {}; - - private ClientConnectionPhases() { - throw new AssertionError(); - } -} 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 ad3aa128b..7e3e15772 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 @@ -3,13 +3,15 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.forge.ForgeConstants; +import com.velocitypowered.proxy.connection.forge.ForgeUtil; import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.protocol.packet.ClientSettings; @@ -158,9 +160,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { VelocityServerConnection serverConn = player.getConnectedServer(); MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null; if (serverConn != null && backendConn != null) { - if (backendConn.getState() != StateRegistry.PLAY) { - logger.warn("Plugin message was sent while the backend was in PLAY. Channel: {}. Packet discarded."); - } else if (PluginMessageUtil.isRegister(packet)) { + if (PluginMessageUtil.isRegister(packet)) { List actuallyRegistered = new ArrayList<>(); List channels = PluginMessageUtil.getChannels(packet); for (String channel : channels) { @@ -184,26 +184,33 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } else if (PluginMessageUtil.isMcBrand(packet)) { PluginMessage rewritten = PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion()); backendConn.write(rewritten); - } else if (!player.getPhase().handle(player, this, packet)) { - - if (!player.getPhase().consideredComplete() - || !serverConn.getPhase().consideredComplete()) { - - // The client is trying to send messages too early. This is primarily caused by mods, but - // it's further aggravated by Velocity. To work around these issues, we will queue any - // non-FML handshake messages to be sent once the FML handshake has completed or the JoinGame - // packet has been received by the proxy, whichever comes first. - loginPluginMessages.add(packet); - } else { - ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); - if (id == null) { - backendConn.write(packet); - } else { - PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, - packet.getData()); - server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet), - backendConn.eventLoop()); + } else if (backendConn.isLegacyForge() && !serverConn.hasCompletedJoin()) { + if (packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { + if (!player.getModInfo().isPresent()) { + List mods = ForgeUtil.readModList(packet); + if (!mods.isEmpty()) { + player.setModInfo(new ModInfo("FML", mods)); + } } + + // Always forward the FML handshake to the remote server. + backendConn.write(packet); + } else { + // The client is trying to send messages too early. This is primarily caused by mods, but + // it's further aggravated by Velocity. To work around these issues, we will queue any + // non-FML handshake messages to be sent once the JoinGame packet has been received by the + // proxy. + loginPluginMessages.add(packet); + } + } else { + ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); + if (id == null) { + backendConn.write(packet); + } else { + PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, + packet.getData()); + server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet), + backendConn.eventLoop()); } } } @@ -220,7 +227,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } MinecraftConnection smc = serverConnection.getConnection(); - if (smc != null && serverConnection.getPhase().consideredComplete()) { + if (smc != null && serverConnection.hasCompletedJoin()) { smc.write(packet); } } @@ -234,7 +241,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } MinecraftConnection smc = serverConnection.getConnection(); - if (smc != null && serverConnection.getPhase().consideredComplete()) { + if (smc != null && serverConnection.hasCompletedJoin()) { smc.write(buf.retain()); } } @@ -282,8 +289,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { spawned = true; player.getConnection().delayedWrite(joinGame); - // Required for Legacy Forge - player.getPhase().onFirstJoin(player); + // We have something special to do for legacy Forge servers - during first connection the FML + // handshake will transition to complete regardless. Thus, we need to ensure that a reset + // packet is ALWAYS sent on first switch. + // + // As we know that calling this branch only happens on first join, we set that if we are a + // Forge client that we must reset on the next switch. + // + // The call will handle if the player is not a Forge player appropriately. + player.getConnection().setCanSendLegacyFmlResetPacket(true); } else { // Clear tab list to avoid duplicate entries player.getTabList().clearAll(); @@ -345,7 +359,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Flush everything player.getConnection().flush(); serverMc.flush(); - serverConn.completeJoin(); + serverConn.setHasCompletedJoin(true); + if (serverConn.isLegacyForge()) { + // We only need to indicate we can send a reset packet if we complete a handshake, that is, + // logged onto a Forge server. + // + // The special case is if we log onto a Vanilla server as our first server, FML will treat + // this as complete and **will** need a reset packet sending at some point. We will handle + // this during initial player connection if the player is detected to be forge. + // + // We do not use the result of VelocityServerConnection#isLegacyForge() directly because we + // don't want to set it false if this is a first connection to a Vanilla server. + // + // See LoginSessionHandler#handle for where the counterpart to this method is + player.getConnection().setCanSendLegacyFmlResetPacket(true); + } } public List getServerBossBars() { @@ -373,20 +401,4 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getConnection().write(response); } } - - /** - * Immediately send any queued messages to the server. - */ - public void flushQueuedMessages() { - VelocityServerConnection serverConnection = player.getConnectedServer(); - if (serverConnection != null) { - MinecraftConnection connection = serverConnection.getConnection(); - if (connection != null) { - PluginMessage pm; - while ((pm = loginPluginMessages.poll()) != null) { - connection.write(pm); - } - } - } - } } 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 ea71285eb..66c5a3855 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 @@ -30,13 +30,12 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.forge.ForgeConstants; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; -import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.Disconnect; -import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.TitlePacket; import com.velocitypowered.proxy.server.VelocityRegisteredServer; @@ -49,7 +48,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.ThreadLocalRandom; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; @@ -81,7 +79,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private @Nullable ModInfo modInfo; private final VelocityTabList tabList; private final VelocityServer server; - private ClientConnectionPhase connectionPhase; @MonotonicNonNull private List serversToTry = null; @@ -94,7 +91,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { this.connection = connection; this.virtualHost = virtualHost; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; - this.connectionPhase = connection.getType().getInitialClientPhase(); } @Override @@ -143,7 +139,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return Optional.ofNullable(modInfo); } - public void setModInfo(ModInfo modInfo) { + void setModInfo(ModInfo modInfo) { this.modInfo = modInfo; server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, modInfo)); } @@ -413,7 +409,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } public void sendLegacyForgeHandshakeResetPacket() { - connectionPhase.resetConnectionPhase(this); + if (connection.canSendLegacyFmlResetPacket()) { + connection.write(ForgeConstants.resetPacket()); + connection.setCanSendLegacyFmlResetPacket(false); + } } private MinecraftConnection ensureBackendConnection() { @@ -470,39 +469,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { ensureBackendConnection().write(Chat.createServerbound(input)); } - /** - * Sends a {@link KeepAlive} packet to the player with a random ID. - * The response will be ignored by Velocity as it will not match the - * ID last sent by the server. - */ - public void sendKeepAlive() { - if (connection.getState() == StateRegistry.PLAY) { - KeepAlive keepAlive = new KeepAlive(); - keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); - connection.write(keepAlive); - } - } - - /** - * Gets the current "phase" of the connection, mostly used for tracking - * modded negotiation for legacy forge servers and provides methods - * for performing phase specific actions. - * - * @return The {@link ClientConnectionPhase} - */ - public ClientConnectionPhase getPhase() { - return connectionPhase; - } - - /** - * Sets the current "phase" of the connection. See {@link #getPhase()} - * - * @param connectionPhase The {@link ClientConnectionPhase} - */ - public void setPhase(ClientConnectionPhase connectionPhase) { - this.connectionPhase = connectionPhase; - } - private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final RegisteredServer toConnect; 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 9a04833e2..2f52e49b0 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 @@ -10,11 +10,8 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.VelocityConfiguration; -import com.velocitypowered.proxy.connection.ConnectionType; -import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Disconnect; @@ -98,11 +95,13 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { return true; } - ConnectionType type = checkForForge(handshake); - connection.setType(type); + // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13). + boolean isForge = handshake.getServerAddress().endsWith("\0FML\0"); + connection.setLegacyForge(isForge); - // Make sure legacy forwarding is not in use on this connection. - if (!type.checkServerAddressIsValid(handshake.getServerAddress())) { + // 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 true; @@ -125,18 +124,6 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { } } - private ConnectionType checkForForge(Handshake handshake) { - // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13). - if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN) - && handshake.getProtocolVersion().getProtocol() < ProtocolVersion.MINECRAFT_1_13.getProtocol()) { - return ConnectionTypes.LEGACY_FORGE; - } else { - // For later: See if we can determine Forge 1.13+ here, else this will need to be UNDETERMINED - // until later in the cycle (most likely determinable during the LOGIN phase) - return ConnectionTypes.VANILLA; - } - } - private String cleanVhost(String hostname) { int zeroIdx = hostname.indexOf('\0'); return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx); 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 1771e6b2e..510bbad6a 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 @@ -17,6 +17,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; @@ -53,6 +54,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final String MOJANG_HASJOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; + private static final GameProfile.Property IS_FORGE_CLIENT_PROPERTY = + new GameProfile.Property("forgeClient", "true", ""); private final VelocityServer server; private final MinecraftConnection inbound; @@ -211,9 +214,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } private void initializePlayer(GameProfile profile, boolean onlineMode) { - // Some connection types may need to alter the game profile. - profile = inbound.getType().addGameProfileTokensIfRequired(profile, - server.getConfiguration().getPlayerInfoForwardingMode()); + if (inbound.isLegacyForge() && server.getConfiguration().getPlayerInfoForwardingMode() + == PlayerInfoForwarding.LEGACY) { + // We can't forward the FML token to the server when we are running in legacy 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. + profile = profile.addProperty(IS_FORGE_CLIENT_PROPERTY); + } GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(apiInbound, profile, onlineMode); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java new file mode 100644 index 000000000..322741a31 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java @@ -0,0 +1,22 @@ +package com.velocitypowered.proxy.connection.forge; + +import com.velocitypowered.proxy.protocol.packet.PluginMessage; + +public class ForgeConstants { + + public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; + public static final String FORGE_LEGACY_CHANNEL = "FML"; + public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP"; + private static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[]{-2, 0}; + + private ForgeConstants() { + throw new AssertionError(); + } + + public static PluginMessage resetPacket() { + PluginMessage msg = new PluginMessage(); + msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL); + msg.setData(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()); + return msg; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java new file mode 100644 index 000000000..cf1774e76 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java @@ -0,0 +1,46 @@ +package com.velocitypowered.proxy.connection.forge; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.util.ModInfo; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.util.List; + +public class ForgeUtil { + + private ForgeUtil() { + throw new AssertionError(); + } + + public static List readModList(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + Preconditions + .checkArgument(message.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL), + "message is not a FML HS plugin message"); + + ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData()); + try { + byte discriminator = byteBuf.readByte(); + + if (discriminator == 2) { + ImmutableList.Builder mods = ImmutableList.builder(); + int modCount = ProtocolUtils.readVarInt(byteBuf); + + for (int index = 0; index < modCount; index++) { + String id = ProtocolUtils.readString(byteBuf); + String version = ProtocolUtils.readString(byteBuf); + mods.add(new ModInfo.Mod(id, version)); + } + + return mods.build(); + } + + return ImmutableList.of(); + } finally { + byteBuf.release(); + } + } +} 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 deleted file mode 100644 index 4f1cab77a..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConnectionType.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.velocitypowered.proxy.connection.forge.legacy; - -import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.proxy.config.PlayerInfoForwarding; -import com.velocitypowered.proxy.connection.ConnectionTypes; -import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl; - -/** - * Contains extra logic for {@link ConnectionTypes#LEGACY_FORGE} - */ -public class LegacyForgeConnectionType extends ConnectionTypeImpl { - - private static final GameProfile.Property IS_FORGE_CLIENT_PROPERTY = - new GameProfile.Property("forgeClient", "true", ""); - - public LegacyForgeConnectionType() { - super(LegacyForgeHandshakeClientPhase.NOT_STARTED, LegacyForgeHandshakeBackendPhase.NOT_STARTED); - } - - @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, - // 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) { - return original.addProperty(IS_FORGE_CLIENT_PROPERTY); - } - - return original; - } - - @Override - public boolean checkServerAddressIsValid(String address) { - return super.checkServerAddressIsValid( - address.replaceAll(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN, "")); - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java deleted file mode 100644 index 6ff0a048f..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.velocitypowered.proxy.connection.forge.legacy; - -/** - * Constants for use with Legacy Forge systems. - */ -public class LegacyForgeConstants { - - /** - * Clients attempting to connect to 1.8+ Forge servers will have - * this token appended to the hostname in the initial handshake - * packet. - */ - public static final String HANDSHAKE_HOSTNAME_TOKEN = "\0FML\0"; - - /** - * The channel for legacy forge handshakes. - */ - public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; - - /** - * The reset packet discriminator. - */ - private static final int RESET_DATA_DISCRIMINATOR = -2; - - /** - * The acknowledgement packet discriminator. - */ - static final int ACK_DISCRIMINATOR = -1; - - /** - * The Server -> Client Hello discriminator. - */ - static final int SERVER_HELLO_DISCRIMINATOR = 0; - - /** - * The Client -> Server Hello discriminator. - */ - static final int CLIENT_HELLO_DISCRIMINATOR = 1; - - /** - * The Mod List discriminator. - */ - static final int MOD_LIST_DISCRIMINATOR = 2; - - /** - * The Registry discriminator. - */ - static final int REGISTRY_DISCRIMINATOR = 3; - - /** - * The form of the data for the reset packet - */ - static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[]{RESET_DATA_DISCRIMINATOR, 0}; - - private LegacyForgeConstants() { - throw new AssertionError(); - } - -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java deleted file mode 100644 index 505e068c8..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.velocitypowered.proxy.connection.forge.legacy; - -import com.velocitypowered.proxy.connection.ConnectionTypes; -import com.velocitypowered.proxy.connection.backend.BackendConnectionPhase; -import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; -import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; -import javax.annotation.Nullable; - -/** - * Allows for simple tracking of the phase that the Legacy - * Forge handshake is in (server side). - */ -public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase { - - /** - * Dummy phase for use with {@link BackendConnectionPhases#UNKNOWN} - */ - NOT_STARTED(LegacyForgeConstants.SERVER_HELLO_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeBackendPhase nextPhase() { - return HELLO; - } - }, - - /** - * Sent a hello to the client, waiting for a hello back before sending - * the mod list. - */ - HELLO(LegacyForgeConstants.MOD_LIST_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeBackendPhase nextPhase() { - return SENT_MOD_LIST; - } - - @Override - void onTransitionToNewPhase(VelocityServerConnection connection) { - // We must always reset the handshake before a modded connection is established if - // we haven't done so already. - if (connection.getConnection() != null) { - connection.getConnection().setType(ConnectionTypes.LEGACY_FORGE); - } - connection.getPlayer().sendLegacyForgeHandshakeResetPacket(); - } - }, - - /** - * The mod list from the client has been accepted and a server mod list - * has been sent. Waiting for the client to acknowledge. - */ - SENT_MOD_LIST(LegacyForgeConstants.REGISTRY_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeBackendPhase nextPhase() { - return SENT_SERVER_DATA; - } - }, - - /** - * The server data is being sent or has been sent, and is waiting for - * the client to acknowledge it has processed this. - */ - SENT_SERVER_DATA(LegacyForgeConstants.ACK_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeBackendPhase nextPhase() { - return WAITING_ACK; - } - }, - - /** - * Waiting for the client to acknowledge before completing handshake. - */ - WAITING_ACK(LegacyForgeConstants.ACK_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeBackendPhase nextPhase() { - return COMPLETE; - } - }, - - /** - * The server has completed the handshake and will continue after the client ACK. - */ - COMPLETE(null) { - @Override - public boolean consideredComplete() { - return true; - } - }; - - @Nullable private final Integer packetToAdvanceOn; - - /** - * Creates an instance of the {@link LegacyForgeHandshakeClientPhase}. - * - * @param packetToAdvanceOn The ID of the packet discriminator that indicates - * that the server has moved onto a new phase, and - * as such, Velocity should do so too (inspecting - * {@link #nextPhase()}. A null indicates there is no - * further phase to transition to. - */ - LegacyForgeHandshakeBackendPhase(@Nullable Integer packetToAdvanceOn) { - this.packetToAdvanceOn = packetToAdvanceOn; - } - - @Override - public final boolean handle(VelocityServerConnection serverConnection, - ConnectedPlayer player, - PluginMessage message) { - if (message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { - // Get the phase and check if we need to start the next phase. - LegacyForgeHandshakeBackendPhase newPhase = getNewPhase(serverConnection, message); - - // Update phase on server - serverConnection.setConnectionPhase(newPhase); - - // Write the packet to the player, we don't need it now. - player.getConnection().write(message); - return true; - } - - // Not handled, fallback - return false; - } - - @Override - public boolean consideredComplete() { - return false; - } - - @Override - public void onDepartForNewServer(VelocityServerConnection serverConnection, - ConnectedPlayer player) { - // If the server we are departing is modded, we must always reset the client's handshake. - player.getPhase().resetConnectionPhase(player); - } - - /** - * Performs any specific tasks when moving to a new phase. - * - * @param connection The server connection - */ - void onTransitionToNewPhase(VelocityServerConnection connection) { - } - - /** - * Gets the next phase, if any (will return self if we are at the end - * of the handshake). - * - * @return The next phase - */ - LegacyForgeHandshakeBackendPhase nextPhase() { - return this; - } - - /** - * Get the phase to act on, depending on the packet that has been sent. - * - * @param serverConnection The server Velocity is connecting to - * @param packet The packet - * @return The phase to transition to, which may be the same as before. - */ - private LegacyForgeHandshakeBackendPhase getNewPhase(VelocityServerConnection serverConnection, - PluginMessage packet) { - if (packetToAdvanceOn != null - && LegacyForgeUtil.getHandshakePacketDiscriminator(packet) == packetToAdvanceOn) { - LegacyForgeHandshakeBackendPhase phaseToTransitionTo = nextPhase(); - phaseToTransitionTo.onTransitionToNewPhase(serverConnection); - return phaseToTransitionTo; - } - - return this; - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java deleted file mode 100644 index f8e3f2ef6..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java +++ /dev/null @@ -1,255 +0,0 @@ -package com.velocitypowered.proxy.connection.forge.legacy; - -import com.velocitypowered.api.util.ModInfo; -import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.connection.client.ClientConnectionPhase; -import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; -import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; -import java.util.List; -import javax.annotation.Nullable; - -/** - * Allows for simple tracking of the phase that the Legacy - * Forge handshake is in - */ -public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase { - - /** - * No handshake packets have yet been sent. - * Transition to {@link #HELLO} when the ClientHello is sent. - */ - NOT_STARTED(LegacyForgeConstants.CLIENT_HELLO_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeClientPhase nextPhase() { - return HELLO; - } - - @Override - public void onFirstJoin(ConnectedPlayer player) { - // We have something special to do for legacy Forge servers - during first connection the FML - // handshake will getNewPhase to complete regardless. Thus, we need to ensure that a reset - // packet is ALWAYS sent on first switch. - // - // As we know that calling this branch only happens on first join, we set that if we are a - // Forge client that we must reset on the next switch. - player.setPhase(LegacyForgeHandshakeClientPhase.COMPLETE); - } - - @Override - boolean onHandle(ConnectedPlayer player, - ClientPlaySessionHandler handler, - PluginMessage message, - MinecraftConnection backendConn) { - // If we stay in this phase, we do nothing because it means the packet wasn't handled. - // Returning false indicates this - return false; - } - }, - - /** - * Client and Server exchange pleasantries. - * Transition to {@link #MOD_LIST} when the ModList is sent. - */ - HELLO(LegacyForgeConstants.MOD_LIST_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeClientPhase nextPhase() { - return MOD_LIST; - } - }, - - - - /** - * The Mod list is sent to the server, captured by Velocity. - * Transition to {@link #WAITING_SERVER_DATA} when an ACK is sent, which - * indicates to the server to start sending state data. - */ - MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeClientPhase nextPhase() { - return WAITING_SERVER_DATA; - } - - @Override - boolean onHandle(ConnectedPlayer player, - ClientPlaySessionHandler handler, - PluginMessage message, - MinecraftConnection backendConn) { - // Read the mod list if we haven't already. - if (!player.getModInfo().isPresent()) { - List mods = LegacyForgeUtil.readModList(message); - if (!mods.isEmpty()) { - player.setModInfo(new ModInfo("FML", mods)); - } - } - - return super.onHandle(player, handler, message, backendConn); - } - }, - - /** - * Waiting for state data to be received. - * Transition to {@link #WAITING_SERVER_COMPLETE} when this is complete - * and the client sends an ACK packet to confirm - */ - WAITING_SERVER_DATA(LegacyForgeConstants.ACK_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeClientPhase nextPhase() { - return WAITING_SERVER_COMPLETE; - } - }, - - /** - * Waiting on the server to send another ACK. - * Transition to {@link #PENDING_COMPLETE} when client sends another - * ACK - */ - WAITING_SERVER_COMPLETE(LegacyForgeConstants.ACK_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeClientPhase nextPhase() { - return PENDING_COMPLETE; - } - }, - - /** - * Waiting on the server to send yet another ACK. - * Transition to {@link #COMPLETE} when client sends another - * ACK - */ - PENDING_COMPLETE(LegacyForgeConstants.ACK_DISCRIMINATOR) { - @Override - LegacyForgeHandshakeClientPhase nextPhase() { - return COMPLETE; - } - }, - - /** - * The handshake is complete. The handshake can be reset. - * - *

Note that a successful connection to a server does not mean that - * we will be in this state. After a handshake reset, if the next server - * is vanilla we will still be in the {@link #NOT_STARTED} phase, - * which means we must NOT send a reset packet. This is handled by - * overriding the {@link #resetConnectionPhase(ConnectedPlayer)} in this - * element (it is usually a no-op).

- */ - COMPLETE(null) { - @Override - public void resetConnectionPhase(ConnectedPlayer player) { - player.getConnection().write(LegacyForgeUtil.resetPacket()); - player.setPhase(LegacyForgeHandshakeClientPhase.NOT_STARTED); - } - - @Override - public boolean consideredComplete() { - return true; - } - - @Override - boolean onHandle(ConnectedPlayer player, - ClientPlaySessionHandler handler, - PluginMessage message, - MinecraftConnection backendConn) { - super.onHandle(player, handler, message, backendConn); - - // just in case the timing is awful - player.sendKeepAlive(); - handler.flushQueuedMessages(); - - return true; - } - }; - - @Nullable private final Integer packetToAdvanceOn; - - /** - * Creates an instance of the {@link LegacyForgeHandshakeClientPhase}. - * - * @param packetToAdvanceOn The ID of the packet discriminator that indicates - * that the client has moved onto a new phase, and - * as such, Velocity should do so too (inspecting - * {@link #nextPhase()}. A null indicates there is no - * further phase to transition to. - */ - LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) { - this.packetToAdvanceOn = packetToAdvanceOn; - } - - @Override - public final boolean handle(ConnectedPlayer player, - ClientPlaySessionHandler handler, - PluginMessage message) { - VelocityServerConnection serverConn = player.getConnectedServer(); - if (serverConn != null) { - MinecraftConnection backendConn = serverConn.getConnection(); - if (backendConn != null - && message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { - // Get the phase and check if we need to start the next phase. - LegacyForgeHandshakeClientPhase newPhase = getNewPhase(message); - - // Update phase on player - player.setPhase(newPhase); - - // Perform phase handling - return newPhase.onHandle(player, handler, message, backendConn); - } - } - - // Not handled, fallback - return false; - } - - /** - * Handles the phase tasks - * - * @param player The player - * @param handler The {@link ClientPlaySessionHandler} that is handling - * packets - * @param message The message to handle - * @param backendConn The backend connection to write to, if required. - * - * @return true if handled, false otherwise. - */ - boolean onHandle(ConnectedPlayer player, - ClientPlaySessionHandler handler, - PluginMessage message, - MinecraftConnection backendConn) { - // Send the packet on to the server. - backendConn.write(message); - - // We handled the packet. No need to continue processing. - return true; - } - - @Override - public boolean consideredComplete() { - return false; - } - - /** - * Gets the next phase, if any (will return self if we are at the end - * of the handshake). - * - * @return The next phase - */ - LegacyForgeHandshakeClientPhase nextPhase() { - return this; - } - - /** - * Get the phase to act on, depending on the packet that has been sent. - * - * @param packet The packet - * @return The phase to transition to, which may be the same as before. - */ - private LegacyForgeHandshakeClientPhase getNewPhase(PluginMessage packet) { - if (packetToAdvanceOn != null - && LegacyForgeUtil.getHandshakePacketDiscriminator(packet) == packetToAdvanceOn) { - return nextPhase(); - } - - return this; - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java deleted file mode 100644 index f75067fb5..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.velocitypowered.proxy.connection.forge.legacy; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.util.ModInfo; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.packet.PluginMessage; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import java.util.List; - -class LegacyForgeUtil { - - private LegacyForgeUtil() { - throw new AssertionError(); - } - - /** - * Gets the discriminator from the FML|HS packet (the first byte in the data) - * - * @param message The message to analyse - * @return The discriminator - */ - static byte getHandshakePacketDiscriminator(PluginMessage message) { - Preconditions.checkArgument( - message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)); - ByteBuf buf = Unpooled.wrappedBuffer(message.getData()); - try { - return buf.readByte(); - } finally { - buf.release(); - } - } - - /** - * Gets the mod list from the mod list packet and parses it. - * - * @param message The message - * @return The list of mods. May be empty. - */ - static List readModList(PluginMessage message) { - Preconditions.checkNotNull(message, "message"); - Preconditions - .checkArgument(message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL), - "message is not a FML HS plugin message"); - - ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData()); - try { - byte discriminator = byteBuf.readByte(); - - if (discriminator == LegacyForgeConstants.MOD_LIST_DISCRIMINATOR) { - ImmutableList.Builder mods = ImmutableList.builder(); - int modCount = ProtocolUtils.readVarInt(byteBuf); - - for (int index = 0; index < modCount; index++) { - String id = ProtocolUtils.readString(byteBuf); - String version = ProtocolUtils.readString(byteBuf); - mods.add(new ModInfo.Mod(id, version)); - } - - return mods.build(); - } - - return ImmutableList.of(); - } finally { - byteBuf.release(); - } - } - - /** - * Creates a reset packet. - * - * @return A copy of the reset packet - */ - static PluginMessage resetPacket() { - PluginMessage msg = new PluginMessage(); - msg.setChannel(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL); - msg.setData(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()); - return msg; - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionTypeImpl.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionTypeImpl.java deleted file mode 100644 index cba5efac7..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionTypeImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.velocitypowered.proxy.connection.util; - -import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.proxy.config.PlayerInfoForwarding; -import com.velocitypowered.proxy.connection.ConnectionType; -import com.velocitypowered.proxy.connection.backend.BackendConnectionPhase; -import com.velocitypowered.proxy.connection.client.ClientConnectionPhase; - -/** - * Indicates the type of connection that has been made - */ -public class ConnectionTypeImpl implements ConnectionType { - - private final ClientConnectionPhase initialClientPhase; - private final BackendConnectionPhase initialBackendPhase; - - public ConnectionTypeImpl(ClientConnectionPhase initialClientPhase, - BackendConnectionPhase initialBackendPhase) { - this.initialClientPhase = initialClientPhase; - this.initialBackendPhase = initialBackendPhase; - } - - @Override - public final ClientConnectionPhase getInitialClientPhase() { - return initialClientPhase; - } - - @Override - public final BackendConnectionPhase getInitialBackendPhase() { - return initialBackendPhase; - } - - @Override - public GameProfile addGameProfileTokensIfRequired(GameProfile original, - PlayerInfoForwarding forwardingType) { - return original; - } -} -