From cfad2c65d4944ac21d967e7af0594cf4199275b8 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Fri, 26 Sep 2014 10:20:19 +1000 Subject: [PATCH] Implement Support for MinecraftForge / FML 1.7.10 Additional implementation help provided by @jk-5 and @bloodmc. --- .../java/net/md_5/bungee/api/ServerPing.java | 23 ++ .../bungee/api/connection/ProxiedPlayer.java | 25 ++ .../md_5/bungee/protocol/DefinedPacket.java | 58 ++++- .../protocol/packet/EncryptionRequest.java | 4 +- .../protocol/packet/EncryptionResponse.java | 4 +- .../bungee/protocol/packet/PluginMessage.java | 7 +- .../main/java/net/md_5/bungee/BungeeCord.java | 7 +- .../net/md_5/bungee/BungeeServerInfo.java | 2 +- .../java/net/md_5/bungee/PacketConstants.java | 6 +- .../net/md_5/bungee/ServerConnection.java | 5 +- .../java/net/md_5/bungee/ServerConnector.java | 64 ++++- .../java/net/md_5/bungee/UserConnection.java | 28 ++- .../bungee/connection/InitialHandler.java | 3 - .../bungee/connection/UpstreamBridge.java | 16 ++ .../md_5/bungee/forge/ForgeClientHandler.java | 177 ++++++++++++++ .../forge/ForgeClientHandshakeState.java | 221 ++++++++++++++++++ .../net/md_5/bungee/forge/ForgeConstants.java | 49 ++++ .../net/md_5/bungee/forge/ForgeLogger.java | 71 ++++++ .../md_5/bungee/forge/ForgeServerHandler.java | 85 +++++++ .../forge/ForgeServerHandshakeState.java | 138 +++++++++++ .../net/md_5/bungee/forge/ForgeUtils.java | 74 ++++++ .../forge/IForgeClientPacketHandler.java | 31 +++ .../forge/IForgeServerPacketHandler.java | 36 +++ proxy/src/main/resources/messages.properties | 1 + 24 files changed, 1111 insertions(+), 24 deletions(-) create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandler.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandshakeState.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeConstants.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeLogger.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandler.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandshakeState.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/IForgeClientPacketHandler.java create mode 100644 proxy/src/main/java/net/md_5/bungee/forge/IForgeServerPacketHandler.java diff --git a/api/src/main/java/net/md_5/bungee/api/ServerPing.java b/api/src/main/java/net/md_5/bungee/api/ServerPing.java index 87dfa172..19337a55 100644 --- a/api/src/main/java/net/md_5/bungee/api/ServerPing.java +++ b/api/src/main/java/net/md_5/bungee/api/ServerPing.java @@ -1,5 +1,7 @@ package net.md_5.bungee.api; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; @@ -74,6 +76,27 @@ public class ServerPing private String description; private Favicon favicon; + @Data + public static class ModInfo + { + + private String type = "FML"; + private List modList = new ArrayList<>(); + } + + @Data + @AllArgsConstructor + public static class ModItem + { + + private String modid; + private String version; + } + + // Right now, we don't get the mods from the user, so we just use a stock ModInfo object to + // create the server ping. Vanilla clients will ignore this. + private final ModInfo modinfo = new ModInfo(); + @Deprecated public ServerPing(Protocol version, Players players, String description, String favicon) { diff --git a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java index dc3255bf..f39c3473 100644 --- a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java +++ b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java @@ -1,6 +1,7 @@ package net.md_5.bungee.api.connection; import java.util.Locale; +import java.util.Map; import java.util.UUID; import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.CommandSender; @@ -153,4 +154,28 @@ public interface ProxiedPlayer extends Connection, CommandSender * @see Title */ void sendTitle(Title title); + + /** + * Gets this player's Forge Mod List, if the player has sent this + * information during the lifetime of their connection to Bungee. There is + * no guarantee that information is available at any time, as it is only + * sent during a FML handshake. Therefore, this will only contain + * information for a user that has attempted joined a Forge server. + *

+ * Consumers of this API should be aware that an empty mod list does + * not indicate that a user is not a Forge user, and so should not + * use this API to check for this - there is no way to tell this reliably. + *

+ *

+ * Calling this when handling a + * {@link net.md_5.bungee.api.event.ServerConnectedEvent} may be the best + * place to do so as this event occurs after a FML handshake has completed, + * if any has occurred. + *

