From 621e5e22004d895c0e4f2bb6abae3005d2035e22 Mon Sep 17 00:00:00 2001 From: LesterLian <32392052+LesterLian@users.noreply.github.com> Date: Mon, 12 Apr 2021 07:46:33 -0400 Subject: [PATCH] Implement Realms support (#1533) Resolve #51 * Realms: update to new API; fix HTTP Get * Realms: suggested changes Co-authored-by: ORelio * Realms: suggested changes Co-authored-by: ORelio * Add negative number support for JSON parser * Nice print realms worlds result * Option to join Realms world with world ID * Suggested changes Co-authored-by: ORelio * Failure handle Co-authored-by: ORelio * world id paired with index * fix text * Clean up the code a bit * Add setting for displaying Realms worlds * Rename Realms worlds setting * Put messages into translation file Co-authored-by: Zizhen Lian Co-authored-by: ORelio Co-authored-by: ReinforceZwei <39955851+ReinforceZwei@users.noreply.github.com> --- MinecraftClient/Json.cs | 4 +- MinecraftClient/Program.cs | 42 ++++++++- MinecraftClient/Protocol/ProtocolHandler.cs | 85 +++++++++++++++++-- .../Resources/config/MinecraftClient.ini | 1 + MinecraftClient/Resources/lang/en.ini | 6 ++ MinecraftClient/Settings.cs | 2 + 6 files changed, 129 insertions(+), 11 deletions(-) diff --git a/MinecraftClient/Json.cs b/MinecraftClient/Json.cs index bf752da2..1904f494 100644 --- a/MinecraftClient/Json.cs +++ b/MinecraftClient/Json.cs @@ -137,10 +137,10 @@ namespace MinecraftClient break; //Number - case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': case '-': data = new JSONData(JSONData.DataType.String); StringBuilder sb = new StringBuilder(); - while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.') + while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.' || toparse[cursorpos] == '-') { sb.Append(toparse[cursorpos]); cursorpos++; diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index b831e5a5..23b0bde4 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -219,6 +219,7 @@ namespace MinecraftClient if (result == ProtocolHandler.LoginResult.Success) { Settings.Username = session.PlayerName; + bool isRealms = false; if (Settings.ConsoleTitle != "") Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); @@ -229,12 +230,45 @@ namespace MinecraftClient if (Settings.DebugMessages) Translations.WriteLine("debug.session_id", session.ID); - //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE + List availableWorlds = new List(); + if (Settings.MinecraftRealmsEnabled && !String.IsNullOrEmpty(session.ID)) + availableWorlds = ProtocolHandler.RealmsListWorlds(Settings.Username, session.PlayerID, session.ID); if (Settings.ServerIP == "") { Translations.Write("mcc.ip"); - Settings.SetServerIP(Console.ReadLine()); + string addressInput = Console.ReadLine(); + if (addressInput.StartsWith("realms:")) + { + if (Settings.MinecraftRealmsEnabled) + { + if (availableWorlds.Count == 0) + { + HandleFailure(Translations.Get("error.realms.access_denied"), false, ChatBot.DisconnectReason.LoginRejected); + return; + } + int worldIndex = Convert.ToUInt16(addressInput.Split(':')[1]); + string worldId = availableWorlds[worldIndex]; + string RealmsAddress = ProtocolHandler.GetRealmsWorldServerAddress(worldId, Settings.Username, session.PlayerID, session.ID); + if (RealmsAddress != "") + { + addressInput = RealmsAddress; + isRealms = true; + Settings.ServerVersion = MCHighestVersion; + } + else + { + HandleFailure(Translations.Get("error.realms.server_unavailable"), false, ChatBot.DisconnectReason.LoginRejected); + return; + } + } + else + { + HandleFailure(Translations.Get("error.realms.disabled"), false, null); + return; + } + } + Settings.SetServerIP(addressInput); } //Get server version @@ -259,7 +293,7 @@ namespace MinecraftClient } //Retrieve server info if version is not manually set OR if need to retrieve Forge information - if (protocolversion == 0 || Settings.ServerAutodetectForge || (Settings.ServerForceForge && !ProtocolHandler.ProtocolMayForceForge(protocolversion))) + if (!isRealms && (protocolversion == 0 || Settings.ServerAutodetectForge || (Settings.ServerForceForge && !ProtocolHandler.ProtocolMayForceForge(protocolversion)))) { if (protocolversion != 0) Translations.WriteLine("mcc.forge"); @@ -272,7 +306,7 @@ namespace MinecraftClient } //Force-enable Forge support? - if (Settings.ServerForceForge && forgeInfo == null) + if (!isRealms && Settings.ServerForceForge && forgeInfo == null) { if (ProtocolHandler.ProtocolMayForceForge(protocolversion)) { diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index d3009260..e7c59a71 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -675,14 +675,88 @@ namespace MinecraftClient.Protocol catch { return false; } } - //Test method currently not working - //See https://github.com/ORelio/Minecraft-Console-Client/issues/51 - public static void RealmsListWorlds(string username, string uuid, string accesstoken) + /// + /// Retrieve available Realms worlds of a player and display them + /// + /// Player Minecraft username + /// Player UUID + /// Access token + /// List of ID of available Realms worlds + public static List RealmsListWorlds(string username, string uuid, string accesstoken) { string result = ""; string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username, Program.MCHighestVersion); - DoHTTPSGet("mcoapi.minecraft.net", "/worlds", cookies, ref result); - Console.WriteLine(result); + DoHTTPSGet("pc.realms.minecraft.net", "/worlds", cookies, ref result); + Json.JSONData realmsWorlds = Json.ParseJson(result); + List realmsWorldsResult = new List(); // Store world ID + if (realmsWorlds.Properties.ContainsKey("servers") + && realmsWorlds.Properties["servers"].Type == Json.JSONData.DataType.Array + && realmsWorlds.Properties["servers"].DataArray.Count > 0) + { + List availableWorlds = new List(); // Store string to print + int index = 0; + foreach (Json.JSONData realmsServer in realmsWorlds.Properties["servers"].DataArray) + { + if (realmsServer.Properties.ContainsKey("name") + && realmsServer.Properties.ContainsKey("owner") + && realmsServer.Properties.ContainsKey("id") + && realmsServer.Properties.ContainsKey("daysLeft")) + { + int daysLeft; + if (int.TryParse(realmsServer.Properties["daysLeft"].StringValue, out daysLeft)) + { + if (daysLeft > 0) + { + availableWorlds.Add(String.Format("[{0}] {2} ({3}) - {1}", + index++, + realmsServer.Properties["id"].StringValue, + realmsServer.Properties["name"].StringValue, + realmsServer.Properties["owner"].StringValue)); + realmsWorldsResult.Add(realmsServer.Properties["id"].StringValue); + } + } + } + } + if (availableWorlds.Count > 0) + { + Translations.WriteLine("mcc.realms_available"); + foreach (var world in availableWorlds) + ConsoleIO.WriteLine(world); + Translations.WriteLine("mcc.realms_join"); + } + } + return realmsWorldsResult; + } + + /// + /// Get the server address of a Realms world by world ID + /// + /// The world ID of the Realms world + /// Player Minecraft username + /// Player UUID + /// Access token + /// Server address (host:port) or empty string if failure + public static string GetRealmsWorldServerAddress(string worldId, string username, string uuid, string accesstoken) + { + string result = ""; + string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username, Program.MCHighestVersion); + int statusCode = DoHTTPSGet("pc.realms.minecraft.net", "/worlds/v1/" + worldId + "/join/pc", cookies, ref result); + if (statusCode == 200) + { + Json.JSONData serverAddress = Json.ParseJson(result); + if (serverAddress.Properties.ContainsKey("address")) + return serverAddress.Properties["address"].StringValue; + else + { + Translations.WriteLine("error.realms.ip_error"); + return ""; + } + } + else + { + Translations.WriteLine("error.realms.access_denied"); + return ""; + } } /// @@ -705,6 +779,7 @@ namespace MinecraftClient.Protocol http_request.Add("Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7"); http_request.Add("Connection: close"); http_request.Add(""); + http_request.Add(""); return DoHTTPSRequest(http_request, host, ref result); } diff --git a/MinecraftClient/Resources/config/MinecraftClient.ini b/MinecraftClient/Resources/config/MinecraftClient.ini index 6315c9cf..192f2216 100644 --- a/MinecraftClient/Resources/config/MinecraftClient.ini +++ b/MinecraftClient/Resources/config/MinecraftClient.ini @@ -39,6 +39,7 @@ exitonfailure=false # Disable pauses on error, for using MCC in n scriptcache=true # Cache compiled scripts for faster load on low-end devices timestamps=false # Prepend timestamps to chat messages autorespawn=false # Toggle auto respawn if client player was dead (make sure your spawn point is safe) +minecraftrealms=true # Enable support for joining Minecraft Realms worlds [Logging] # Only affect the messages on console. diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index c1aaea6a..436a6fd3 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -39,6 +39,8 @@ mcc.session_fail=Failed to check session. mcc.server_protocol=§8Server version : {0} (protocol v{1}) mcc.with_forge=, with Forge mcc.handshake=§8Handshake successful. (Server ID: {0}) +mcc.realms_available=You have access to the following Realms worlds +mcc.realms_join=Use realms:index as server IP to join the Realms world [debug] @@ -82,6 +84,10 @@ error.forge_encrypt=§8Forge StartEncryption Handshake did not complete successf error.setting.str2int=Failed to convert '{0}' into an int. Please check your settings. error.http_code=§8Got error code from server: {0} error.auth=§8Got error code from server while refreshing authentication: {0} +error.realms.ip_error=Cannot retrieve the server IP of your Realms world +error.realms.access_denied=This Realms world does not exist or access was denied +error.realms.server_unavailable=Realms server may require some time to start up. Please retry again later. +error.realms.disabled=Trying to join a Realms world but Realms support is disabled in config [internal command] # MCC internal help command diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index d21f9c98..3684d920 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -99,6 +99,7 @@ namespace MinecraftClient public static bool ResolveSrvRecordsShortTimeout = true; public static bool EntityHandling = false; public static bool AutoRespawn = false; + public static bool MinecraftRealmsEnabled = true; // Logging public enum FilterModeEnum { Blacklist, Whitelist } @@ -308,6 +309,7 @@ namespace MinecraftClient case "autorespawn": AutoRespawn = str2bool(argValue); break; // Backward compatible so people can still enable debug with old config format case "debugmessages": DebugMessages = str2bool(argValue); break; + case "minecraftrealms": MinecraftRealmsEnabled = str2bool(argValue); break; case "botowners": Bots_Owners.Clear();