From d44fc22c8ab27b9032a510402514938326da7795 Mon Sep 17 00:00:00 2001 From: Anon Date: Sun, 22 Mar 2026 22:19:59 +0100 Subject: [PATCH] Fix offline Microsoft auth profile-key checks and complete status ping/pong during auto-version detection. --- MinecraftClient/Program.cs | 3 +- .../Protocol/Handlers/Protocol18.cs | 176 +++++++++++------- .../Protocol/ProfileKey/KeyUtils.cs | 20 ++ 3 files changed, 126 insertions(+), 73 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index cd6716eb..e5ced59f 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -596,7 +596,8 @@ namespace MinecraftClient 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 */) + && protocolversion >= 759 /* 1.19 and above */ + && !string.IsNullOrWhiteSpace(session.ID)) { // Load cached profile key from disk if necessary if (Config.Main.Advanced.ProfileKeyCache == CacheType.disk) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index ab70ae6d..579dd925 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -3520,81 +3520,113 @@ namespace MinecraftClient.Protocol.Handlers /// True if ping was successful public static bool DoPing(string host, int port, ref int protocolVersion, ref ForgeInfo? forgeInfo) { - var version = ""; - var tcp = ProxyHandler.NewTcpClient(host, port); - tcp.ReceiveTimeout = 30000; // 30 seconds - tcp.ReceiveBufferSize = 1024 * 1024; - SocketWrapper socketWrapper = new(tcp); - DataTypes dataTypes = new(MC_1_8_Version); + SocketWrapper? socketWrapper = null; - var serverPort = BitConverter.GetBytes((ushort)port); - Array.Reverse(serverPort); - - // Ping Packet - var pingPacket = dataTypes.ConcatBytes( - // Packet Id - DataTypes.GetVarInt(0), - - // Protocol Version - DataTypes.GetVarInt(-1), - - // Server IP (Host) - dataTypes.GetString(host), - - // Server port - serverPort, - - // Next State - DataTypes.GetVarInt(1)); - - socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(pingPacket.Length), pingPacket)); - - // Status Request Packet - var statusRequest = DataTypes.GetVarInt(0); - socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(statusRequest.Length), statusRequest)); - - // Read Response length - var packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); - if (packetLength <= 0) return false; - - // Read the Packet Id - var packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); - if (dataTypes.ReadNextVarInt(packetData) != 0x00) return false; - - var result = dataTypes.ReadNextString(packetData); // Get the Json data - - if (Config.Logging.DebugMessages) + try { - // May contain formatting codes, cannot use WriteLineFormatted - Console.ForegroundColor = ConsoleColor.DarkGray; - ConsoleIO.WriteLine(result); - Console.ForegroundColor = ConsoleColor.Gray; + var version = ""; + var tcp = ProxyHandler.NewTcpClient(host, port); + tcp.ReceiveTimeout = 30000; // 30 seconds + tcp.ReceiveBufferSize = 1024 * 1024; + socketWrapper = new SocketWrapper(tcp); + DataTypes dataTypes = new(MC_1_8_Version); + + var serverPort = BitConverter.GetBytes((ushort)port); + Array.Reverse(serverPort); + + // Ping Packet + var pingPacket = dataTypes.ConcatBytes( + // Packet Id + DataTypes.GetVarInt(0), + + // Protocol Version + DataTypes.GetVarInt(-1), + + // Server IP (Host) + dataTypes.GetString(host), + + // Server port + serverPort, + + // Next State + DataTypes.GetVarInt(1)); + + socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(pingPacket.Length), pingPacket)); + + // Status Request Packet + var statusRequest = DataTypes.GetVarInt(0); + socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(statusRequest.Length), statusRequest)); + + // Read Response length + var packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); + if (packetLength <= 0) + return false; + + // Read the Packet Id + var packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); + if (dataTypes.ReadNextVarInt(packetData) != 0x00) + return false; + + var result = dataTypes.ReadNextString(packetData); // Get the Json data + + if (Config.Logging.DebugMessages) + { + // May contain formatting codes, cannot use WriteLineFormatted + Console.ForegroundColor = ConsoleColor.DarkGray; + ConsoleIO.WriteLine(result); + Console.ForegroundColor = ConsoleColor.Gray; + } + + if (string.IsNullOrEmpty(result) || !result.StartsWith("{") || !result.EndsWith("}")) + return false; + + var jsonData = Json.ParseJson(result); + if (jsonData is not System.Text.Json.Nodes.JsonObject jsonObj || !jsonObj.ContainsKey("version")) + return false; + + var versionData = jsonObj["version"]!.AsObject(); + + // Retrieve display name of the Minecraft version + if (versionData["name"] is { } nameNode) + version = nameNode.GetStringValue(); + + // Retrieve protocol version number for handling this server + if (versionData["protocol"] is { } protocolNode) + protocolVersion = int.Parse(protocolNode.GetStringValue(), + NumberStyles.Any, CultureInfo.CurrentCulture); + + // Check for forge on the server. + Protocol18Forge.ServerInfoCheckForge(jsonObj, ref forgeInfo); + + // Complete the normal status exchange so the probe connection closes cleanly server-side. + try + { + long pingPayload = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var pingRequest = dataTypes.ConcatBytes(DataTypes.GetVarInt(0x01), DataTypes.GetLong(pingPayload)); + socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(pingRequest.Length), pingRequest)); + + packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); + if (packetLength > 0) + { + packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); + if (dataTypes.ReadNextVarInt(packetData) == 0x01) + dataTypes.ReadNextLong(packetData); + } + } + catch + { + // Some servers may close the probe connection immediately after the status response. + } + + ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.mcc_server_protocol, version, + protocolVersion + (forgeInfo != null ? Translations.mcc_with_forge : ""))); + + return true; + } + finally + { + socketWrapper?.Disconnect(); } - - if (string.IsNullOrEmpty(result) || !result.StartsWith("{") || !result.EndsWith("}")) return false; - - var jsonData = Json.ParseJson(result); - if (jsonData is not System.Text.Json.Nodes.JsonObject jsonObj || !jsonObj.ContainsKey("version")) - return false; - - var versionData = jsonObj["version"]!.AsObject(); - - //Retrieve display name of the Minecraft version - if (versionData["name"] is { } nameNode) - version = nameNode.GetStringValue(); - - //Retrieve protocol version number for handling this server - if (versionData["protocol"] is { } protocolNode) - protocolVersion = int.Parse(protocolNode.GetStringValue(), - NumberStyles.Any, CultureInfo.CurrentCulture); - - // Check for forge on the server. - Protocol18Forge.ServerInfoCheckForge(jsonObj, ref forgeInfo); - - ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.mcc_server_protocol, version, - protocolVersion + (forgeInfo != null ? Translations.mcc_with_forge : ""))); - - return true; } /// diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index ba5e5610..604385b0 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -20,6 +20,9 @@ namespace MinecraftClient.Protocol.ProfileKey ProxiedWebRequest.Response? response = null; try { + if (!isYggdrasil && string.IsNullOrWhiteSpace(accessToken)) + return null; + if (!isYggdrasil) { var request = new ProxiedWebRequest(certificates) @@ -34,11 +37,28 @@ namespace MinecraftClient.Protocol.ProfileKey { ConsoleIO.WriteLine(response.Body.ToString()); } + + if (response.StatusCode < 200 || response.StatusCode >= 300) + { + throw new InvalidOperationException(string.IsNullOrWhiteSpace(response.Body) + ? "Certificate endpoint returned an error response." + : response.Body); + } } // see https://github.com/yushijinhun/authlib-injector/blob/da910956eaa30d2f6c2c457222d188aeb53b0d1f/src/main/java/moe/yushi/authlibinjector/httpd/ProfileKeyFilter.java#L49 // POST to "https://api.minecraftservices.com/player/certificates" with authlib-injector will get a dummy response var json = isYggdrasil ? MakeDummyResponse() : Json.ParseJson(response!.Body); + if (json?["keyPair"]?["publicKey"] == null + || json["keyPair"]?["privateKey"] == null + || json["publicKeySignature"] == null + || json["publicKeySignatureV2"] == null + || json["expiresAt"] == null + || json["refreshedAfter"] == null) + { + throw new InvalidOperationException("Certificate endpoint returned an unexpected payload."); + } + // Error here PublicKey publicKey = new(pemKey: json!["keyPair"]!["publicKey"]!.GetStringValue(), sig: json["publicKeySignature"]!.GetStringValue(),