+ * + * @return A {@link Map} of mods, where the key is the name of the mod, and + * the value is the version. Returns an empty list if the FML handshake has + * not occurred for this {@link ProxiedPlayer} yet. + */ + Map getModList(); } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java index 647cc974..32b7554f 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java @@ -31,18 +31,30 @@ public abstract class DefinedPacket return new String( b, Charsets.UTF_8 ); } - public static void writeArrayLegacy(byte[] b, ByteBuf buf) + public static void writeArrayLegacy(byte[] b, ByteBuf buf, boolean allowExtended) { - Preconditions.checkArgument( b.length <= Short.MAX_VALUE, "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.length ); - - buf.writeShort( b.length ); + // (Integer.MAX_VALUE & 0x1FFF9A ) = 2097050 - Forge's current upper limit + if ( allowExtended ) + { + Preconditions.checkArgument( b.length <= ( Integer.MAX_VALUE & 0x1FFF9A ), "Cannot send array longer than 2097050 (got %s bytes)", b.length ); + } else + { + Preconditions.checkArgument( b.length <= Short.MAX_VALUE, "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.length ); + } + // Write a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for Forge only) + // No vanilla packet should give a 3 byte packet, this method will still retain vanilla behaviour. + writeVarShort( buf, b.length ); buf.writeBytes( b ); } public static byte[] readArrayLegacy(ByteBuf buf) { - short len = buf.readShort(); - Preconditions.checkArgument( len <= Short.MAX_VALUE, "Cannot receive array longer than Short.MAX_VALUE (got %s bytes)", len ); + // Read in a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for Forge only) + // No vanilla packet should give a 3 byte packet, this method will still retain vanilla behaviour. + int len = readVarShort( buf ); + + // (Integer.MAX_VALUE & 0x1FFF9A ) = 2097050 - Forge's current upper limit + Preconditions.checkArgument( len <= ( Integer.MAX_VALUE & 0x1FFF9A ), "Cannot receive array longer than 2097050 (got %s bytes)", len ); byte[] ret = new byte[ len ]; buf.readBytes( ret ); @@ -83,6 +95,11 @@ public abstract class DefinedPacket } public static int readVarInt(ByteBuf input) + { + return readVarInt( input, 5 ); + } + + public static int readVarInt(ByteBuf input, int maxBytes) { int out = 0; int bytes = 0; @@ -93,7 +110,7 @@ public abstract class DefinedPacket out |= ( in & 0x7F ) << ( bytes++ * 7 ); - if ( bytes > 5 ) + if ( bytes > maxBytes ) { throw new RuntimeException( "VarInt too big" ); } @@ -129,6 +146,33 @@ public abstract class DefinedPacket } } + public static int readVarShort(ByteBuf buf) + { + int low = buf.readUnsignedShort(); + int high = 0; + if ( ( low & 0x8000 ) != 0 ) + { + low = low & 0x7FFF; + high = buf.readUnsignedByte(); + } + return ( ( high & 0xFF ) << 15 ) | low; + } + + public static void writeVarShort(ByteBuf buf, int toWrite) + { + int low = toWrite & 0x7FFF; + int high = ( toWrite & 0x7F8000 ) >> 15; + if ( high != 0 ) + { + low = low | 0x8000; + } + buf.writeShort( low ); + if ( high != 0 ) + { + buf.writeByte( high ); + } + } + public static void writeUUID(UUID value, ByteBuf output) { output.writeLong( value.getMostSignificantBits() ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionRequest.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionRequest.java index ca4e735d..bc1a649b 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionRequest.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionRequest.java @@ -41,8 +41,8 @@ public class EncryptionRequest extends DefinedPacket writeString( serverId, buf ); if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT ) { - writeArrayLegacy( publicKey, buf ); - writeArrayLegacy( verifyToken, buf ); + writeArrayLegacy( publicKey, buf, false ); + writeArrayLegacy( verifyToken, buf, false ); } else { writeArray( publicKey, buf ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionResponse.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionResponse.java index eaa34b6a..2bda5116 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionResponse.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/EncryptionResponse.java @@ -38,8 +38,8 @@ public class EncryptionResponse extends DefinedPacket { if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT ) { - writeArrayLegacy( sharedSecret, buf ); - writeArrayLegacy( verifyToken, buf ); + writeArrayLegacy( sharedSecret, buf, false ); + writeArrayLegacy( verifyToken, buf, false ); } else { writeArray( sharedSecret, buf ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/PluginMessage.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/PluginMessage.java index 690ed71c..e855b00c 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/packet/PluginMessage.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/PluginMessage.java @@ -24,6 +24,11 @@ public class PluginMessage extends DefinedPacket private String tag; private byte[] data; + /** + * Allow this packet to be sent as an "extended" packet. + */ + private boolean allowExtendedPacket = false; + @Override public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { @@ -44,7 +49,7 @@ public class PluginMessage extends DefinedPacket writeString( tag, buf ); if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT ) { - writeArrayLegacy( data, buf ); + writeArrayLegacy( data, buf, allowExtendedPacket ); } else { buf.writeBytes( data ); diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index c62d6fe7..cd382526 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -69,6 +69,7 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.command.*; import net.md_5.bungee.conf.YamlConfig; +import net.md_5.bungee.forge.ForgeConstants; import net.md_5.bungee.log.LoggingOutputStream; import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.protocol.DefinedPacket; @@ -229,6 +230,10 @@ public class BungeeCord extends ProxyServer pluginManager.loadPlugins(); config.load(); + registerChannel( ForgeConstants.FML_TAG ); + registerChannel( ForgeConstants.FML_HANDSHAKE_TAG ); + registerChannel( ForgeConstants.FORGE_REGISTER ); + isRunning = true; pluginManager.enablePlugins(); @@ -538,7 +543,7 @@ public class BungeeCord extends ProxyServer public PluginMessage registerChannels() { - return new PluginMessage( "REGISTER", Util.format( pluginChannels, "\00" ).getBytes( Charsets.UTF_8 ) ); + return new PluginMessage( "REGISTER", Util.format( pluginChannels, "\00" ).getBytes( Charsets.UTF_8 ), false ); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java index a7062e50..be3debb4 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java @@ -110,7 +110,7 @@ public class BungeeServerInfo implements ServerInfo return true; } else if ( queue ) { - packetQueue.add( new PluginMessage( channel, data ) ); + packetQueue.add( new PluginMessage( channel, data, false ) ); } return false; } diff --git a/proxy/src/main/java/net/md_5/bungee/PacketConstants.java b/proxy/src/main/java/net/md_5/bungee/PacketConstants.java index 82814b07..2747561b 100644 --- a/proxy/src/main/java/net/md_5/bungee/PacketConstants.java +++ b/proxy/src/main/java/net/md_5/bungee/PacketConstants.java @@ -1,8 +1,8 @@ package net.md_5.bungee; -import net.md_5.bungee.protocol.packet.Respawn; import net.md_5.bungee.protocol.packet.ClientStatus; import net.md_5.bungee.protocol.packet.PluginMessage; +import net.md_5.bungee.protocol.packet.Respawn; public class PacketConstants { @@ -13,6 +13,6 @@ public class PacketConstants public static final PluginMessage FORGE_MOD_REQUEST = new PluginMessage( "FML", new byte[] { 0, 0, 0, 0, 0, 2 - } ); - public static final PluginMessage I_AM_BUNGEE = new PluginMessage( "BungeeCord", new byte[ 0 ] ); + }, false ); + public static final PluginMessage I_AM_BUNGEE = new PluginMessage( "BungeeCord", new byte[ 0 ], false ); } diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnection.java b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java index 50c13924..b531ee6c 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java @@ -25,6 +25,9 @@ public class ServerConnection implements Server @Getter @Setter private boolean isObsolete; + @Getter + private final boolean forgeServer = false; + private final Unsafe unsafe = new Unsafe() { @Override @@ -37,7 +40,7 @@ public class ServerConnection implements Server @Override public void sendData(String channel, byte[] data) { - unsafe().sendPacket( new PluginMessage( channel, data ) ); + unsafe().sendPacket( new PluginMessage( channel, data, forgeServer ) ); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index 1e43586a..e893a284 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -5,6 +5,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import java.util.Objects; import java.util.Queue; +import java.util.Set; +import lombok.Getter; import lombok.RequiredArgsConstructor; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ProxyServer; @@ -19,6 +21,9 @@ import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.connection.CancelSendSignal; import net.md_5.bungee.connection.DownstreamBridge; import net.md_5.bungee.connection.LoginResult; +import net.md_5.bungee.forge.ForgeConstants; +import net.md_5.bungee.forge.ForgeServerHandler; +import net.md_5.bungee.forge.ForgeUtils; import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.PacketHandler; @@ -45,6 +50,8 @@ public class ServerConnector extends PacketHandler private final UserConnection user; private final BungeeServerInfo target; private State thisState = State.LOGIN_SUCCESS; + @Getter + private ForgeServerHandler handshakeHandler; private enum State { @@ -70,6 +77,7 @@ public class ServerConnector extends PacketHandler { this.ch = channel; + this.handshakeHandler = new ForgeServerHandler( user, ch, target ); Handshake originalHandshake = user.getPendingConnection().getHandshake(); Handshake copiedHandshake = new Handshake( originalHandshake.getProtocolVersion(), originalHandshake.getHost(), originalHandshake.getPort(), 2 ); @@ -102,6 +110,10 @@ public class ServerConnector extends PacketHandler Preconditions.checkState( thisState == State.LOGIN_SUCCESS, "Not expecting LOGIN_SUCCESS" ); ch.setProtocol( Protocol.GAME ); thisState = State.LOGIN; + if ( user.getServer() != null && user.getForgeClientHandler().isHandshakeComplete() ) + { + user.getForgeClientHandler().resetHandshake(); + } throw CancelSendSignal.INSTANCE; } @@ -142,6 +154,11 @@ public class ServerConnector extends PacketHandler ch.write( user.getSettings() ); } + if ( user.getForgeClientHandler().getClientModList() == null && !user.getForgeClientHandler().isHandshakeComplete() ) // Vanilla + { + user.getForgeClientHandler().setHandshakeComplete(); + } + if ( user.getServer() == null ) { // Once again, first connection @@ -158,12 +175,12 @@ public class ServerConnector extends PacketHandler { MinecraftOutput out = new MinecraftOutput(); out.writeStringUTF8WithoutLengthHeaderBecauseDinnerboneStuffedUpTheMCBrandPacket( ProxyServer.getInstance().getName() + " (" + ProxyServer.getInstance().getVersion() + ")" ); - user.unsafe().sendPacket( new PluginMessage( "MC|Brand", out.toArray() ) ); + user.unsafe().sendPacket( new PluginMessage( "MC|Brand", out.toArray(), handshakeHandler.isServerForge() ) ); } else { ByteBuf brand = ByteBufAllocator.DEFAULT.heapBuffer(); DefinedPacket.writeString( bungee.getName() + " (" + bungee.getVersion() + ")", brand ); - user.unsafe().sendPacket( new PluginMessage( "MC|Brand", brand.array().clone() ) ); + user.unsafe().sendPacket( new PluginMessage( "MC|Brand", brand.array().clone(), handshakeHandler.isServerForge() ) ); brand.release(); } } else @@ -249,6 +266,49 @@ public class ServerConnector extends PacketHandler throw CancelSendSignal.INSTANCE; } + @Override + public void handle(PluginMessage pluginMessage) throws Exception + { + if ( pluginMessage.getTag().equals( ForgeConstants.FML_REGISTER ) ) + { + Set channels = ForgeUtils.readRegisteredChannels( pluginMessage ); + boolean isForgeServer = false; + for ( String channel : channels ) + { + if ( channel.equals( ForgeConstants.FML_HANDSHAKE_TAG ) ) + { + isForgeServer = true; + break; + } + } + + if ( isForgeServer && !this.handshakeHandler.isServerForge() ) + { + // We now set the server-side handshake handler for the client to this. + handshakeHandler.setServerAsForgeServer(); + user.setForgeServerHandler( handshakeHandler ); + } + } + + if ( pluginMessage.getTag().equals( ForgeConstants.FML_HANDSHAKE_TAG ) || pluginMessage.getTag().equals( ForgeConstants.FORGE_REGISTER ) ) + { + this.handshakeHandler.handle( pluginMessage ); + if ( user.getForgeClientHandler().checkUserOutdated() ) + { + ch.close(); + user.getPendingConnects().remove( target ); + } + + // We send the message as part of the handler, so don't send it here. + throw CancelSendSignal.INSTANCE; + } else + { + // We have to forward these to the user, especially with Forge as stuff might break + // This includes any REGISTER messages we intercepted earlier. + user.unsafe().sendPacket( pluginMessage ); + } + } + @Override public String toString() { diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index b3e52349..6a89b52a 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -1,6 +1,7 @@ package net.md_5.bungee; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -13,6 +14,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -34,6 +36,8 @@ import net.md_5.bungee.api.score.Scoreboard; import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.entitymap.EntityMap; +import net.md_5.bungee.forge.ForgeClientHandler; +import net.md_5.bungee.forge.ForgeServerHandler; import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.PipelineUtils; @@ -117,6 +121,13 @@ public final class UserConnection implements ProxiedPlayer private EntityMap entityRewrite; private Locale locale; /*========================================================================*/ + @Getter + @Setter + private ForgeClientHandler forgeClientHandler; + @Getter + @Setter + private ForgeServerHandler forgeServerHandler; + /*========================================================================*/ private final Unsafe unsafe = new Unsafe() { @Override @@ -152,6 +163,8 @@ public final class UserConnection implements ProxiedPlayer { addGroups( s ); } + + forgeClientHandler = new ForgeClientHandler( this ); } public void sendPacket(PacketWrapper packet) @@ -369,7 +382,7 @@ public final class UserConnection implements ProxiedPlayer @Override public void sendData(String channel, byte[] data) { - unsafe().sendPacket( new PluginMessage( channel, data ) ); + unsafe().sendPacket( new PluginMessage( channel, data, forgeClientHandler.isForgeUser() ) ); } @Override @@ -470,6 +483,19 @@ public final class UserConnection implements ProxiedPlayer return ( locale == null && settings != null ) ? locale = Locale.forLanguageTag( settings.getLocale().replaceAll( "_", "-" ) ) : locale; } + @Override + public Map getModList() + { + if ( forgeClientHandler.getClientModList() == null ) + { + // Return an empty map, rather than a null, if the client hasn't got any mods, + // or is yet to complete a handshake. + return ImmutableMap.of(); + } + + return ImmutableMap.copyOf( forgeClientHandler.getClientModList() ); + } + private static final String EMPTY_TEXT = ComponentSerializer.toString( new TextComponent( "" ) ); @Override diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 8dddd189..42137ad0 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -288,9 +288,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection return; } - // TODO: Nuuuu Mojang why u do this - // unsafe().sendPacket( PacketConstants.I_AM_BUNGEE ); - // unsafe().sendPacket( PacketConstants.FORGE_MOD_REQUEST ); Callback callback = new Callback() { diff --git a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java index ba3c87d0..b6988a3d 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java @@ -19,6 +19,7 @@ import net.md_5.bungee.protocol.packet.ClientSettings; import net.md_5.bungee.protocol.packet.PluginMessage; import java.util.ArrayList; import java.util.List; +import net.md_5.bungee.forge.ForgeConstants; import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.packet.TabCompleteResponse; @@ -142,6 +143,21 @@ public class UpstreamBridge extends PacketHandler throw CancelSendSignal.INSTANCE; } + // We handle forge handshake messages if forge support is enabled. + if ( pluginMessage.getTag().equals( ForgeConstants.FML_HANDSHAKE_TAG ) ) + { + // Let our forge client handler deal with this packet. + con.getForgeClientHandler().handle( pluginMessage ); + throw CancelSendSignal.INSTANCE; + } + + if ( con.getServer() != null && !con.getServer().isForgeServer() && pluginMessage.getData().length > Short.MAX_VALUE ) + { + // Drop the packet if the server is not a Forge server and the message was > 32kiB (as suggested by @jk-5) + // Do this AFTER the mod list, so we get that even if the intial server isn't modded. + throw CancelSendSignal.INSTANCE; + } + PluginMessageEvent event = new PluginMessageEvent( con, con.getServer(), pluginMessage.getTag(), pluginMessage.getData().clone() ); if ( bungee.getPluginManager().callEvent( event ).isCancelled() ) { diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandler.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandler.java new file mode 100644 index 00000000..c624ee0c --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandler.java @@ -0,0 +1,177 @@ +package net.md_5.bungee.forge; + +import java.util.ArrayDeque; +import java.util.Map; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.protocol.packet.PluginMessage; + +/** + * Handles the Forge Client data and handshake procedure. + */ +@RequiredArgsConstructor +public class ForgeClientHandler +{ + + @NonNull + private final UserConnection con; + + @Getter + @Setter(AccessLevel.PACKAGE) + private boolean forgeOutdated = false; + + /** + * The users' mod list. + */ + @Getter + @Setter(AccessLevel.PACKAGE) + private Map clientModList = null; + + private final ArrayDeque packetQueue = new ArrayDeque(); + + @NonNull + @Setter(AccessLevel.PACKAGE) + private ForgeClientHandshakeState state = ForgeClientHandshakeState.HELLO; + + private PluginMessage serverModList = null; + private PluginMessage serverIdList = null; + + /** + * Handles the Forge packet. + * + * @param message The Forge Handshake packet to handle. + */ + public void handle(PluginMessage message) throws IllegalArgumentException + { + if ( !message.getTag().equalsIgnoreCase( ForgeConstants.FML_HANDSHAKE_TAG ) ) + { + throw new IllegalArgumentException( "Expecting a Forge Handshake packet." ); + } + + message.setAllowExtendedPacket( true ); // FML allows extended packets so this must be enabled + ForgeClientHandshakeState prevState = state; + packetQueue.add( message ); + state = state.send( message, con ); + if ( state != prevState ) // state finished, send packets + { + synchronized ( packetQueue ) + { + while ( !packetQueue.isEmpty() ) + { + ForgeLogger.logClient( ForgeLogger.LogDirection.SENDING, prevState.name(), packetQueue.getFirst() ); + con.getForgeServerHandler().receive( packetQueue.removeFirst() ); + } + } + } + } + + /** + * Receives a {@link PluginMessage} from ForgeServer to pass to Client. + * + * @param message The message to being received. + */ + public void receive(PluginMessage message) throws IllegalArgumentException + { + state = state.handle( message, con ); + } + + /** + * Resets the client handshake state to HELLO, and, if we know the handshake + * has been completed before, send the reset packet. + */ + public void resetHandshake() + { + state = ForgeClientHandshakeState.HELLO; + con.unsafe().sendPacket( ForgeConstants.FML_RESET_HANDSHAKE ); + } + + /** + * Sends the server mod list to the client, or stores it for sending later. + * + * @param modList The {@link PluginMessage} to send to the client containing + * the mod list. + * @throws IllegalArgumentException Thrown if the {@link PluginMessage} was + * not as expected. + */ + public void setServerModList(PluginMessage modList) throws IllegalArgumentException + { + if ( !modList.getTag().equalsIgnoreCase( ForgeConstants.FML_HANDSHAKE_TAG ) || modList.getData()[0] != 2 ) + { + throw new IllegalArgumentException( "modList" ); + } + + this.serverModList = modList; + } + + /** + * Sends the server ID list to the client, or stores it for sending later. + * + * @param idList The {@link PluginMessage} to send to the client containing + * the ID list. + * @throws IllegalArgumentException Thrown if the {@link PluginMessage} was + * not as expected. + */ + public void setServerIdList(PluginMessage idList) throws IllegalArgumentException + { + if ( !idList.getTag().equalsIgnoreCase( ForgeConstants.FML_HANDSHAKE_TAG ) || idList.getData()[0] != 3 ) + { + throw new IllegalArgumentException( "idList" ); + } + + this.serverIdList = idList; + } + + /** + * Returns whether the handshake is complete. + * + * @return true if the handshake has been completed. + */ + public boolean isHandshakeComplete() + { + return this.state == ForgeClientHandshakeState.DONE; + } + + public void setHandshakeComplete() + { + this.state = ForgeClientHandshakeState.DONE; + } + + /** + * Returns whether we know if the user is a forge user. + * + * @return true if the user is a forge user. + */ + public boolean isForgeUser() + { + return clientModList != null; + } + + /** + * Checks to see if a user is using an outdated FML build, and takes + * appropriate action on the User side. This should only be called during a + * server connection, by the ServerConnector + * + * @return true if the user's FML build is outdated, otherwise + * false + */ + public boolean checkUserOutdated() + { + if ( forgeOutdated ) + { + if ( con.isDimensionChange() ) + { + con.disconnect( BungeeCord.getInstance().getTranslation( "connect_kick_outdated_forge" ) ); + } else + { + con.sendMessage( BungeeCord.getInstance().getTranslation( "connect_kick_outdated_forge" ) ); + } + } + + return forgeOutdated; + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandshakeState.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandshakeState.java new file mode 100644 index 00000000..6fd15ba4 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeClientHandshakeState.java @@ -0,0 +1,221 @@ +package net.md_5.bungee.forge; + +import java.util.Map; +import net.md_5.bungee.ServerConnector; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.forge.ForgeLogger.LogDirection; +import net.md_5.bungee.protocol.packet.PluginMessage; + +/** + * Handshake sequence manager for the Bungee - Forge Client (Upstream) link. + * Modelled after the Forge implementation. See + * https://github.com/MinecraftForge/FML/blob/master/src/main/java/cpw/mods/fml/common/network/handshake/FMLHandshakeClientState.java + */ +enum ForgeClientHandshakeState implements IForgeClientPacketHandler +{ + + /** + * Initiated at the start of a client handshake. This is a special case + * where we don't want to use a {@link PluginMessage}, we're just sending + * stuff out here. + * + * Transitions into the HELLO state upon completion. + * + * Requires: {@link UserConnection}. + */ + START + { + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + ForgeLogger.logClient( LogDirection.RECEIVED, this.name(), message ); + con.unsafe().sendPacket( message ); + con.getForgeClientHandler().setState( HELLO ); + return HELLO; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + return HELLO; + } + }, + /** + * Waiting to receive a client HELLO and the mod list. Upon receiving the + * mod list, return the mod list of the server. + * + * We will be stuck in this state if we don't have a forge client. This is + * OK. + * + * Transitions to the WAITINGCACK state upon completion. + * + * Requires: + * {@link PluginMessage}, {@link UserConnection}, {@link ServerConnector} + */ + HELLO + { + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + ForgeLogger.logClient( LogDirection.RECEIVED, this.name(), message ); + // Server Hello. + if ( message.getData()[0] == 0 ) + { + con.unsafe().sendPacket( message ); + } + + return this; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + // Client Hello. + if ( message.getData()[0] == 1 ) + { + return this; + } + + // Mod list. + if ( message.getData()[0] == 2 ) + { + if ( con.getForgeClientHandler().getClientModList() == null ) + { + // This is the first Forge connection - so get the mods now. + // Once we've done it, no point doing it again. + Map clientModList = ForgeUtils.readModList( message ); + con.getForgeClientHandler().setClientModList( clientModList ); + + // Get the version from the mod list. + // TODO: Remove this once Bungee becomes 1.8 only. + int buildNumber = ForgeUtils.getFmlBuildNumber( clientModList ); + + // If we get 0, we're probably using a testing build, so let it though. Otherwise, check the build number. + if ( buildNumber < ForgeConstants.FML_MIN_BUILD_VERSION && buildNumber != 0 ) + { + // Mark the user as an old Forge user. This will then cause any Forge ServerConnectors to cancel any + // connections to it. + con.getForgeClientHandler().setForgeOutdated( true ); + } + } + + return WAITINGSERVERDATA; + } + + return this; + } + + }, + WAITINGSERVERDATA + { + + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + ForgeLogger.logClient( ForgeLogger.LogDirection.RECEIVED, this.name(), message ); + // Mod list. + if ( message.getData()[0] == 2 ) + { + con.unsafe().sendPacket( message ); + } + + return this; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + // ACK + return WAITINGSERVERCOMPLETE; + } + }, + WAITINGSERVERCOMPLETE + { + + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + ForgeLogger.logClient( ForgeLogger.LogDirection.RECEIVED, this.name(), message ); + // Mod ID's. + if ( message.getData()[0] == 3 ) + { + con.unsafe().sendPacket( message ); + return this; + } + + con.unsafe().sendPacket( message ); // pass everything else + return this; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + // Send ACK. + return PENDINGCOMPLETE; + } + }, + PENDINGCOMPLETE + { + + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + // Ack. + if ( message.getData()[0] == -1 ) + { + ForgeLogger.logClient( ForgeLogger.LogDirection.RECEIVED, this.name(), message ); + con.unsafe().sendPacket( message ); + } + + return this; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + // Send an ACK + return COMPLETE; + } + }, + COMPLETE + { + + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + // Ack. + if ( message.getData()[0] == -1 ) + { + ForgeLogger.logClient( ForgeLogger.LogDirection.RECEIVED, this.name(), message ); + con.unsafe().sendPacket( message ); + } + + return this; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + return DONE; + } + }, + /** + * Handshake has been completed. Ignores any future handshake packets. + */ + DONE + { + + @Override + public ForgeClientHandshakeState handle(PluginMessage message, UserConnection con) + { + ForgeLogger.logClient( ForgeLogger.LogDirection.RECEIVED, this.name(), message ); + return this; + } + + @Override + public ForgeClientHandshakeState send(PluginMessage message, UserConnection con) + { + return this; + } + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeConstants.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeConstants.java new file mode 100644 index 00000000..e78bda6a --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeConstants.java @@ -0,0 +1,49 @@ +package net.md_5.bungee.forge; + +import java.util.regex.Pattern; +import net.md_5.bungee.protocol.packet.PluginMessage; + +public class ForgeConstants +{ + + // Forge + public static final String FORGE_REGISTER = "FORGE"; + + // FML + public static final String FML_TAG = "FML"; + public static final String FML_HANDSHAKE_TAG = "FML|HS"; + public static final String FML_REGISTER = "REGISTER"; + + public static final PluginMessage FML_RESET_HANDSHAKE = new PluginMessage( FML_HANDSHAKE_TAG, new byte[] + { + -2, 0 + }, false ); + public static final PluginMessage FML_ACK = new PluginMessage( FML_HANDSHAKE_TAG, new byte[] + { + -1, 0 + }, false ); + public static final PluginMessage FML_START_CLIENT_HANDSHAKE = new PluginMessage( FML_HANDSHAKE_TAG, new byte[] + { + 0, 1 + }, false ); + public static final PluginMessage FML_START_SERVER_HANDSHAKE = new PluginMessage( FML_HANDSHAKE_TAG, new byte[] + { + 1, 1 + }, false ); + public static final PluginMessage FML_EMPTY_MOD_LIST = new PluginMessage( FML_HANDSHAKE_TAG, new byte[] + { + 2, 0 + }, false ); + + /** + * The minimum Forge version required to use Forge features. TODO: When the + * FML branch gets pulled, update this number to be the build that includes + * the change. + */ + public static final int FML_MIN_BUILD_VERSION = 1209; + + /** + * Regex to use to scrape the version information from a FML handshake. + */ + public static final Pattern FML_HANDSHAKE_VERSION_REGEX = Pattern.compile( "(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)" ); +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeLogger.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeLogger.java new file mode 100644 index 00000000..d0e8f297 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeLogger.java @@ -0,0 +1,71 @@ +package net.md_5.bungee.forge; + +import java.util.logging.Level; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.protocol.packet.PluginMessage; + +class ForgeLogger +{ + + static void logServer(LogDirection direction, String stateName, PluginMessage message) + { + String dir = direction == LogDirection.SENDING ? "Server -> Bungee" : "Server <- Bungee"; + String log = "[" + stateName + " " + dir + "][" + direction.name() + ": " + getNameFromDiscriminator( message.getTag(), message ) + "]"; + BungeeCord.getInstance().getLogger().log( Level.FINE, log ); + } + + static void logClient(LogDirection direction, String stateName, PluginMessage message) + { + String dir = direction == LogDirection.SENDING ? "Client -> Bungee" : "Client <- Bungee"; + String log = "[" + stateName + " " + dir + "][" + direction.name() + ": " + getNameFromDiscriminator( message.getTag(), message ) + "]"; + BungeeCord.getInstance().getLogger().log( Level.FINE, log ); + } + + private static String getNameFromDiscriminator(String channel, PluginMessage message) + { + byte discrim = message.getData()[0]; + if ( channel.equals( ForgeConstants.FML_HANDSHAKE_TAG ) ) + { + switch ( discrim ) + { + case -2: + return "Reset"; + case -1: + return "HandshakeAck"; + case 0: + return "ServerHello"; + case 1: + return "ClientHello"; + case 2: + return "ModList"; + case 3: + return "ModIdData"; + default: + return "Unknown"; + } + } else if ( channel.equals( ForgeConstants.FORGE_REGISTER ) ) + { + switch ( discrim ) + { + case 1: + return "DimensionRegister"; + case 2: + return "FluidIdMap"; + default: + return "Unknown"; + } + } + return "UnknownChannel"; + } + + public enum LogDirection + { + + SENDING, + RECEIVED + } + + private ForgeLogger() + { // Don't allow instantiations + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandler.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandler.java new file mode 100644 index 00000000..3fe5ec5f --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandler.java @@ -0,0 +1,85 @@ +package net.md_5.bungee.forge; + +import java.util.ArrayDeque; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.forge.ForgeLogger.LogDirection; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.protocol.packet.PluginMessage; + +/** + * Contains data about the Forge server, and handles the handshake. + */ +@RequiredArgsConstructor +public class ForgeServerHandler +{ + + private final UserConnection con; + @Getter + private final ChannelWrapper ch; + + @Getter(AccessLevel.PACKAGE) + private final ServerInfo serverInfo; + + @Getter + private ForgeServerHandshakeState state = ForgeServerHandshakeState.START; + + @Getter + private boolean serverForge = false; + + private final ArrayDeque packetQueue = new ArrayDeque(); + + /** + * Handles any {@link PluginMessage} that contains a FML Handshake or Forge + * Register. + * + * @param message The message to handle. + * @throws IllegalArgumentException If the wrong packet is sent down. + */ + public void handle(PluginMessage message) throws IllegalArgumentException + { + if ( !message.getTag().equalsIgnoreCase( ForgeConstants.FML_HANDSHAKE_TAG ) && !message.getTag().equalsIgnoreCase( ForgeConstants.FORGE_REGISTER ) ) + { + throw new IllegalArgumentException( "Expecting a Forge REGISTER or FML Handshake packet." ); + } + + message.setAllowExtendedPacket( true ); // FML allows extended packets so this must be enabled + ForgeServerHandshakeState prevState = state; + packetQueue.add( message ); + state = state.send( message, con ); + if ( state != prevState ) // send packets + { + synchronized ( packetQueue ) + { + while ( !packetQueue.isEmpty() ) + { + ForgeLogger.logServer( LogDirection.SENDING, prevState.name(), packetQueue.getFirst() ); + con.getForgeClientHandler().receive( packetQueue.removeFirst() ); + } + } + } + } + + /** + * Receives a {@link PluginMessage} from ForgeClientData to pass to Server. + * + * @param message The message to being received. + */ + public void receive(PluginMessage message) throws IllegalArgumentException + { + state = state.handle( message, ch ); + } + + /** + * Flags the server as a Forge server. Cannot be used to set a server back + * to vanilla (there would be no need) + */ + public void setServerAsForgeServer() + { + serverForge = true; + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandshakeState.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandshakeState.java new file mode 100644 index 00000000..c04253aa --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeServerHandshakeState.java @@ -0,0 +1,138 @@ +package net.md_5.bungee.forge; + +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.forge.ForgeLogger.LogDirection; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.protocol.packet.PluginMessage; + +/** + * Handshake sequence manager for the Bungee - Forge Server (Downstream/Server + * Connector) link. Modelled after the Forge implementation. + */ +public enum ForgeServerHandshakeState implements IForgeServerPacketHandler +{ + + /** + * Start the handshake. + * + */ + START + { + @Override + public ForgeServerHandshakeState handle(PluginMessage message, ChannelWrapper ch) + { + ForgeLogger.logServer( LogDirection.RECEIVED, this.name(), message ); + ch.write( message ); + return this; + } + + @Override + public ForgeServerHandshakeState send(PluginMessage message, UserConnection con) + { + // Send custom channel registration. Send Hello. + return HELLO; + } + }, + HELLO + { + + @Override + public ForgeServerHandshakeState handle(PluginMessage message, ChannelWrapper ch) + { + ForgeLogger.logServer( LogDirection.RECEIVED, this.name(), message ); + if ( message.getData()[0] == 1 ) // Client Hello + { + ch.write( message ); + } + + if ( message.getData()[0] == 2 ) // Client ModList + { + ch.write( message ); + } + + return this; + } + + @Override + public ForgeServerHandshakeState send(PluginMessage message, UserConnection con) + { + // Send Server Mod List. + return WAITINGCACK; + } + }, + WAITINGCACK + { + + @Override + public ForgeServerHandshakeState handle(PluginMessage message, ChannelWrapper ch) + { + ForgeLogger.logServer( LogDirection.RECEIVED, this.name(), message ); + ch.write( message ); + return this; + } + + @Override + public ForgeServerHandshakeState send(PluginMessage message, UserConnection con) + { + if ( message.getData()[0] == 3 && message.getTag().equals( ForgeConstants.FML_HANDSHAKE_TAG ) ) + { + con.getForgeClientHandler().setServerIdList( message ); + return this; + } + + if ( message.getData()[0] == -1 && message.getTag().equals( ForgeConstants.FML_HANDSHAKE_TAG ) ) // transition to COMPLETE after sending ACK + { + return this; + } + + if ( message.getTag().equals( ForgeConstants.FORGE_REGISTER ) ) // wait for Forge channel registration + { + return COMPLETE; + } + + return this; + } + }, + COMPLETE + { + + @Override + public ForgeServerHandshakeState handle(PluginMessage message, ChannelWrapper ch) + { + // Wait for ACK + ForgeLogger.logServer( LogDirection.RECEIVED, this.name(), message ); + ch.write( message ); + return this; + } + + @Override + public ForgeServerHandshakeState send(PluginMessage message, UserConnection con) + { + // Send ACK + return DONE; + } + + }, + /** + * Handshake has been completed. Do not respond to any more handshake + * packets. + */ + DONE + { + + @Override + public ForgeServerHandshakeState handle(PluginMessage message, ChannelWrapper ch) + { + // RECEIVE 2 ACKS + ForgeLogger.logServer( LogDirection.RECEIVED, this.name(), message ); + ch.write( message ); + return this; + } + + @Override + public ForgeServerHandshakeState send(PluginMessage message, UserConnection con) + { + return this; + } + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java new file mode 100644 index 00000000..3b9bd8c5 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java @@ -0,0 +1,74 @@ +package net.md_5.bungee.forge; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import net.md_5.bungee.protocol.DefinedPacket; +import net.md_5.bungee.protocol.packet.PluginMessage; + +public class ForgeUtils +{ + + /** + * Gets the registered FML channels from the packet. + * + * @param pluginMessage The packet representing FMLProxyPacket. + * @return The registered channels. + */ + public static Set readRegisteredChannels(PluginMessage pluginMessage) + { + String channels = new String( pluginMessage.getData(), Charsets.UTF_8 ); + String[] split = channels.split( "\0" ); + Set channelSet = ImmutableSet.copyOf( split ); + return channelSet; + } + + /** + * Gets the modlist from the packet. + * + * @param pluginMessage The packet representing FMLProxyPacket. + * @return The modlist. + */ + public static Map readModList(PluginMessage pluginMessage) + { + Map modTags = Maps.newHashMap(); + ByteBuf payload = Unpooled.wrappedBuffer( pluginMessage.getData() ); + byte discriminator = payload.readByte(); + if ( discriminator == 2 ) // ModList + { + ByteBuf buffer = payload.slice(); + int modCount = DefinedPacket.readVarInt( buffer, 2 ); + for ( int i = 0; i < modCount; i++ ) + { + modTags.put( DefinedPacket.readString( buffer ), DefinedPacket.readString( buffer ) ); + } + } + return modTags; + } + + /** + * Get the build number of FML from the packet. + * + * @param modList The modlist, as a Map. + * @return The build number, or 0 if it failed. + */ + public static int getFmlBuildNumber(Map modList) + { + if ( modList.containsKey( "FML" ) ) + { + Matcher matcher = ForgeConstants.FML_HANDSHAKE_VERSION_REGEX.matcher( modList.get( "FML" ) ); + if ( matcher.find() ) + { + // We know from the regex that we have an int. + return Integer.parseInt( matcher.group( 4 ) ); + } + } + + return 0; + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/IForgeClientPacketHandler.java b/proxy/src/main/java/net/md_5/bungee/forge/IForgeClientPacketHandler.java new file mode 100644 index 00000000..ccdbe1bc --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/IForgeClientPacketHandler.java @@ -0,0 +1,31 @@ +package net.md_5.bungee.forge; + +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.protocol.packet.PluginMessage; + +/** + * An interface that defines a Forge Handshake Client packet. + * + * @param The State to transition to. + */ +public interface IForgeClientPacketHandler +{ + + /** + * Handles any {@link PluginMessage} packets. + * + * @param message The {@link PluginMessage} to handle. + * @param con The {@link UserConnection} to send packets to. + * @return The state to transition to. + */ + public S handle(PluginMessage message, UserConnection con); + + /** + * Sends any {@link PluginMessage} packets. + * + * @param message The {@link PluginMessage} to send. + * @param con The {@link UserConnection} to set data. + * @return The state to transition to. + */ + public S send(PluginMessage message, UserConnection con); +} diff --git a/proxy/src/main/java/net/md_5/bungee/forge/IForgeServerPacketHandler.java b/proxy/src/main/java/net/md_5/bungee/forge/IForgeServerPacketHandler.java new file mode 100644 index 00000000..6699a47c --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/forge/IForgeServerPacketHandler.java @@ -0,0 +1,36 @@ +package net.md_5.bungee.forge; + +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.protocol.packet.PluginMessage; + +/** + * An interface that defines a Forge Handshake Server packet. + * + * @param The State to transition to. + */ +public interface IForgeServerPacketHandler +{ + + /** + * Handles any {@link net.md_5.bungee.protocol.packet.PluginMessage} + * packets. + * + * @param message The {@link net.md_5.bungee.protocol.packet.PluginMessage} + * to handle. + * @param ch The {@link ChannelWrapper} to send packets to. + * @return The state to transition to. + */ + public S handle(PluginMessage message, ChannelWrapper ch); + + /** + * Sends any {@link net.md_5.bungee.protocol.packet.PluginMessage} packets. + * + * @param message The {@link net.md_5.bungee.protocol.packet.PluginMessage} + * to send. + * @param con The {@link net.md_5.bungee.UserConnection} to send packets to + * or read from. + * @return The state to transition to. + */ + public S send(PluginMessage message, UserConnection con); +} diff --git a/proxy/src/main/resources/messages.properties b/proxy/src/main/resources/messages.properties index 588d826a..c01a052b 100644 --- a/proxy/src/main/resources/messages.properties +++ b/proxy/src/main/resources/messages.properties @@ -3,6 +3,7 @@ already_connected=\u00a7cYou are already connected to this server! already_connecting=\u00a7cAlready connecting to this server! command_list=\u00a7a[{0}] \u00a7e({1}): \u00a7r{2} connect_kick=\u00a7cKicked whilst connecting to {0}: {1} +connect_kick_outdated_forge=\u00a7cYour version of Forge is outdated. Please update Forge and try again. current_server=\u00a76You are currently connected to {0}. fallback_kick=\u00a7cCould not connect to default or fallback server, please try again later: {0} fallback_lobby=\u00a7cCould not connect to target server, you have been moved to the fallback server.