Skip to content

Commit

Permalink
Implement Forge FML3 protocol (MC 1.18+) and Fix yggdrasil-auth chat …
Browse files Browse the repository at this point in the history
…issue

Implement Forge FML3 protocol (MC 1.18+) and Fix yggdrasil-auth chat issue
  • Loading branch information
milutinke authored Jan 14, 2024
2 parents ae5e016 + a8643d8 commit 6faddae
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 28 deletions.
4 changes: 2 additions & 2 deletions MinecraftClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ private static void InitializeClient()
}
}

if (Config.Main.General.AccountType == LoginType.microsoft
if ((Config.Main.General.AccountType == LoginType.microsoft || Config.Main.General.AccountType == LoginType.yggdrasil)
&& (InternalConfig.Account.Password != "-" || Config.Main.General.Method == LoginMethod.browser)
&& Config.Signature.LoginWithSecureProfile
&& protocolversion >= 759 /* 1.19 and above */)
Expand All @@ -582,7 +582,7 @@ private static void InitializeClient()
if (playerKeyPair == null || playerKeyPair.NeedRefresh())
{
ConsoleIO.WriteLineFormatted(Translations.mcc_fetching_key, acceptnewlines: true);
playerKeyPair = KeyUtils.GetNewProfileKeys(session.ID);
playerKeyPair = KeyUtils.GetNewProfileKeys(session.ID, Config.Main.General.AccountType == LoginType.yggdrasil);
if (Config.Main.Advanced.ProfileKeyCache != CacheType.none && playerKeyPair != null)
{
KeysCache.Store(loginLower, playerKeyPair);
Expand Down
3 changes: 2 additions & 1 deletion MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
enum FMLVersion
{
FML,
FML2
FML2,
FML3
}
}
122 changes: 122 additions & 0 deletions MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ internal ForgeInfo(FMLVersion fmlVersion)
};
Version = fmlVersion;
break;
case FMLVersion.FML3:
Mods = new List<ForgeMod>
{
new ForgeMod("forge", "ANY")
};
Version = fmlVersion;
break;
default:
throw new InvalidOperationException(Translations.error_forgeforce);
}
Expand Down Expand Up @@ -133,10 +140,125 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion)
}

break;
case FMLVersion.FML3:
// Example ModInfo for Minecraft 1.18 and greater (FML3)

// {
// "enforcesSecureChat": true,
// "forgeData": {
// "channels": [],
// "mods": [],
// "truncated": false, // legacy versions see truncated lists, modern versions ignore this truncated flag (binary data has its own)
// "fmlNetworkVersion": 3,
// "d": "ȳ\u0000\u0000ࠨ㐤獋㙖⹌ᦘ̺⸱恤䒸⡑⛧沮婙㨹牥ఈㄵচ₀沮婙㨹牥ఈㄵচ倠⹡岙㜲獥䋊㷍᭳ႇׇ஌᜘㘴娘▅筳ص䰭宛㘲、\u0000ᠸጋ囗湌夜㘲杩棐䐱ᅱ挃☥ోᤗ㌮ఀ׈䬣 坖ɍ䮌ᤘ\r\n旉䠳ዣ◆䲌㜃瑥廮ⷉࠋ–䁠奚Ҵ㔱摜䂸ᅱ獳ౠᡚ㜷汥戊䂸űဓĠ嵛㖱数嫤Ǎ塰䛶ⶎᮚ㞳晲擞ᖝ″ዣ䘆ఋʂ潦令ඕ爈䖔⺁ᥚ⾹潳棤㦥ᬻ挐؅䅀㠹楬ۨ㣄উ瀀渀嬛㘼扩搢䃀熁挂♥\r\n墋㒺摬牜ࣜ䁠嘗湌孛㜴浩惂䠙熙排٥孁㒰ͮ屢Ӏ䠐⚐䷮ᣛ㊴瑳戚䢸熁匒إ஍᜚ܴ䫜巑፻ᚷؠ䀀ㆃ牵䋨㦥ࠫ㋣䗆䂌㨈慲䫬ᖱᮓᘧ汬尚ㆰ٫屲㣄ᆉ恳ಭ川㤷፫擨妅挫♖乮塘 㖱慰\r\n囆䓩\t"
// },
// "description": {
// "text": "A Minecraft Server"
// },
// "players": {
// "max": 100,
// "online": 0
// },
// "version": {
// "name": "1.20.1",
// "protocol": 763
// }
// }

// All buffer data are encoded and write to forgeData["d"]
// https://github.com/MinecraftForge/MinecraftForge/blob/cb12df41e13da576b781be695f80728b9594c25f/src/main/java/net/minecraftforge/network/ServerStatusPing.java#L264

// 1.18 and greater, the buffer is encoded for efficiency
// see https://github.com/MinecraftForge/MinecraftForge/pull/8169

