using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Protocol.Handlers.Forge;
using System.Threading;
namespace MinecraftClient.Protocol.Handlers
{
///
/// Handler for the Minecraft Forge protocol
///
class Protocol18Forge
{
private int protocolversion;
private DataTypes dataTypes;
private Protocol18Handler protocol18;
private IMinecraftComHandler mcHandler;
private ForgeInfo forgeInfo;
private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START;
private bool ForgeEnabled() { return forgeInfo != null; }
///
/// Initialize a new Forge protocol handler
///
/// Forge Server Information
/// Minecraft protocol version
/// Minecraft data types handler
public Protocol18Forge(ForgeInfo forgeInfo, int protocolVersion, DataTypes dataTypes, Protocol18Handler protocol18, IMinecraftComHandler mcHandler)
{
this.forgeInfo = forgeInfo;
this.protocolversion = protocolVersion;
this.dataTypes = dataTypes;
this.protocol18 = protocol18;
this.mcHandler = mcHandler;
}
///
/// Get Forge-Tagged server address
///
/// Server Address
/// Forge-Tagged server address
public string GetServerAddress(string serverAddress)
{
if (ForgeEnabled())
return serverAddress + "\0FML\0";
return serverAddress;
}
///
/// Completes the Minecraft Forge handshake.
///
/// Whether the handshake was successful.
public bool CompleteForgeHandshake()
{
if (ForgeEnabled())
{
int packetID = -1;
Queue packetData = new Queue();
while (fmlHandshakeState != FMLHandshakeClientState.DONE)
{
protocol18.ReadNextPacket(ref packetID, packetData);
if (packetID == 0x40) // Disconnect
{
mcHandler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
return false;
}
else
{
// Send back regular packet to the vanilla protocol handler
protocol18.HandlePacket(packetID, packetData);
}
}
}
return true;
}
///
/// Read Forge VarShort field
///
/// Packet data to read from
/// Length from packet data
public int ReadNextVarShort(Queue packetData)
{
if (ForgeEnabled())
{
// Forge special VarShort field.
return (int)dataTypes.ReadNextVarShort(packetData);
}
else
{
// Vanilla regular Short field
return (int)dataTypes.ReadNextShort(packetData);
}
}
///
/// Handle Forge plugin messages
///
/// Plugin message channel
/// Plugin message data
/// Current world dimension
/// 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 (channel == "FML|HS")
{
FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)dataTypes.ReadNextByte(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" };
protocol18.SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels)));
byte fmlProtocolVersion = dataTypes.ReadNextByte(packetData);
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion);
if (fmlProtocolVersion >= 1)
currentDimension = dataTypes.ReadNextInt(packetData);
// 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.
if (Settings.DebugMessages)
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] = dataTypes.ConcatBytes(dataTypes.GetString(mod.ModID), dataTypes.GetString(mod.Version));
}
SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList,
dataTypes.ConcatBytes(dataTypes.GetVarInt(forgeInfo.Mods.Count), dataTypes.ConcatBytes(mods)));
fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA;
return true;
case FMLHandshakeClientState.WAITINGSERVERDATA:
if (discriminator != FMLHandshakeDiscriminator.ModList)
return false;
Thread.Sleep(2000);
if (Settings.DebugMessages)
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 < Protocol18Handler.MC18Version)
{
// 1.7.10 and below have one registry
// with blocks and items.
int registrySize = dataTypes.ReadNextVarInt(packetData);
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted("§8Received registry with " + registrySize + " entries");
fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
}
else
{
// 1.8+ has more than one registry.
bool hasNextRegistry = dataTypes.ReadNextBool(packetData);
string registryName = dataTypes.ReadNextString(packetData);
int registrySize = dataTypes.ReadNextVarInt(packetData);
if (Settings.DebugMessages)
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;
if (Settings.DebugMessages)
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 });
if (Settings.DebugMessages)
ConsoleIO.WriteLine("Forge server connection complete!");
fmlHandshakeState = FMLHandshakeClientState.DONE;
return true;
}
}
}
return false;
}
///
/// 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)
{
protocol18.SendPluginChannelPacket("FML|HS", dataTypes.ConcatBytes(new byte[] { (byte)discriminator }, data));
}
///
/// Server Info: Check for For Forge on a Minecraft server Ping result
///
/// JSON data returned by the server
/// ForgeInfo to populate
public static void ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo forgeInfo)
{
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);
if (forgeInfo.Mods.Any())
{
if (Settings.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§8Server is running Forge. Mod list:");
foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods)
{
ConsoleIO.WriteLineFormatted("§8 " + mod.ToString());
}
}
else ConsoleIO.WriteLineFormatted("§8Server is running Forge.");
}
else forgeInfo = null;
}
}
}
}
}