diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs
index fed884a1..e1e5c6bd 100644
--- a/MinecraftClient/McTcpClient.cs
+++ b/MinecraftClient/McTcpClient.cs
@@ -8,6 +8,7 @@ using System.IO;
using System.Net;
using MinecraftClient.Protocol;
using MinecraftClient.Proxy;
+using MinecraftClient.Protocol.Handlers.Forge;
namespace MinecraftClient
{
@@ -54,9 +55,9 @@ namespace MinecraftClient
/// The server port to use
/// Minecraft protocol version to use
- public McTcpClient(string username, string uuid, string sessionID, int protocolversion, string server_ip, ushort port)
+ public McTcpClient(string username, string uuid, string sessionID, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port)
{
- StartClient(username, uuid, sessionID, server_ip, port, protocolversion, false, "");
+ StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, false, "");
}
///
@@ -70,9 +71,9 @@ namespace MinecraftClient
/// Minecraft protocol version to use
/// The text or command to send.
- public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, string command)
+ public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command)
{
- StartClient(username, uuid, sessionID, server_ip, port, protocolversion, true, command);
+ StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, true, command);
}
///
@@ -87,7 +88,7 @@ namespace MinecraftClient
/// If set to true, the client will send a single command and then disconnect from the server
/// The text or command to send. Will only be sent if singlecommand is set to true.
- private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, bool singlecommand, string command)
+ private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command)
{
bool retry = false;
this.sessionid = sessionID;
@@ -113,7 +114,7 @@ namespace MinecraftClient
{
client = ProxyHandler.newTcpClient(host, port);
client.ReceiveBufferSize = 1024 * 1024;
- handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this);
+ handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, forgeInfo, this);
Console.WriteLine("Version is supported.\nLogging in...");
try
diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
index 9e1fc0c7..9c09a5e1 100644
--- a/MinecraftClient/MinecraftClient.csproj
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -114,6 +114,9 @@
+
+
+
diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs
index a9e4575d..302e50bb 100644
--- a/MinecraftClient/Program.cs
+++ b/MinecraftClient/Program.cs
@@ -5,6 +5,7 @@ using System.Text;
using MinecraftClient.Protocol;
using System.Reflection;
using System.Threading;
+using MinecraftClient.Protocol.Handlers.Forge;
namespace MinecraftClient
{
@@ -145,6 +146,7 @@ namespace MinecraftClient
//Get server version
int protocolversion = 0;
+ ForgeInfo forgeInfo = null;
if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto")
{
@@ -166,7 +168,7 @@ namespace MinecraftClient
if (protocolversion == 0)
{
Console.WriteLine("Retrieving Server Info...");
- if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion))
+ if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo))
{
HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost);
return;
@@ -180,9 +182,9 @@ namespace MinecraftClient
//Start the main TCP client
if (Settings.SingleCommand != "")
{
- Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, Settings.SingleCommand);
+ Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand);
}
- else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, Settings.ServerIP, Settings.ServerPort);
+ else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort);
//Update console title
if (Settings.ConsoleTitle != "")
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/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
new file mode 100755
index 00000000..03180432
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Protocol.Handlers.Forge
+{
+ ///
+ /// Contains information about a modded server install.
+ ///
+ public class ForgeInfo
+ {
+ ///
+ /// Represents an individual forge mod.
+ ///
+ public class ForgeMod
+ {
+ public ForgeMod(String ModID, String Version)
+ {
+ this.ModID = ModID;
+ this.Version = Version;
+ }
+
+ public readonly String ModID;
+ public readonly String Version;
+
+ public override string ToString()
+ {
+ return ModID + " v" + Version;
+ }
+ }
+
+ public List Mods;
+
+ ///
+ /// Create a new ForgeInfo from the given data.
+ ///
+ /// The modinfo JSON tag.
+ internal ForgeInfo(Json.JSONData data)
+ {
+ // Example ModInfo (with spacing):
+
+ // "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"
+ // }]
+ // }
+
+ this.Mods = new List();
+ foreach (Json.JSONData mod in data.Properties["modList"].DataArray)
+ {
+ String modid = mod.Properties["modid"].StringValue;
+ String version = mod.Properties["version"].StringValue;
+
+ this.Mods.Add(new ForgeMod(modid, version));
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs
index 1813b9af..89cedbe6 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol16.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs
@@ -615,6 +615,31 @@ namespace MinecraftClient.Protocol.Handlers
return false; //Only supported since MC 1.7
}
+ ///
+ /// Send a plugin channel packet to the server.
+ ///
+ /// Channel to send packet on
+ /// packet Data
+
+ public bool SendPluginChannelPacket(string channel, byte[] data)
+ {
+ try {
+ byte[] channelLength = BitConverter.GetBytes((short)channel.Length);
+ Array.Reverse(channelLength);
+
+ byte[] channelData = Encoding.BigEndianUnicode.GetBytes(channel);
+
+ byte[] dataLength = BitConverter.GetBytes((short)data.Length);
+ Array.Reverse(dataLength);
+
+ Send(concatBytes(new byte[] { 0xFA }, channelLength, channelData, dataLength, data));
+
+ return true;
+ }
+ catch (SocketException) { return false; }
+ catch (System.IO.IOException) { return false; }
+ }
+
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs
index f5fe4071..a9631206 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs
@@ -7,6 +7,7 @@ using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
using System.Security.Cryptography;
+using MinecraftClient.Protocol.Handlers.Forge;
namespace MinecraftClient.Protocol.Handlers
{
@@ -17,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 = "";
@@ -25,18 +26,23 @@ namespace MinecraftClient.Protocol.Handlers
private bool encrypted = false;
private int protocolversion;
+ // Server forge info -- may be null.
+ private ForgeInfo forgeInfo;
+ private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START;
+
IMinecraftComHandler handler;
Thread netRead;
IAesStream s;
TcpClient c;
- public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
+ public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler, ForgeInfo ForgeInfo)
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
+ this.forgeInfo = ForgeInfo;
}
private Protocol18Handler(TcpClient Client)
@@ -133,94 +139,234 @@ namespace MinecraftClient.Protocol.Handlers
return false; //Ignored packet
}
}
- else //Regular in-game packets
+ // Regular in-game packets
+
+ switch (packetID)
{
- 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
+ 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++)
{
- //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++)
+ 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 (protocolversion < MC18Version)
+ {
+ if (forgeInfo == null)
{
- 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);
+ // 1.7 and lower prefix plugin channel packets with the length.
+ // We can skip it, though.
+ readNextShort(ref packetData);
}
- break;
- case 0x3A: //Tab-Complete Result
- int autocomplete_count = readNextVarInt(ref packetData);
- string tab_list = "";
- for (int i = 0; i < autocomplete_count; i++)
+ else
{
- autocomplete_result = readNextString(ref packetData);
- if (autocomplete_result != "")
- tab_list = tab_list + autocomplete_result + " ";
+ // Forge does something even weirder with the length.
+ readNextVarShort(ref packetData);
}
- 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
- }
+ }
+ if (forgeInfo != null)
+ {
+ if (channel == "FML|HS")
+ {
+ FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData);
+
+ if (discriminator == FMLHandshakeDiscriminator.HandshakeReset)
+ {
+ fmlHandshakeState = FMLHandshakeClientState.START;
+ return true;
+ }
+
+ switch (fmlHandshakeState)
+ {
+ case FMLHandshakeClientState.START:
+ 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++)
+ {
+ ForgeInfo.ForgeMod mod = forgeInfo.Mods[i];
+ mods[i] = concatBytes(getString(mod.ModID), getString(mod.Version));
+ }
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList,
+ concatBytes(getVarInt(forgeInfo.Mods.Count), concatBytes(mods)));
+
+ fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA;
+
+ return true;
+ case FMLHandshakeClientState.WAITINGSERVERDATA:
+ if (discriminator != FMLHandshakeDiscriminator.ModList)
+ return false;
+
+ Thread.Sleep(2000);
+
+ ConsoleIO.WriteLineFormatted("§8Accepting server mod list...");
+ // Tell the server that yes, we are OK with the mods it has
+ // even though we don't actually care what mods it has.
+
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
+ new byte[] { (byte)FMLHandshakeClientState.WAITINGSERVERDATA });
+
+ fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE;
+ return false;
+ case FMLHandshakeClientState.WAITINGSERVERCOMPLETE:
+ // The server now will tell us a bunch of registry information.
+ // We need to read it all, though, until it says that there is no more.
+ if (discriminator != FMLHandshakeDiscriminator.RegistryData)
+ return false;
+
+ if (protocolversion < MC18Version)
+ {
+ // 1.7.10 and below have one registry
+ // with blocks and items.
+ int registrySize = readNextVarInt(ref packetData);
+
+ ConsoleIO.WriteLineFormatted("§8Received registry " +
+ "with " + registrySize + " entries");
+
+ fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
+ }
+ else
+ {
+ // 1.8+ has more than one registry.
+
+ bool hasNextRegistry = readNextBool(ref packetData);
+ string registryName = readNextString(ref packetData);
+ int registrySize = readNextVarInt(ref packetData);
+
+ ConsoleIO.WriteLineFormatted("§8Received registry " + registryName +
+ " with " + registrySize + " entries");
+
+ if (!hasNextRegistry)
+ {
+ fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
+ }
+ }
+
+ return false;
+ case FMLHandshakeClientState.PENDINGCOMPLETE:
+ // The server will ask us to accept the registries.
+ // Just say yes.
+ if (discriminator != FMLHandshakeDiscriminator.HandshakeAck)
+ return false;
+
+ ConsoleIO.WriteLineFormatted("§8Accepting server registries...");
+
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
+ new byte[] { (byte)FMLHandshakeClientState.PENDINGCOMPLETE });
+ fmlHandshakeState = FMLHandshakeClientState.COMPLETE;
+
+ return true;
+ case FMLHandshakeClientState.COMPLETE:
+ // One final "OK". On the actual forge source, a packet is sent from
+ // the client to the client saying that the connection was complete, but
+ // we don't need to do that.
+
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
+ new byte[] { (byte)FMLHandshakeClientState.COMPLETE });
+ ConsoleIO.WriteLine("Forge server connection complete!");
+
+ fmlHandshakeState = FMLHandshakeClientState.DONE;
+ return true;
+ }
+ }
+ }
+ return false;
+ 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
}
@@ -258,7 +404,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)
@@ -273,7 +419,7 @@ namespace MinecraftClient.Protocol.Handlers
}
return new byte[] { };
}
-
+
///
/// Read some data from a cache of bytes and remove it from the cache
///
@@ -326,6 +472,18 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToInt16(rawValue, 0);
}
+ ///
+ /// Read an unsigned short integer from a cache of bytes and remove it from the cache
+ ///
+ /// The unsigned short integer value
+
+ private static ushort readNextUShort(ref byte[] cache)
+ {
+ byte[] rawValue = readData(2, ref cache);
+ Array.Reverse(rawValue); //Endianness
+ return BitConverter.ToUInt16(rawValue, 0);
+ }
+
///
/// Read a uuid from a cache of bytes and remove it from the cache
///
@@ -372,7 +530,7 @@ namespace MinecraftClient.Protocol.Handlers
}
return i;
}
-
+
///
/// Read an integer from a cache of bytes and remove it from the cache
///
@@ -396,6 +554,36 @@ namespace MinecraftClient.Protocol.Handlers
return i;
}
+ ///
+ /// Read an "extended short", which is actually an int of some kind, from the cache of bytes.
+ /// This is only done with forge. It looks like it's a normal short, except that if the high
+ /// bit is set, it has an extra byte.
+ ///
+ /// Cache of bytes to read from
+ /// The int
+
+ private static int readNextVarShort(ref byte[] cache)
+ {
+ ushort low = readNextUShort(ref cache);
+ byte high = 0;
+ if ((low & 0x8000) != 0)
+ {
+ low &= 0x7FFF;
+ high = readNextByte(ref cache);
+ }
+ return ((high & 0xFF) << 15) | low;
+ }
+
+ ///
+ /// 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
///
@@ -431,6 +619,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
///
@@ -473,6 +674,17 @@ 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 packet to the server, compression and encryption will be handled automatically
///
@@ -500,7 +712,7 @@ namespace MinecraftClient.Protocol.Handlers
}
}
- SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet));
+ SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet));
}
///
@@ -525,7 +737,7 @@ namespace MinecraftClient.Protocol.Handlers
public bool Login()
{
byte[] protocol_version = getVarInt(protocolversion);
- byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost());
+ byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost() + (forgeInfo != null ? "\0FML\0" : ""));
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port);
byte[] next_state = getVarInt(2);
@@ -560,6 +772,15 @@ namespace MinecraftClient.Protocol.Handlers
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
login_phase = false;
+
+ if (forgeInfo != null) {
+ // Do the forge handshake.
+ if (!CompleteForgeHandshake())
+ {
+ return false;
+ }
+ }
+
StartUpdating();
return true; //No need to check session or start encryption
}
@@ -567,6 +788,33 @@ namespace MinecraftClient.Protocol.Handlers
}
}
+ ///
+ /// Completes the Minecraft Forge handshake.
+ ///
+ /// Whether the handshake was successful.
+ private bool CompleteForgeHandshake()
+ {
+ int packetID = -1;
+ byte[] packetData = new byte[0];
+
+ while (fmlHandshakeState != FMLHandshakeClientState.DONE)
+ {
+ readNextPacket(ref packetID, ref packetData);
+
+ if (packetID == 0x40) // Disconect
+ {
+ handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData)));
+ return false;
+ }
+ else
+ {
+ handlePacket(packetID, packetData);
+ }
+ }
+
+ return true;
+ }
+
///
/// Start network encryption. Automatically called by Login() if the server requests encryption.
///
@@ -614,6 +862,16 @@ namespace MinecraftClient.Protocol.Handlers
else if (packetID == 0x02) //Login successful
{
login_phase = false;
+
+ if (forgeInfo != null)
+ {
+ // Do the forge handshake.
+ if (!CompleteForgeHandshake())
+ {
+ return false;
+ }
+ }
+
StartUpdating();
return true;
}
@@ -669,13 +927,34 @@ namespace MinecraftClient.Protocol.Handlers
{
if (String.IsNullOrEmpty(brandInfo))
return false;
+
+ return SendPluginChannelPacket("MC|Brand", getString(brandInfo));
+ }
+
+ ///
+ /// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically
+ ///
+ /// Channel to send packet on
+ /// packet Data
+
+ public bool SendPluginChannelPacket(string channel, byte[] data)
+ {
try
{
- byte[] channel = Encoding.UTF8.GetBytes("MC|Brand");
- byte[] channelLen = getVarInt(channel.Length);
- byte[] brand = Encoding.UTF8.GetBytes(brandInfo);
- byte[] brandLen = getVarInt(brand.Length);
- SendPacket(0x17, concatBytes(channelLen, channel, brandLen, brand));
+ // In 1.7, length needs to be included.
+ // In 1.8, it must not be.
+ if (protocolversion < MC18Version)
+ {
+ byte[] length = BitConverter.GetBytes((short)data.Length);
+ Array.Reverse(length);
+
+ SendPacket(0x17, concatBytes(getString(channel), length, data));
+ }
+ else
+ {
+ SendPacket(0x17, concatBytes(getString(channel), data));
+ }
+
return true;
}
catch (SocketException) { return false; }
@@ -730,7 +1009,7 @@ namespace MinecraftClient.Protocol.Handlers
///
/// True if ping was successful
- public static bool doPing(string host, int port, ref int protocolversion)
+ public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo)
{
string version = "";
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
@@ -760,26 +1039,43 @@ namespace MinecraftClient.Protocol.Handlers
if (readNextVarInt(ref packetData) == 0x00) //Read Packet ID
{
string result = readNextString(ref packetData); //Get the Json data
+
if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}"))
{
Json.JSONData jsonData = Json.ParseJson(result);
if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version"))
{
- jsonData = jsonData.Properties["version"];
+ Json.JSONData versionData = jsonData.Properties["version"];
//Retrieve display name of the Minecraft version
- if (jsonData.Properties.ContainsKey("name"))
- version = jsonData.Properties["name"].StringValue;
+ if (versionData.Properties.ContainsKey("name"))
+ version = versionData.Properties["name"].StringValue;
//Retrieve protocol version number for handling this server
- if (jsonData.Properties.ContainsKey("protocol"))
- protocolversion = atoi(jsonData.Properties["protocol"].StringValue);
+ if (versionData.Properties.ContainsKey("protocol"))
+ protocolversion = atoi(versionData.Properties["protocol"].StringValue);
//Automatic fix for BungeeCord 1.8 reporting itself as 1.7...
if (protocolversion < 47 && version.Split(' ', '/').Contains("1.8"))
protocolversion = ProtocolHandler.MCVer2ProtocolVersion("1.8.0");
ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ").");
+
+ // Check for forge on the server.
+ if (jsonData.Properties.ContainsKey("modinfo") && jsonData.Properties["modinfo"].Type == Json.JSONData.DataType.Object)
+ {
+ Json.JSONData modData = jsonData.Properties["modinfo"];
+ if (modData.Properties.ContainsKey("type") && modData.Properties["type"].StringValue == "FML")
+ {
+ forgeInfo = new ForgeInfo(modData);
+
+ ConsoleIO.WriteLineFormatted("§8Server is running forge. Mod list:");
+ foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods)
+ {
+ ConsoleIO.WriteLineFormatted("§8 " + mod.ToString());
+ }
+ }
+ }
return true;
}
}
diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs
index 9c351ebc..218f49d4 100644
--- a/MinecraftClient/Protocol/IMinecraftCom.cs
+++ b/MinecraftClient/Protocol/IMinecraftCom.cs
@@ -51,5 +51,16 @@ namespace MinecraftClient.Protocol
/// True if brand info was successfully sent
bool SendBrandInfo(string brandInfo);
+
+ ///
+ /// Send a plugin channel packet to the server.
+ ///
+ /// http://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/
+ ///
+ /// Channel to send packet on
+ /// packet Data
+ /// True if message was successfully sent
+
+ bool SendPluginChannelPacket(string channel, byte[] data);
}
}
diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs
index dcd507d7..067ab4bf 100644
--- a/MinecraftClient/Protocol/ProtocolHandler.cs
+++ b/MinecraftClient/Protocol/ProtocolHandler.cs
@@ -6,6 +6,7 @@ using MinecraftClient.Protocol.Handlers;
using MinecraftClient.Proxy;
using System.Net.Sockets;
using System.Net.Security;
+using MinecraftClient.Protocol.Handlers.Forge;
namespace MinecraftClient.Protocol
{
@@ -23,16 +24,17 @@ namespace MinecraftClient.Protocol
/// Will contain protocol version, if ping successful
/// TRUE if ping was successful
- public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion)
+ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion, ref ForgeInfo forgeInfo)
{
bool success = false;
int protocolversionTmp = 0;
+ ForgeInfo forgeInfoTmp = null;
if (AutoTimeout.Perform(() =>
{
try
{
if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp)
- || Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp))
+ || Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp, ref forgeInfoTmp))
{
success = true;
}
@@ -40,11 +42,12 @@ namespace MinecraftClient.Protocol
}
catch (Exception e)
{
- ConsoleIO.WriteLineFormatted("§8" + e.Message);
+ ConsoleIO.WriteLineFormatted("§8" + e.ToString());
}
}, TimeSpan.FromSeconds(30)))
{
protocolversion = protocolversionTmp;
+ forgeInfo = forgeInfoTmp;
return success;
}
else
@@ -62,14 +65,14 @@ namespace MinecraftClient.Protocol
/// Handler with the appropriate callbacks
///
- public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
+ public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, ForgeInfo forgeInfo, IMinecraftComHandler Handler)
{
int[] supportedVersions_Protocol16 = { 51, 60, 61, 72, 73, 74, 78 };
if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1)
return new Protocol16Handler(Client, ProtocolVersion, Handler);
int[] supportedVersions_Protocol18 = { 4, 5, 47 };
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
- return new Protocol18Handler(Client, ProtocolVersion, Handler);
+ return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo);
throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported.");
}