string encodedData = data.Properties["d"].StringValue;
Queue<byte> dataPackage = decodeOptimized(encodedData);
DataTypes dataTypes = new DataTypes(Protocol18Handler.MC_1_18_1_Version);

//
// [truncated][boolean] placeholder for whether we are truncating
// [Mod Size][unsigned short] short so that we can replace it later in case of truncation
//
bool truncated = dataTypes.ReadNextBool(dataPackage);
var modsSize = dataTypes.ReadNextUShort(dataPackage);

Dictionary<string, string> channels = new();
Dictionary<string, string> mods = new();

for (var i = 0; i < modsSize; i++) {
var channelSizeAndVersionFlag = dataTypes.ReadNextVarInt(dataPackage);
var channelSize = channelSizeAndVersionFlag >> 1;

int VERSION_FLAG_IGNORESERVERONLY = 0b1;
var isIgnoreServerOnly = (channelSizeAndVersionFlag & VERSION_FLAG_IGNORESERVERONLY) != 0;

var modId = dataTypes.ReadNextString(dataPackage);

string IGNORESERVERONLY = "";// it was "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
var modVersion = isIgnoreServerOnly ? IGNORESERVERONLY : dataTypes.ReadNextString(dataPackage);

for (var i1 = 0; i1 < channelSize; i1++) {
var channelName = dataTypes.ReadNextString(dataPackage);
var channelVersion = dataTypes.ReadNextString(dataPackage);
var requiredOnClient = dataTypes.ReadNextBool(dataPackage);
channels.Add(modId + ":" + channelName, channelVersion + ":" + requiredOnClient);
}

mods.Add(modId, modVersion);
Mods.Add(new ForgeMod(modId, modVersion));
}

var nonModChannelCount = dataTypes.ReadNextVarInt(dataPackage);
for (var i = 0; i < nonModChannelCount; i++) {
var channelName = dataTypes.ReadNextString(dataPackage);
var channelVersion = dataTypes.ReadNextString(dataPackage);
var requiredOnClient = dataTypes.ReadNextBool(dataPackage);
channels.Add(channelName, channelVersion + ":" + requiredOnClient);
}

break;
default:
throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!");
}
}

// https://github.com/MinecraftForge/MinecraftForge/blob/cb12df41e13da576b781be695f80728b9594c25f/src/main/java/net/minecraftforge/network/ServerStatusPing.java#L361
// Decode binary data ForgeData["d"] to Queue<byte>
private static Queue<byte> decodeOptimized(string encodedData) {
// Console.WriteLine("Got encoded data:" + encodedData + ", decoding...");
int size0 = encodedData[0];
int size1 = encodedData[1];
int size = size0 | (size1 << 15);

List<byte> packageData = new();

int stringIndex = 2;
int buffer = 0;
int bitsInBuf = 0;

while (stringIndex < encodedData.Length)
{
while (bitsInBuf >= 8)
{
packageData.Add((byte)buffer);
buffer >>= 8;
bitsInBuf -= 8;
}

char c = encodedData[stringIndex];
buffer |= (c & 0x7FFF) << bitsInBuf;
bitsInBuf += 15;
stringIndex++;
}

while (packageData.Count < size)
{
packageData.Add((byte)buffer);
buffer >>= 8;
bitsInBuf -= 8;
}

return new Queue<byte>(packageData.ToArray());
}
}
}
102 changes: 89 additions & 13 deletions MinecraftClient/Protocol/Handlers/Protocol18Forge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,23 +233,27 @@ public bool HandlePluginMessage(string channel, Queue<byte> packetData, ref int
}

/// <summary>
/// Handle Forge plugin messages during login phase (Forge Protocol version 2: FML2)
/// Handle Forge plugin messages during login phase (Forge Protocol version 2: FML2 or Forge Protocol version 3: FML3)
/// </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")
if (ForgeEnabled() && (forgeInfo!.Version == FMLVersion.FML2 || forgeInfo!.Version == FMLVersion.FML3) && 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
// https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java
// https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java
// https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/NetworkInitialization.java
// https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java
// https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java
//
// FML3 packets:
// https://github.com/MinecraftForge/MinecraftForge/blob/1.18.x/src/main/java/net/minecraftforge/network/NetworkInitialization.java
// https://github.com/MinecraftForge/MinecraftForge/blob/1.18.x/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:
//
Expand All @@ -274,6 +278,8 @@ public bool HandleLoginPluginRequest(string channel, Queue<byte> packetData, ref
// 2 = Client to Server - Mod List
// 3 = Server to Client - Registry
// 4 = Server to Client - Config
// 5 = Server to Client - Mod Data List (FML3)
// 6 = Server to Client - MismatchedMod List (FML3)
//
// 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.
Expand Down Expand Up @@ -320,6 +326,15 @@ public bool HandleLoginPluginRequest(string channel, Queue<byte> packetData, ref
for (int i = 0; i < registryCount; i++)
registries.Add(dataTypes.ReadNextString(packetData));

// FML3 specific,
List<string> dataPackRegistries = new();
if (forgeInfo!.Version == FMLVersion.FML3 && packetData.Count != 0)
{
int dataPackRegistryCount = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < dataPackRegistryCount; i++)
dataPackRegistries.Add(dataTypes.ReadNextString(packetData));
}

// Server Mod List Reply: FMLHandshakeMessages.java > C2SModListReply > encode()
//
// [ Mod Count ][ VarInt ]
Expand Down Expand Up @@ -375,7 +390,7 @@ public bool HandleLoginPluginRequest(string channel, Queue<byte> packetData, ref
string registryName = dataTypes.ReadNextString(packetData);
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_registry, registryName));
}

