Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-12-24 15:20:35 +01:00
Merge pull request #136 from dualspiral/forge-handshake
Refactor Forge support to track the handshake & include keepalives
Dieser Commit ist enthalten in:
Commit
ed4994a792
@ -0,0 +1,47 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -56,9 +56,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
private ProtocolVersion protocolVersion;
|
private ProtocolVersion protocolVersion;
|
||||||
private ProtocolVersion nextProtocolVersion;
|
private ProtocolVersion nextProtocolVersion;
|
||||||
private @Nullable MinecraftConnectionAssociation association;
|
private @Nullable MinecraftConnectionAssociation association;
|
||||||
private boolean isLegacyForge;
|
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
private boolean canSendLegacyFmlResetPacket = false;
|
private ConnectionType connectionType = ConnectionTypes.UNDETERMINED;
|
||||||
|
|
||||||
public MinecraftConnection(Channel channel, VelocityServer server) {
|
public MinecraftConnection(Channel channel, VelocityServer server) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
@ -279,22 +278,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
this.association = association;
|
this.association = association;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLegacyForge() {
|
|
||||||
return isLegacyForge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLegacyForge(boolean isForge) {
|
|
||||||
this.isLegacyForge = isForge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canSendLegacyFmlResetPacket() {
|
|
||||||
return canSendLegacyFmlResetPacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCanSendLegacyFmlResetPacket(boolean canSendLegacyFMLResetPacket) {
|
|
||||||
this.canSendLegacyFmlResetPacket = isLegacyForge && canSendLegacyFMLResetPacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProtocolVersion getNextProtocolVersion() {
|
public ProtocolVersion getNextProtocolVersion() {
|
||||||
return this.nextProtocolVersion;
|
return this.nextProtocolVersion;
|
||||||
}
|
}
|
||||||
@ -302,4 +285,20 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
public void setNextProtocolVersion(ProtocolVersion nextProtocolVersion) {
|
public void setNextProtocolVersion(ProtocolVersion nextProtocolVersion) {
|
||||||
this.nextProtocolVersion = 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
* <p>Note that Forge phases are found in the enum
|
||||||
|
* {@link LegacyForgeHandshakeBackendPhase}.</p>
|
||||||
|
*/
|
||||||
|
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) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
* <p>See {@link LegacyForgeHandshakeBackendPhase} for Legacy Forge
|
||||||
|
* versions</p>
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import com.velocitypowered.proxy.VelocityServer;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.forge.ForgeConstants;
|
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||||
@ -94,18 +94,9 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverConn.hasCompletedJoin() && packet.getChannel()
|
if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) {
|
||||||
.equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
// Handled.
|
||||||
if (!serverConn.isLegacyForge()) {
|
return true;
|
||||||
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());
|
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||||
@ -173,7 +164,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
if (mc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_12_2) <= 0) {
|
if (mc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_12_2) <= 0) {
|
||||||
String channel = message.getChannel();
|
String channel = message.getChannel();
|
||||||
minecraftOrFmlMessage = channel.startsWith("MC|") || channel
|
minecraftOrFmlMessage = channel.startsWith("MC|") || channel
|
||||||
.startsWith(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
.startsWith(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||||
} else {
|
} else {
|
||||||
minecraftOrFmlMessage = message.getChannel().startsWith("minecraft:");
|
minecraftOrFmlMessage = message.getChannel().startsWith("minecraft:");
|
||||||
}
|
}
|
||||||
|
@ -117,14 +117,15 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
serverConn.getPlayer().getConnection()
|
serverConn.getPlayer().getConnection()
|
||||||
.setSessionHandler(new ClientPlaySessionHandler(server, serverConn.getPlayer()));
|
.setSessionHandler(new ClientPlaySessionHandler(server, serverConn.getPlayer()));
|
||||||
} else {
|
} else {
|
||||||
// If the server we are departing is modded, we must always reset the client's handshake.
|
// For Legacy Forge
|
||||||
if (existingConnection.isLegacyForge()) {
|
existingConnection.getPhase().onDepartForNewServer(serverConn, serverConn.getPlayer());
|
||||||
serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shut down the existing server connection.
|
// Shut down the existing server connection.
|
||||||
serverConn.getPlayer().setConnectedServer(null);
|
serverConn.getPlayer().setConnectedServer(null);
|
||||||
existingConnection.disconnect();
|
existingConnection.disconnect();
|
||||||
|
|
||||||
|
// Send keep alive to try to avoid timeouts
|
||||||
|
serverConn.getPlayer().sendKeepAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
smc.getChannel().config().setAutoRead(false);
|
smc.getChannel().config().setAutoRead(false);
|
||||||
|
@ -17,10 +17,12 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
|
|||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||||
|
import com.velocitypowered.proxy.connection.ConnectionTypes;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
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.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||||
@ -44,9 +46,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
private final ConnectedPlayer proxyPlayer;
|
private final ConnectedPlayer proxyPlayer;
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
private @Nullable MinecraftConnection connection;
|
private @Nullable MinecraftConnection connection;
|
||||||
private boolean legacyForge = false;
|
|
||||||
private boolean hasCompletedJoin = false;
|
private boolean hasCompletedJoin = false;
|
||||||
private boolean gracefulDisconnect = false;
|
private boolean gracefulDisconnect = false;
|
||||||
|
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
|
||||||
private long lastPingId;
|
private long lastPingId;
|
||||||
private long lastPingSent;
|
private long lastPingSent;
|
||||||
|
|
||||||
@ -93,6 +95,10 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
// Kick off the connection process
|
// Kick off the connection process
|
||||||
connection.setSessionHandler(
|
connection.setSessionHandler(
|
||||||
new LoginSessionHandler(server, VelocityServerConnection.this, result));
|
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();
|
startHandshake();
|
||||||
} else {
|
} else {
|
||||||
// We need to remember to reset the in-flight connection to allow connect() to work
|
// We need to remember to reset the in-flight connection to allow connect() to work
|
||||||
@ -133,8 +139,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion());
|
handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion());
|
||||||
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
|
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
|
||||||
handshake.setServerAddress(createLegacyForwardingAddress());
|
handshake.setServerAddress(createLegacyForwardingAddress());
|
||||||
} else if (proxyPlayer.getConnection().isLegacyForge()) {
|
} else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
|
||||||
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
|
handshake.setServerAddress(handshake.getServerAddress() + LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN);
|
||||||
} else {
|
} else {
|
||||||
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
|
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
|
||||||
}
|
}
|
||||||
@ -198,20 +204,17 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLegacyForge() {
|
public void completeJoin() {
|
||||||
return legacyForge;
|
if (!hasCompletedJoin) {
|
||||||
}
|
hasCompletedJoin = true;
|
||||||
|
if (connectionPhase == BackendConnectionPhases.UNKNOWN) {
|
||||||
void setLegacyForge(boolean modded) {
|
// Now we know
|
||||||
legacyForge = modded;
|
connectionPhase = BackendConnectionPhases.VANILLA;
|
||||||
}
|
if (connection != null) {
|
||||||
|
connection.setType(ConnectionTypes.VANILLA);
|
||||||
public boolean hasCompletedJoin() {
|
}
|
||||||
return hasCompletedJoin;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHasCompletedJoin(boolean hasCompletedJoin) {
|
|
||||||
this.hasCompletedJoin = hasCompletedJoin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isGracefulDisconnect() {
|
boolean isGracefulDisconnect() {
|
||||||
@ -245,4 +248,35 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
return connection != null && !connection.isClosed() && !gracefulDisconnect
|
return connection != null && !connection.isClosed() && !gracefulDisconnect
|
||||||
&& proxyPlayer.isActive();
|
&& 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
* <p>Note that Forge phases are found in the enum
|
||||||
|
* {@link LegacyForgeHandshakeClientPhase}.</p>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The vanilla {@link ClientConnectionPhase}s.
|
||||||
|
*
|
||||||
|
* <p>See {@link com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeClientPhase}
|
||||||
|
* for Legacy Forge phases</p>
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,13 @@ package com.velocitypowered.proxy.connection.client;
|
|||||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
import com.velocitypowered.api.util.ModInfo;
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
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.MinecraftPacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||||
@ -160,7 +158,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||||
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
||||||
if (serverConn != null && backendConn != null) {
|
if (serverConn != null && backendConn != null) {
|
||||||
if (PluginMessageUtil.isRegister(packet)) {
|
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)) {
|
||||||
List<String> actuallyRegistered = new ArrayList<>();
|
List<String> actuallyRegistered = new ArrayList<>();
|
||||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||||
for (String channel : channels) {
|
for (String channel : channels) {
|
||||||
@ -184,33 +184,26 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
||||||
PluginMessage rewritten = PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion());
|
PluginMessage rewritten = PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion());
|
||||||
backendConn.write(rewritten);
|
backendConn.write(rewritten);
|
||||||
} else if (backendConn.isLegacyForge() && !serverConn.hasCompletedJoin()) {
|
} else if (!player.getPhase().handle(player, this, packet)) {
|
||||||
if (packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
|
||||||
if (!player.getModInfo().isPresent()) {
|
|
||||||
List<ModInfo.Mod> mods = ForgeUtil.readModList(packet);
|
|
||||||
if (!mods.isEmpty()) {
|
|
||||||
player.setModInfo(new ModInfo("FML", mods));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always forward the FML handshake to the remote server.
|
if (!player.getPhase().consideredComplete()
|
||||||
backendConn.write(packet);
|
|| !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 {
|
} else {
|
||||||
// The client is trying to send messages too early. This is primarily caused by mods, but
|
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||||
// it's further aggravated by Velocity. To work around these issues, we will queue any
|
if (id == null) {
|
||||||
// non-FML handshake messages to be sent once the JoinGame packet has been received by the
|
backendConn.write(packet);
|
||||||
// proxy.
|
} else {
|
||||||
loginPluginMessages.add(packet);
|
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
|
||||||
}
|
packet.getData());
|
||||||
} else {
|
server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet),
|
||||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
backendConn.eventLoop());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,7 +220,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MinecraftConnection smc = serverConnection.getConnection();
|
MinecraftConnection smc = serverConnection.getConnection();
|
||||||
if (smc != null && serverConnection.hasCompletedJoin()) {
|
if (smc != null && serverConnection.getPhase().consideredComplete()) {
|
||||||
smc.write(packet);
|
smc.write(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +234,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MinecraftConnection smc = serverConnection.getConnection();
|
MinecraftConnection smc = serverConnection.getConnection();
|
||||||
if (smc != null && serverConnection.hasCompletedJoin()) {
|
if (smc != null && serverConnection.getPhase().consideredComplete()) {
|
||||||
smc.write(buf.retain());
|
smc.write(buf.retain());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,15 +282,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
spawned = true;
|
spawned = true;
|
||||||
player.getConnection().delayedWrite(joinGame);
|
player.getConnection().delayedWrite(joinGame);
|
||||||
|
|
||||||
// We have something special to do for legacy Forge servers - during first connection the FML
|
// Required for Legacy Forge
|
||||||
// handshake will transition to complete regardless. Thus, we need to ensure that a reset
|
player.getPhase().onFirstJoin(player);
|
||||||
// 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 {
|
} else {
|
||||||
// Clear tab list to avoid duplicate entries
|
// Clear tab list to avoid duplicate entries
|
||||||
player.getTabList().clearAll();
|
player.getTabList().clearAll();
|
||||||
@ -359,21 +345,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
// Flush everything
|
// Flush everything
|
||||||
player.getConnection().flush();
|
player.getConnection().flush();
|
||||||
serverMc.flush();
|
serverMc.flush();
|
||||||
serverConn.setHasCompletedJoin(true);
|
serverConn.completeJoin();
|
||||||
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<UUID> getServerBossBars() {
|
public List<UUID> getServerBossBars() {
|
||||||
@ -401,4 +373,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
player.getConnection().write(response);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,12 +30,13 @@ import com.velocitypowered.proxy.VelocityServer;
|
|||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
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.ConnectionMessages;
|
||||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
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.Chat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
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.PluginMessage;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
|
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
|
||||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||||
@ -48,6 +49,7 @@ import java.util.Optional;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import net.kyori.text.Component;
|
import net.kyori.text.Component;
|
||||||
import net.kyori.text.TextComponent;
|
import net.kyori.text.TextComponent;
|
||||||
import net.kyori.text.TranslatableComponent;
|
import net.kyori.text.TranslatableComponent;
|
||||||
@ -79,6 +81,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
private @Nullable ModInfo modInfo;
|
private @Nullable ModInfo modInfo;
|
||||||
private final VelocityTabList tabList;
|
private final VelocityTabList tabList;
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
|
private ClientConnectionPhase connectionPhase;
|
||||||
|
|
||||||
@MonotonicNonNull
|
@MonotonicNonNull
|
||||||
private List<String> serversToTry = null;
|
private List<String> serversToTry = null;
|
||||||
@ -91,6 +94,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.virtualHost = virtualHost;
|
this.virtualHost = virtualHost;
|
||||||
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
||||||
|
this.connectionPhase = connection.getType().getInitialClientPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -139,7 +143,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
return Optional.ofNullable(modInfo);
|
return Optional.ofNullable(modInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setModInfo(ModInfo modInfo) {
|
public void setModInfo(ModInfo modInfo) {
|
||||||
this.modInfo = modInfo;
|
this.modInfo = modInfo;
|
||||||
server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, modInfo));
|
server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, modInfo));
|
||||||
}
|
}
|
||||||
@ -409,10 +413,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sendLegacyForgeHandshakeResetPacket() {
|
public void sendLegacyForgeHandshakeResetPacket() {
|
||||||
if (connection.canSendLegacyFmlResetPacket()) {
|
connectionPhase.resetConnectionPhase(this);
|
||||||
connection.write(ForgeConstants.resetPacket());
|
|
||||||
connection.setCanSendLegacyFmlResetPacket(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinecraftConnection ensureBackendConnection() {
|
private MinecraftConnection ensureBackendConnection() {
|
||||||
@ -469,6 +470,39 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
ensureBackendConnection().write(Chat.createServerbound(input));
|
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 class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||||
|
|
||||||
private final RegisteredServer toConnect;
|
private final RegisteredServer toConnect;
|
||||||
|
@ -10,8 +10,11 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
|||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
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.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
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.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||||
@ -95,13 +98,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
|
ConnectionType type = checkForForge(handshake);
|
||||||
boolean isForge = handshake.getServerAddress().endsWith("\0FML\0");
|
connection.setType(type);
|
||||||
connection.setLegacyForge(isForge);
|
|
||||||
|
|
||||||
// Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_
|
// Make sure legacy forwarding is not in use on this connection.
|
||||||
// reject Forge.
|
if (!type.checkServerAddressIsValid(handshake.getServerAddress())) {
|
||||||
if (handshake.getServerAddress().contains("\0") && !isForge) {
|
|
||||||
connection.closeWith(Disconnect
|
connection.closeWith(Disconnect
|
||||||
.create(TextComponent.of("Running Velocity behind Velocity is unsupported.")));
|
.create(TextComponent.of("Running Velocity behind Velocity is unsupported.")));
|
||||||
return true;
|
return true;
|
||||||
@ -124,6 +125,18 @@ 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) {
|
private String cleanVhost(String hostname) {
|
||||||
int zeroIdx = hostname.indexOf('\0');
|
int zeroIdx = hostname.indexOf('\0');
|
||||||
return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx);
|
return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx);
|
||||||
|
@ -17,7 +17,6 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
|
|||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
|
|
||||||
@ -54,8 +53,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||||
private static final String MOJANG_HASJOINED_URL =
|
private static final String MOJANG_HASJOINED_URL =
|
||||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
|
"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 VelocityServer server;
|
||||||
private final MinecraftConnection inbound;
|
private final MinecraftConnection inbound;
|
||||||
@ -214,14 +211,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializePlayer(GameProfile profile, boolean onlineMode) {
|
private void initializePlayer(GameProfile profile, boolean onlineMode) {
|
||||||
if (inbound.isLegacyForge() && server.getConfiguration().getPlayerInfoForwardingMode()
|
// Some connection types may need to alter the game profile.
|
||||||
== PlayerInfoForwarding.LEGACY) {
|
profile = inbound.getType().addGameProfileTokensIfRequired(profile,
|
||||||
// We can't forward the FML token to the server when we are running in legacy forwarding mode,
|
server.getConfiguration().getPlayerInfoForwardingMode());
|
||||||
// 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,
|
GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(apiInbound, profile,
|
||||||
onlineMode);
|
onlineMode);
|
||||||
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
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<ModInfo.Mod> 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<ModInfo.Mod> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
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, ""));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,255 @@
|
|||||||
|
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<ModInfo.Mod> 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.
|
||||||
|
*
|
||||||
|
* <p>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).</p>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
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<ModInfo.Mod> 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<ModInfo.Mod> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren