diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
index 0b6964ed..b3f84dac 100644
--- a/MinecraftClient/MinecraftClient.csproj
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -143,6 +143,7 @@
+
diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs
new file mode 100644
index 00000000..8cf4de09
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Protocol.Handlers.Forge
+{
+ ///
+ /// Version of the FML protocol
+ ///
+ ///
+ enum FMLVersion
+ {
+ FML,
+ FML2
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
index 088049b8..cd9d3732 100755
--- a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
+++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
@@ -31,92 +31,94 @@ namespace MinecraftClient.Protocol.Handlers.Forge
}
public List Mods;
+ internal FMLVersion Version;
///
/// Create a new ForgeInfo from the given data.
///
/// The modinfo JSON tag.
- /// Thrown on missing mod list in JSON data
- internal ForgeInfo(Json.JSONData data)
+ /// Forge protocol version
+ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion)
{
this.Mods = new List();
- bool listFound = false;
+ this.Version = fmlVersion;
- // Example ModInfo for Minecraft 1.12 and lower (FML)
-
- // "modinfo": {
- // "type": "FML",
- // "modList": [{
- // "modid": "mcp",
- // "version": "9.05"
- // }, {
- // "modid": "FML",
- // "version": "8.0.99.99"
- // }, {
- // "modid": "Forge",
- // "version": "11.14.3.1512"
- // }, {
- // "modid": "rpcraft",
- // "version": "Beta 1.3 - 1.8.0"
- // }]
- // }
-
- if (data.Properties.ContainsKey("modList") && data.Properties["modList"].Type == Json.JSONData.DataType.Array)
+ switch (fmlVersion)
{
- listFound = true;
+ case FMLVersion.FML:
- foreach (Json.JSONData mod in data.Properties["modList"].DataArray)
- {
- String modid = mod.Properties["modid"].StringValue;
- String version = mod.Properties["version"].StringValue;
+ // Example ModInfo for Minecraft 1.12 and lower (FML)
- this.Mods.Add(new ForgeMod(modid, version));
- }
+ // "modinfo": {
+ // "type": "FML",
+ // "modList": [{
+ // "modid": "mcp",
+ // "version": "9.05"
+ // }, {
+ // "modid": "FML",
+ // "version": "8.0.99.99"
+ // }, {
+ // "modid": "Forge",
+ // "version": "11.14.3.1512"
+ // }, {
+ // "modid": "rpcraft",
+ // "version": "Beta 1.3 - 1.8.0"
+ // }]
+ // }
+
+ foreach (Json.JSONData mod in data.Properties["modList"].DataArray)
+ {
+ String modid = mod.Properties["modid"].StringValue;
+ String modversion = mod.Properties["version"].StringValue;
+
+ this.Mods.Add(new ForgeMod(modid, modversion));
+ }
+
+ break;
+
+ case FMLVersion.FML2:
+
+ // Example ModInfo for Minecraft 1.13 and greater (FML2)
+
+ // "forgeData": {
+ // "channels": [{
+ // "res": "minecraft:unregister",
+ // "version": "FML2",
+ // "required": true
+ // }, {
+ // "res": "minecraft:register",
+ // "version": "FML2",
+ // "required": true
+ // }],
+ // "mods": [{
+ // "modId": "minecraft",
+ // "modmarker": "1.15.2"
+ // }, {
+ // "modId": "forge",
+ // "modmarker": "ANY"
+ // }, {
+ // "modId": "rats",
+ // "modmarker": "5.3.2"
+ // }, {
+ // "modId": "citadel",
+ // "modmarker": "1.1.11"
+ // }],
+ // "fmlNetworkVersion": 2
+ // }
+
+ foreach (Json.JSONData mod in data.Properties["mods"].DataArray)
+ {
+ String modid = mod.Properties["modId"].StringValue;
+ String modmarker = mod.Properties["modmarker"].StringValue;
+
+ this.Mods.Add(new ForgeMod(modid, modmarker));
+ }
+
+ break;
+
+ default:
+ throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!");
}
-
- // Example ModInfo for Minecraft 1.13 and greater (FML2)
-
- // "forgeData": {
- // "channels": [{
- // "res": "minecraft:unregister",
- // "version": "FML2",
- // "required": true
- // }, {
- // "res": "minecraft:register",
- // "version": "FML2",
- // "required": true
- // }],
- // "mods": [{
- // "modId": "minecraft",
- // "modmarker": "1.15.2"
- // }, {
- // "modId": "forge",
- // "modmarker": "ANY"
- // }, {
- // "modId": "rats",
- // "modmarker": "5.3.2"
- // }, {
- // "modId": "citadel",
- // "modmarker": "1.1.11"
- // }],
- // "fmlNetworkVersion": 2
- // }
-
- if (data.Properties.ContainsKey("mods") && data.Properties["mods"].Type == Json.JSONData.DataType.Array)
- {
- listFound = true;
-
- foreach (Json.JSONData mod in data.Properties["mods"].DataArray)
- {
- String modid = mod.Properties["modId"].StringValue;
- String version = mod.Properties["modmarker"].StringValue;
-
- this.Mods.Add(new ForgeMod(modid, version));
- }
- }
-
- if (!listFound)
- throw new ArgumentException("Missing mod list", "data");
}
}
}
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs
index de30a254..aae34d65 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs
@@ -169,7 +169,7 @@ namespace MinecraftClient.Protocol.Handlers
{
packetData.Clear();
int size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size
- byte[] rawpacket = socketWrapper.ReadDataRAW(size);//Packet contents
+ byte[] rawpacket = socketWrapper.ReadDataRAW(size); //Packet contents
for (int i = 0; i < rawpacket.Length; i++)
packetData.Enqueue(rawpacket[i]);
@@ -209,6 +209,13 @@ namespace MinecraftClient.Protocol.Handlers
if (protocolversion >= MC18Version)
compression_treshold = dataTypes.ReadNextVarInt(packetData);
break;
+ case 0x04:
+ int messageId = dataTypes.ReadNextVarInt(packetData);
+ string channel = dataTypes.ReadNextString(packetData);
+ List responseData = new List();
+ bool understood = pForge.HandleLoginPluginRequest(channel, packetData, ref responseData);
+ SendLoginPluginResponse(messageId, understood, responseData.ToArray());
+ return understood;
default:
return false; //Ignored packet
}
@@ -1012,7 +1019,7 @@ namespace MinecraftClient.Protocol.Handlers
{
byte[] protocol_version = dataTypes.GetVarInt(protocolversion);
string server_address = pForge.GetServerAddress(handler.GetServerHost());
- byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port);
+ byte[] server_port = dataTypes.GetUShort((ushort)handler.GetServerPort());
byte[] next_state = dataTypes.GetVarInt(2);
byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state);
@@ -1445,6 +1452,25 @@ namespace MinecraftClient.Protocol.Handlers
catch (ObjectDisposedException) { return false; }
}
+ ///
+ /// Send a Login Plugin Response packet (0x02)
+ ///
+ /// Login Plugin Request message Id
+ /// TRUE if the request was understood
+ /// Response to the request
+ /// TRUE if successfully sent
+ public bool SendLoginPluginResponse(int messageId, bool understood, byte[] data)
+ {
+ try
+ {
+ SendPacket(0x02, dataTypes.ConcatBytes(dataTypes.GetVarInt(messageId), dataTypes.GetBool(understood), data));
+ return true;
+ }
+ catch (SocketException) { return false; }
+ catch (System.IO.IOException) { return false; }
+ catch (ObjectDisposedException) { return false; }
+ }
+
///
/// Send an Interact Entity Packet to server
///
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs
index 5df42cba..cd429bca 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs
@@ -44,17 +44,17 @@ namespace MinecraftClient.Protocol.Handlers
public string GetServerAddress(string serverAddress)
{
if (ForgeEnabled())
- return serverAddress + "\0FML\0";
+ return serverAddress + "\0" + forgeInfo.Version + "\0";
return serverAddress;
}
///
- /// Completes the Minecraft Forge handshake.
+ /// Completes the Minecraft Forge handshake (Forge Protocol version 1: FML)
///
/// Whether the handshake was successful.
public bool CompleteForgeHandshake()
{
- if (ForgeEnabled())
+ if (ForgeEnabled() && forgeInfo.Version == FMLVersion.FML)
{
int packetID = -1;
Queue packetData = new Queue();
@@ -98,7 +98,7 @@ namespace MinecraftClient.Protocol.Handlers
}
///
- /// Handle Forge plugin messages
+ /// Handle Forge plugin messages (Forge Protocol version 1: FML)
///
/// Plugin message channel
/// Plugin message data
@@ -106,7 +106,7 @@ namespace MinecraftClient.Protocol.Handlers
/// TRUE if the plugin message was recognized and handled
public bool HandlePluginMessage(string channel, Queue packetData, ref int currentDimension)
{
- if (ForgeEnabled() && fmlHandshakeState != FMLHandshakeClientState.DONE)
+ if (ForgeEnabled() && forgeInfo.Version == FMLVersion.FML && fmlHandshakeState != FMLHandshakeClientState.DONE)
{
if (channel == "FML|HS")
{
@@ -234,7 +234,198 @@ namespace MinecraftClient.Protocol.Handlers
}
///
- /// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically.
+ /// Handle Forge plugin messages during login phase (Forge Protocol version 2: FML2)
+ ///
+ /// Plugin message channel
+ /// Plugin message data
+ /// Response data to return to server
+ /// TRUE/FALSE depending on whether the packet was understood or not
+ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref List responseData)
+ {
+ if (ForgeEnabled() && forgeInfo.Version == FMLVersion.FML2 && channel == "fml:loginwrapper")
+ {
+ // Forge Handshake handler source code used to implement the FML2 packets:
+ // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java
+ // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java
+ // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/NetworkInitialization.java
+ // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java
+ // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java
+ //
+ // During Login, Forge will send a set of LoginPluginRequest packets and we need to respond accordingly.
+ // Each login plugin message contains in its payload field an inner packet created by FMLLoginWrapper.java:
+ //
+ // [ ResourceLocation ][ String ] // FML Channel name
+ // [ Inner Packet ][ Packet ] // Minecraft-Like packet
+ //
+ // The channel name allows identifying which handler in Forge will process the packet
+ // For instance, the handshake channel is fml:handshake as per FMLNetworkConstants.java
+ //
+ // The inner packet has the same layout as a Minecraft packet.
+ // Forge uses Minecraft's PacketBuffer class to decode the packet.
+ // We assume no network compression is active at this point.
+ //
+ // [ Length ][ VarInt ]
+ // [ PacketID ][ VarInt ]
+ // [ Data ][ .... ]
+ //
+ // Once decoded, the packet ID for fml:handshake is mapped in NetworkInitialization.java:
+ //
+ // 99 = Client to Server - Client ACK
+ // 1 = Server to Client - Mod List
+ // 2 = Client to Server - Mod List
+ // 3 = Server to Client - Registry
+ // 4 = Server to Client - Config
+ //
+ // The content of each message is mapped into a class inside FMLHandshakeMessages.java
+ // FMLHandshakeHandler will then process the packet, e.g. handleServerModListOnClient() for Server Mod List.
+
+ string fmlChannel = dataTypes.ReadNextString(packetData);
+ dataTypes.ReadNextVarInt(packetData); // Packet length
+ int packetID = dataTypes.ReadNextVarInt(packetData);
+
+ if (fmlChannel == "fml:handshake")
+ {
+ bool fmlResponseReady = false;
+ List fmlResponsePacket = new List();
+
+ switch (packetID)
+ {
+ case 1:
+ // Server Mod List: FMLHandshakeMessages.java > S2CModList > decode()
+ //
+ // [ Mod Count ][ VarInt ]
+ // [ Mod Name ][ String ] // Amount of entries according to Mod Count
+ // [ Channel Count ][ VarInt ]
+ // [ Chan Name ][ String ] // Amount of entries according to Channel Count
+ // [ Version ][ String ] // Each entry is a pair of Channel Name + Version (1)
+ // [ Registry Count ][ VarInt ]
+ // [ Registry ][ String ] // Amount of entries according to Registry Count
+ //
+ // [1]: Version is usually set to "FML2" for FML stuff and "1" for mods
+
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Received FML2 Server Mod List");
+
+ List mods = new List();
+ int modCount = dataTypes.ReadNextVarInt(packetData);
+ for (int i = 0; i < modCount; i++)
+ mods.Add(dataTypes.ReadNextString(packetData));
+
+ Dictionary channels = new Dictionary();
+ int channelCount = dataTypes.ReadNextVarInt(packetData);
+ for (int i = 0; i < channelCount; i++)
+ channels.Add(dataTypes.ReadNextString(packetData), dataTypes.ReadNextString(packetData));
+
+ List registries = new List();
+ int registryCount = dataTypes.ReadNextVarInt(packetData);
+ for (int i = 0; i < registryCount; i++)
+ registries.Add(dataTypes.ReadNextString(packetData));
+
+ // Server Mod List Reply: FMLHandshakeMessages.java > C2SModListReply > encode()
+ //
+ // [ Mod Count ][ VarInt ]
+ // [ Mod Name ][ String ] // Amount of entries according to Mod Count
+ // [ Channel Count ][ VarInt ]
+ // [ Chan Name ][ String ] // Amount of entries according to Channel Count
+ // [ Version ][ String ] // Each entry is a pair of Channel Name + Version
+ // [ Registry Count ][ VarInt ]
+ // [ Registry ][ String ] // Amount of entries according to Registry Count
+ // [ Version ][ String ] // Each entry is a pair of Registry Name + Version
+ //
+ // We are supposed to validate server info against our set of installed mods, then reply with our list
+ // In MCC, we just want to send a valid response so we'll reply back with data collected from the server.
+
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Sending back FML2 Client Mod List");
+
+ // Packet ID 2: Client to Server Mod List
+ fmlResponsePacket.AddRange(dataTypes.GetVarInt(2));
+ fmlResponsePacket.AddRange(dataTypes.GetVarInt(mods.Count));
+ foreach (string mod in mods)
+ fmlResponsePacket.AddRange(dataTypes.GetString(mod));
+
+ fmlResponsePacket.AddRange(dataTypes.GetVarInt(channels.Count));
+ foreach (KeyValuePair item in channels)
+ {
+ fmlResponsePacket.AddRange(dataTypes.GetString(item.Key));
+ fmlResponsePacket.AddRange(dataTypes.GetString(item.Value));
+ }
+
+ fmlResponsePacket.AddRange(dataTypes.GetVarInt(registries.Count));
+ foreach (string registry in registries)
+ {
+ fmlResponsePacket.AddRange(dataTypes.GetString(registry));
+ // We don't have Registry mapping from server, leave it empty
+ fmlResponsePacket.AddRange(dataTypes.GetString(""));
+ }
+ fmlResponseReady = true;
+ break;
+
+ case 3:
+ // Server Registry: FMLHandshakeMessages.java > S2CRegistry > decode()
+ //
+ // [ Registry Name ][ String ]
+ // [ Snapshot Present ][ Bool ]
+ // [ Snapshot data ][ .... ] // Only if "Snapshot Present" is True
+ //
+ // Registry Snapshot: ForgeRegistry.java > Snapshot > read(PacketBuffer)
+ // Not documented yet. We're ignoring this packet in MCC
+
+ if (Settings.DebugMessages)
+ {
+ string registryName = dataTypes.ReadNextString(packetData);
+ ConsoleIO.WriteLineFormatted("§8Acknowledging FML2 Server Registry: " + registryName);
+ }
+
+ fmlResponsePacket.AddRange(dataTypes.GetVarInt(99));
+ fmlResponseReady = true;
+ break;
+
+ case 4:
+ // Server Config: FMLHandshakeMessages.java > S2CConfigData > decode()
+ //
+ // [ Config Name ][ String ]
+ // [ Config Data ][ .... ] // Remaining packet data (1)
+ //
+ // [1] Config data may containt a standard Minecraft string readable with dataTypes.readNextString()
+ // We're ignoring this packet in MCC
+
+ if (Settings.DebugMessages)
+ {
+ string configName = dataTypes.ReadNextString(packetData);
+ ConsoleIO.WriteLineFormatted("§8Acknowledging FML2 Server Config: " + configName);
+ }
+
+ fmlResponsePacket.AddRange(dataTypes.GetVarInt(99));
+ fmlResponseReady = true;
+ break;
+
+ default:
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Got Unknown FML2 Handshake message no. " + packetID);
+ break;
+ }
+
+ if (fmlResponseReady)
+ {
+ // Wrap our FML packet into a LoginPluginResponse payload
+ responseData.Clear();
+ responseData.AddRange(dataTypes.GetString(fmlChannel));
+ responseData.AddRange(dataTypes.GetVarInt(fmlResponsePacket.Count));
+ responseData.AddRange(fmlResponsePacket);
+ return true;
+ }
+ }
+ else if (Settings.DebugMessages)
+ {
+ ConsoleIO.WriteLineFormatted("§8Ignoring Unknown FML2 LoginMessage channel: " + fmlChannel);
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically. (Forge Protocol version 1: FML)
///
/// Discriminator to use.
/// packet Data
@@ -244,34 +435,52 @@ namespace MinecraftClient.Protocol.Handlers
}
///
- /// Server Info: Check for For Forge on a Minecraft server Ping result
+ /// Server Info: Check for For Forge versions 1 and 2 on a Minecraft server Ping result
///
/// JSON data returned by the server
/// ForgeInfo to populate
/// True if the server is running Forge
public static bool ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo forgeInfo)
{
- return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, "modinfo", "type", "FML") // MC 1.12 and lower
- || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, "forgeData", "fmlNetworkVersion", "2"); // MC 1.13 and greater
+ return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML) // MC 1.12 and lower
+ || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2); // MC 1.13 and greater
}
///
- /// Server Info: Check for For Forge on a Minecraft server Ping result
+ /// Server Info: Check for For Forge on a Minecraft server Ping result (Handles FML and FML2
///
/// JSON data returned by the server
/// ForgeInfo to populate
- /// ForgeData JSON field, e.g. "modinfo"
- /// ForgeData version field, e.g. "type"
- /// ForgeData version value, e.g. "FML"
+ /// Forge protocol version
/// True if the server is running Forge
- private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInfo forgeInfo, string forgeDataTag, string versionField, string versionString)
+ private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInfo forgeInfo, FMLVersion fmlVersion)
{
+ string forgeDataTag;
+ string versionField;
+ string versionString;
+
+ switch (fmlVersion)
+ {
+ case FMLVersion.FML:
+ forgeDataTag = "modinfo";
+ versionField = "type";
+ versionString = "FML";
+ break;
+ case FMLVersion.FML2:
+ forgeDataTag = "forgeData";
+ versionField = "fmlNetworkVersion";
+ versionString = "2";
+ break;
+ default:
+ throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!");
+ }
+
if (jsonData.Properties.ContainsKey(forgeDataTag) && jsonData.Properties[forgeDataTag].Type == Json.JSONData.DataType.Object)
{
Json.JSONData modData = jsonData.Properties[forgeDataTag];
if (modData.Properties.ContainsKey(versionField) && modData.Properties[versionField].StringValue == versionString)
{
- forgeInfo = new ForgeInfo(modData);
+ forgeInfo = new ForgeInfo(modData, fmlVersion);
if (forgeInfo.Mods.Any())
{
ConsoleIO.WriteLineFormatted(String.Format("§8Server is running Forge with {0} mods.", forgeInfo.Mods.Count));