fmlResponsePacket.AddRange(DataTypes.GetVarInt(99));
fmlResponseReady = true;
break;
Expand All @@ -399,6 +414,53 @@ public bool HandleLoginPluginRequest(string channel, Queue<byte> packetData, ref
fmlResponseReady = true;
break;

case 5:
// FML 3
// Server Config: FMLHandshakeMessages.java > S2CModData > decode()
//
// We're ignoring this packet in MCC

/*
// Uncomment this code block if needed
var size = dataTypes.ReadNextVarInt(packetData);
Dictionary<string, string> modsData = new();
for (int i = 0; i < size; i++)
{
var modId = dataTypes.ReadNextString(packetData);
var displayName = dataTypes.ReadNextString(packetData);
var version = dataTypes.ReadNextString(packetData);
modsData.Add(modId, displayName + ":" + version);
}
*/
if (Settings.Config.Logging.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§8" + "Received FML3 Server Mod Data List");
}
break;

case 6:
// FML 3
// Server Config: FMLHandshakeMessages.java > S2CChannelMismatchData > decode()
//
// We're ignoring this packet in MCC

/*
// Uncomment this code block if needed
Dictionary<string, string> mismatchedMods = new();
var size0 = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < size0; i++)
{
var modId = dataTypes.ReadNextString(packetData);
var version = dataTypes.ReadNextString(packetData);
mismatchedMods.Add(modId, version);
}
*/
if (Settings.Config.Logging.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§8" + "Received FML3 Server Mismatched Mods List");
}
break;

default:
if (Settings.Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_unknown, packetID));
Expand Down Expand Up @@ -442,7 +504,8 @@ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, b
public static bool ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo? forgeInfo)
{
return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML) // MC 1.12 and lower
|| ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2); // MC 1.13 and greater
|| ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2) // MC 1.13 to 1.17
|| ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML3); // MC 1.18 and greater
}

/// <summary>
Expand All @@ -463,14 +526,22 @@ public static bool ServerMayForceForge(int protocolVersion)
public static ForgeInfo ServerForceForge(int protocolVersion)
{
if (ServerMayForceForge(protocolVersion))
{
return new ForgeInfo(FMLVersion.FML2);
{
// 1.17 is still FML2
// https://github.com/MinecraftForge/MinecraftForge/blob/50b5414033de82f46be23201db50484f36c37d4f/src/main/java/net/minecraftforge/fmllegacy/network/FMLNetworkConstants.java#L37C29-L37C42
// 1.18 change the constant FMLNETVERSION to 3
// https://github.com/MinecraftForge/MinecraftForge/blob/cb12df41e13da576b781be695f80728b9594c25f/src/main/java/net/minecraftforge/network/NetworkConstants.java#L28
if (protocolVersion > ProtocolHandler.MCVer2ProtocolVersion("1.18"))
{
return new ForgeInfo(FMLVersion.FML3);
}
return new ForgeInfo(FMLVersion.FML2);
}
else throw new InvalidOperationException(Translations.error_forgeforce);
}

/// <summary>
/// Server Info: Check for For Forge on a Minecraft server Ping result (Handles FML and FML2
/// Server Info: Check for For Forge on a Minecraft server Ping result (Handles FML and FML2 and FML3
/// </summary>
/// <param name="jsonData">JSON data returned by the server</param>
/// <param name="forgeInfo">ForgeInfo to populate</param>
Expand All @@ -494,6 +565,11 @@ private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInf
versionField = "fmlNetworkVersion";
versionString = "2";
break;
case FMLVersion.FML3:
forgeDataTag = "forgeData";
versionField = "fmlNetworkVersion";
versionString = "3";
break;
default:
throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!");
}
Expand Down Expand Up @@ -523,6 +599,6 @@ private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInf
}
}
return false;
}
}
}
}
Loading

0 comments on commit 6faddae

Please sign in to comment.