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;
- }
-}
-