diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 2e2ad727..9c09a5e1 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -114,6 +114,8 @@ + + diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs new file mode 100755 index 00000000..a72b4ad5 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Protocol.Handlers.Forge +{ + /// + /// Copy of the forge enum for client states. + /// https://github.com/MinecraftForge/MinecraftForge/blob/ebe9b6d4cbc4a5281c386994f1fbda04df5d2e1f/src/main/java/net/minecraftforge/fml/common/network/handshake/FMLHandshakeClientState.java + /// + enum FMLHandshakeClientState : byte + { + START, + HELLO, + WAITINGSERVERDATA, + WAITINGSERVERCOMPLETE, + PENDINGCOMPLETE, + COMPLETE, + DONE, + ERROR + } +} diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs new file mode 100755 index 00000000..2402eff0 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Protocol.Handlers.Forge +{ + /// + /// Different "discriminator byte" values for the forge handshake. + /// https://github.com/MinecraftForge/MinecraftForge/blob/ebe9b6d4cbc4a5281c386994f1fbda04df5d2e1f/src/main/java/net/minecraftforge/fml/common/network/handshake/FMLHandshakeCodec.java + /// + enum FMLHandshakeDiscriminator : byte + { + ServerHello = 0, + ClientHello = 1, + ModList = 2, + RegistryData = 3, + HandshakeAck = 255, //-1 + HandshakeReset = 254, //-2 + } +} diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index c11479a3..3706e44c 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -18,7 +18,7 @@ namespace MinecraftClient.Protocol.Handlers class Protocol18Handler : IMinecraftCom { private const int MC18Version = 47; - + private int compression_treshold = 0; private bool autocomplete_received = false; private string autocomplete_result = ""; @@ -28,6 +28,7 @@ namespace MinecraftClient.Protocol.Handlers // Server forge info -- may be null. private ForgeInfo forgeInfo; + private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START; IMinecraftComHandler handler; Thread netRead; @@ -138,94 +139,153 @@ namespace MinecraftClient.Protocol.Handlers return false; //Ignored packet } } - else //Regular in-game packets + // Regular in-game packets + + if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE) //Check forge login { - switch (packetID) + switch (fmlHandshakeState) { - case 0x00: //Keep-Alive - SendPacket(0x00, packetData); - break; - case 0x01: //Join game - handler.OnGameJoined(); - break; - case 0x02: //Chat message - string message = readNextString(ref packetData); - try + case FMLHandshakeClientState.START: + if (packetID != 0x3F) + break; + + String channel = readNextString(ref packetData); + + if (channel != "FML|HS") + break; + + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); + if (discriminator != FMLHandshakeDiscriminator.ServerHello) + return false; + + // Send the plugin channel registration. + // REGISTER is somewhat special in that it doesn't actually include length information, + // and is also \0-separated. + // Also, yes, "FML" is there twice. Don't ask me why, but that's the way forge does it. + string[] channels = { "FML|HS", "FML", "FML|MP", "FML", "FORGE" }; + SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels))); + + byte fmlProtocolVersion = readNextByte(ref packetData); + // There's another value afterwards for the dimension, but we don't need it. + + ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion); + + // Tell the server we're running the same version. + SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion }); + + // Then tell the server that we're running the same mods. + ConsoleIO.WriteLineFormatted("§8Sending falsified mod list to server..."); + byte[][] mods = new byte[forgeInfo.Mods.Count][]; + for (int i = 0; i < forgeInfo.Mods.Count; i++) { - //Hide system messages or xp bar messages? - byte messageType = readData(1, ref packetData)[0]; - if ((messageType == 1 && !Settings.DisplaySystemMessages) - || (messageType == 2 && !Settings.DisplayXPBarMessages)) - break; + ForgeInfo.ForgeMod mod = forgeInfo.Mods[i]; + mods[i] = concatBytes(getString(mod.ModID), getString(mod.Version)); } - catch (IndexOutOfRangeException) { /* No message type */ } - handler.OnTextReceived(ChatParser.ParseText(message)); - break; - case 0x38: //Player List update - if (protocolversion >= MC18Version) + SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList, concatBytes(getVarInt(forgeInfo.Mods.Count), concatBytes(mods))); + + fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; + + return true; + } + } + + switch (packetID) + { + case 0x00: //Keep-Alive + SendPacket(0x00, packetData); + break; + case 0x01: //Join game + handler.OnGameJoined(); + break; + case 0x02: //Chat message + string message = readNextString(ref packetData); + try + { + //Hide system messages or xp bar messages? + byte messageType = readData(1, ref packetData)[0]; + if ((messageType == 1 && !Settings.DisplaySystemMessages) + || (messageType == 2 && !Settings.DisplayXPBarMessages)) + break; + } + catch (IndexOutOfRangeException) { /* No message type */ } + handler.OnTextReceived(ChatParser.ParseText(message)); + break; + case 0x38: //Player List update + if (protocolversion >= MC18Version) + { + int action = readNextVarInt(ref packetData); + int numActions = readNextVarInt(ref packetData); + for (int i = 0; i < numActions; i++) { - int action = readNextVarInt(ref packetData); - int numActions = readNextVarInt(ref packetData); - for (int i = 0; i < numActions; i++) + Guid uuid = readNextUUID(ref packetData); + switch (action) { - Guid uuid = readNextUUID(ref packetData); - switch (action) - { - case 0x00: //Player Join - string name = readNextString(ref packetData); - handler.OnPlayerJoin(uuid, name); - break; - case 0x04: //Player Leave - handler.OnPlayerLeave(uuid); - break; - default: - //Unknown player list item type - break; - } + case 0x00: //Player Join + string name = readNextString(ref packetData); + handler.OnPlayerJoin(uuid, name); + break; + case 0x04: //Player Leave + handler.OnPlayerLeave(uuid); + break; + default: + //Unknown player list item type + break; } } - else //MC 1.7.X does not provide UUID in tab-list updates + } + else //MC 1.7.X does not provide UUID in tab-list updates + { + string name = readNextString(ref packetData); + bool online = readNextBool(ref packetData); + short ping = readNextShort(ref packetData); + Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); + if (online) + handler.OnPlayerJoin(FakeUUID, name); + else handler.OnPlayerLeave(FakeUUID); + } + break; + case 0x3A: //Tab-Complete Result + int autocomplete_count = readNextVarInt(ref packetData); + string tab_list = ""; + for (int i = 0; i < autocomplete_count; i++) + { + autocomplete_result = readNextString(ref packetData); + if (autocomplete_result != "") + tab_list = tab_list + autocomplete_result + " "; + } + autocomplete_received = true; + tab_list = tab_list.Trim(); + if (tab_list.Length > 0) + ConsoleIO.WriteLineFormatted("§8" + tab_list, false); + break; + case 0x3F: //Plugin message. + String channel = readNextString(ref packetData); + if (channel == "FML|HS") + { + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); + if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) { - string name = readNextString(ref packetData); - bool online = readNextBool(ref packetData); - short ping = readNextShort(ref packetData); - Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); - if (online) - handler.OnPlayerJoin(FakeUUID, name); - else handler.OnPlayerLeave(FakeUUID); + } - break; - case 0x3A: //Tab-Complete Result - int autocomplete_count = readNextVarInt(ref packetData); - string tab_list = ""; - for (int i = 0; i < autocomplete_count; i++) - { - autocomplete_result = readNextString(ref packetData); - if (autocomplete_result != "") - tab_list = tab_list + autocomplete_result + " "; - } - autocomplete_received = true; - tab_list = tab_list.Trim(); - if (tab_list.Length > 0) - ConsoleIO.WriteLineFormatted("§8" + tab_list, false); - break; - case 0x40: //Kick Packet - handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); - return false; - case 0x46: //Network Compression Treshold Info - if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); - break; - case 0x48: //Resource Pack Send - string url = readNextString(ref packetData); - string hash = readNextString(ref packetData); - //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory - SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); - SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); - break; - default: - return false; //Ignored packet - } + return true; + } + break; + case 0x40: //Kick Packet + handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); + return false; + case 0x46: //Network Compression Treshold Info + if (protocolversion >= MC18Version) + compression_treshold = readNextVarInt(ref packetData); + break; + case 0x48: //Resource Pack Send + string url = readNextString(ref packetData); + string hash = readNextString(ref packetData); + //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory + SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); + SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); + break; + default: + return false; //Ignored packet } return true; //Packet processed } @@ -263,7 +323,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Amount of bytes to read /// The data read from the network as an array - + private byte[] readDataRAW(int offset) { if (offset > 0) @@ -278,7 +338,7 @@ namespace MinecraftClient.Protocol.Handlers } return new byte[] { }; } - + /// /// Read some data from a cache of bytes and remove it from the cache /// @@ -377,7 +437,7 @@ namespace MinecraftClient.Protocol.Handlers } return i; } - + /// /// Read an integer from a cache of bytes and remove it from the cache /// @@ -401,6 +461,16 @@ namespace MinecraftClient.Protocol.Handlers return i; } + /// + /// Read a single byte from a cache of bytes and remove it from the cache + /// + /// The byte that was read + + private static byte readNextByte(ref byte[] cache) + { + return readData(1, ref cache)[0]; + } + /// /// Build an integer for sending over the network /// @@ -436,6 +506,19 @@ namespace MinecraftClient.Protocol.Handlers else return concatBytes(getVarInt(array.Length), array); } + /// + /// Get a byte array from the given string for sending over the network, with length information prepended. + /// + /// String to process + /// Array ready to send + + private byte[] getString(string text) + { + byte[] bytes = Encoding.UTF8.GetBytes(text); + + return concatBytes(getVarInt(bytes.Length), bytes); + } + /// /// Easily append several byte arrays /// @@ -478,6 +561,28 @@ namespace MinecraftClient.Protocol.Handlers } } + /// + /// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically + /// + /// Discriminator to use. + /// packet Data + + private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, byte[] data) + { + SendPluginChannelPacket("FML|HS", concatBytes(new byte[] { (byte)discriminator }, data)); + } + + /// + /// Send a plugin channel packet (0x3F) to the server, compression and encryption will be handled automatically + /// + /// Channel to send packet on + /// packet Data + + private void SendPluginChannelPacket(string channel, byte[] data) + { + SendPacket(0x17, concatBytes(getString(channel), data)); + } + /// /// Send a packet to the server, compression and encryption will be handled automatically /// @@ -505,7 +610,7 @@ namespace MinecraftClient.Protocol.Handlers } } - SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet)); + SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet)); } ///