mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Implement Forge FML2 protocol (MC 1.13+) (#1184)
Forge uses a different handshake scheme in FML2 protocol. This handshake scheme uses LoginPluginRequest/Response packets.
This commit is contained in:
parent
aeac56890b
commit
a28409043c
5 changed files with 346 additions and 91 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the Minecraft Forge handshake.
|
||||
/// Completes the Minecraft Forge handshake (Forge Protocol version 1: FML)
|
||||
/// </summary>
|
||||
/// <returns>Whether the handshake was successful.</returns>
|
||||
public bool CompleteForgeHandshake()
|
||||
{
|
||||
if (ForgeEnabled())
|
||||
if (ForgeEnabled() && forgeInfo.Version == FMLVersion.FML)
|
||||
{
|
||||
int packetID = -1;
|
||||
Queue<byte> packetData = new Queue<byte>();
|
||||
|
|
@ -98,7 +98,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle Forge plugin messages
|
||||
/// Handle Forge plugin messages (Forge Protocol version 1: FML)
|
||||
/// </summary>
|
||||
/// <param name="channel">Plugin message channel</param>
|
||||
/// <param name="packetData">Plugin message data</param>
|
||||
|
|
@ -106,7 +106,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <returns>TRUE if the plugin message was recognized and handled</returns>
|
||||
public bool HandlePluginMessage(string channel, Queue<byte> 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
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
/// <param name="channel">Plugin message channel</param>
|
||||
/// <param name="packetData">Plugin message data</param>
|
||||
/// <param name="responseData">Response data to return to server</param>
|
||||
/// <returns>TRUE/FALSE depending on whether the packet was understood or not</returns>
|
||||
public bool HandleLoginPluginRequest(string channel, Queue<byte> packetData, ref List<byte> 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<byte> fmlResponsePacket = new List<byte>();
|
||||
|
||||
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<string> mods = new List<string>();
|
||||
int modCount = dataTypes.ReadNextVarInt(packetData);
|
||||
for (int i = 0; i < modCount; i++)
|
||||
mods.Add(dataTypes.ReadNextString(packetData));
|
||||
|
||||
Dictionary<string, string> channels = new Dictionary<string, string>();
|
||||
int channelCount = dataTypes.ReadNextVarInt(packetData);
|
||||
for (int i = 0; i < channelCount; i++)
|
||||
channels.Add(dataTypes.ReadNextString(packetData), dataTypes.ReadNextString(packetData));
|
||||
|
||||
List<string> registries = new List<string>();
|
||||
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<string, string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically. (Forge Protocol version 1: FML)
|
||||
/// </summary>
|
||||
/// <param name="discriminator">Discriminator to use.</param>
|
||||
/// <param name="data">packet Data</param>
|
||||
|
|
@ -244,34 +435,52 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="jsonData">JSON data returned by the server</param>
|
||||
/// <param name="forgeInfo">ForgeInfo to populate</param>
|
||||
/// <returns>True if the server is running Forge</returns>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="jsonData">JSON data returned by the server</param>
|
||||
/// <param name="forgeInfo">ForgeInfo to populate</param>
|
||||
/// <param name="forgeDataTag">ForgeData JSON field, e.g. "modinfo"</param>
|
||||
/// <param name="versionField">ForgeData version field, e.g. "type"</param>
|
||||
/// <param name="versionString">ForgeData version value, e.g. "FML"</param>
|
||||
/// <param name="fmlVersion">Forge protocol version</param>
|
||||
/// <returns>True if the server is running Forge</returns>
|
||||
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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue