From 1a41c42ba99f2798c02fa91c2408a260821c5fed Mon Sep 17 00:00:00 2001 From: initsuj Date: Tue, 1 Mar 2016 19:20:05 -0700 Subject: [PATCH 01/10] Cache settings are written and parsed. --- MinecraftClient/Cache/AuthCacheHandler.cs | 12 ++++++++++++ MinecraftClient/Settings.cs | 10 ++++++++++ 2 files changed, 22 insertions(+) create mode 100644 MinecraftClient/Cache/AuthCacheHandler.cs diff --git a/MinecraftClient/Cache/AuthCacheHandler.cs b/MinecraftClient/Cache/AuthCacheHandler.cs new file mode 100644 index 00000000..8b1748b1 --- /dev/null +++ b/MinecraftClient/Cache/AuthCacheHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Cache +{ + public static class AuthCacheHandler + { + public enum Type { NONE, MEMORY, DISK }; + } +} diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 7b0ffe88..51972956 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -29,6 +29,9 @@ namespace MinecraftClient public static string SingleCommand = ""; public static string ConsoleTitle = ""; + //Cache Settings + public static Cache.AuthCacheHandler.Type CacheType = Cache.AuthCacheHandler.Type.NONE; + //Proxy Settings public static bool ProxyEnabledLogin = false; public static bool ProxyEnabledIngame = false; @@ -200,6 +203,12 @@ namespace MinecraftClient } break; + case "accountcache": + if(argValue == "none") { CacheType = Cache.AuthCacheHandler.Type.NONE; } + else if(argValue == "memory") { CacheType = Cache.AuthCacheHandler.Type.MEMORY; } + else if(argValue == "disk") { CacheType = Cache.AuthCacheHandler.Type.DISK; } + break; + case "accountlist": if (File.Exists(argValue)) { @@ -416,6 +425,7 @@ namespace MinecraftClient + "showsystemmessages=true #system messages for server ops\r\n" + "showxpbarmessages=true #messages displayed above xp bar\r\n" + "terrainandmovements=false #uses more ram, cpu, bandwidth\r\n" + + "accountcache=none #use 'none', 'memory' or 'disk'\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" From 8c065320c2c600045bce84ad71b130f51119c7f4 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 19:40:54 -0700 Subject: [PATCH 02/10] validates token successully --- MinecraftClient/Protocol/ProtocolHandler.cs | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 6377cd49..d02f557e 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -211,6 +211,39 @@ namespace MinecraftClient.Protocol } } + public enum ValidationResult { Validated, RefreshRequired, Error }; + + /// + /// Validates whether accessToken must be refreshed + /// + /// Will contain the cached access token previously returned by Minecraft.net + /// Returns the status of the token (Valid, Invalid, etc.) + /// + public static ValidationResult GetTokenValidation(string accesstoken) + { + try + { + string result = ""; + string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\" }"; + int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); + if (code == 204) + { + return ValidationResult.Validated; + } + else if (code == 403) + { + return ValidationResult.RefreshRequired; + } + else + { + return ValidationResult.Error; + } + } + catch + { + return ValidationResult.Error; + } + } /// /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server /// From 2861be757fdc291310ba3354398bf62c4ca0603a Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 19:53:35 -0700 Subject: [PATCH 03/10] clienttoken is created (if not passed) and added to auth request, then returned --- MinecraftClient/Protocol/ProtocolHandler.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index d02f557e..77abf9d5 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -151,12 +151,16 @@ namespace MinecraftClient.Protocol /// Will contain the player's UUID, needed for multiplayer /// Returns the status of the login (Success, Failure, etc.) - public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string uuid) + public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string clienttoken, ref string uuid) { try { string result = ""; - string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\" }"; + if (clienttoken == string.Empty) + { + clienttoken = Guid.NewGuid().ToString(); + } + string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); if (code == 200) { From 7230cd726b836f4da800c2c1290082a72d50dc97 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 19:58:04 -0700 Subject: [PATCH 04/10] validates with saved clienttoken --- MinecraftClient/Protocol/ProtocolHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 77abf9d5..ea095ae0 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -221,14 +221,15 @@ namespace MinecraftClient.Protocol /// Validates whether accessToken must be refreshed /// /// Will contain the cached access token previously returned by Minecraft.net + /// Will contain the cached client token created on login /// Returns the status of the token (Valid, Invalid, etc.) /// - public static ValidationResult GetTokenValidation(string accesstoken) + public static ValidationResult GetTokenValidation(string accesstoken, string clienttoken) { try { string result = ""; - string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\" }"; + string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); if (code == 204) { From fa2dbfef1a67fd8d40d323cf1dc194e67868de94 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 20:00:02 -0700 Subject: [PATCH 05/10] clientToken param info added to docs --- MinecraftClient/Protocol/ProtocolHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index ea095ae0..ca1a5d9c 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -148,6 +148,7 @@ namespace MinecraftClient.Protocol /// Login /// Password /// Will contain the access token returned by Minecraft.net, if the login is successful + /// Will contain the client token generated before sending to Minecraft.net /// Will contain the player's UUID, needed for multiplayer /// Returns the status of the login (Success, Failure, etc.) From 64606c128fd39530939701fdc4a8e08cf9b08b1b Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 21:15:17 -0700 Subject: [PATCH 06/10] new token requests implemented. testing a success response is proving difficult. invaliding the token in a way that it can be refreshed is not documented. --- MinecraftClient/Protocol/ProtocolHandler.cs | 60 +++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index ca1a5d9c..e3e43016 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -6,7 +6,8 @@ using MinecraftClient.Protocol.Handlers; using MinecraftClient.Proxy; using System.Net.Sockets; using System.Net.Security; -using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Protocol.Handlers.Forge; + namespace MinecraftClient.Protocol { @@ -159,7 +160,7 @@ namespace MinecraftClient.Protocol string result = ""; if (clienttoken == string.Empty) { - clienttoken = Guid.NewGuid().ToString(); + clienttoken = Guid.NewGuid().ToString().Replace("-",""); } string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); @@ -216,7 +217,7 @@ namespace MinecraftClient.Protocol } } - public enum ValidationResult { Validated, RefreshRequired, Error }; + public enum ValidationResult { Validated, NewTokenRequired, Error }; /// /// Validates whether accessToken must be refreshed @@ -238,7 +239,7 @@ namespace MinecraftClient.Protocol } else if (code == 403) { - return ValidationResult.RefreshRequired; + return ValidationResult.NewTokenRequired; } else { @@ -250,6 +251,57 @@ namespace MinecraftClient.Protocol return ValidationResult.Error; } } + + public enum NewTokenResult { Success, InvalidToken, NullError, OtherError} + + /// + /// Refreshes invalid token + /// + /// Login + /// Will contain the new access token returned by Minecraft.net, if the refresh is successful + /// Will contain the client token generated before sending to Minecraft.net + /// Will contain the player's UUID, needed for multiplayer + /// Returns the status of the new token request (Success, Failure, etc.) + /// + public static NewTokenResult GetNewToken(ref string user, ref string accesstoken, ref string clienttoken, ref string uuid) + { + try + { + string result = ""; + string json_request = "{ \"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(uuid) + "\", \"name\": \"" + jsonEncode(user) + "\" } }"; + int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result); + if (code == 200) + { + if (result == null) { + return NewTokenResult.NullError; + }else{ + string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { clienttoken = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { user = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; } + return NewTokenResult.Success; + } + } + else if(code == 403 && result.Contains("InvalidToken")) + { + return NewTokenResult.InvalidToken; + } + else + { + ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code); + return NewTokenResult.OtherError; + } + } + catch + { + return NewTokenResult.OtherError; + } + } + /// /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server /// From 75f2f738a2eb482cbe74d2e84d0c925bd943100f Mon Sep 17 00:00:00 2001 From: initsuj Date: Wed, 2 Mar 2016 07:25:09 -0700 Subject: [PATCH 07/10] Added clientID to login call. Updated project file to include Cache namespace. --- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Program.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index c0a631c9..538e2066 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -72,6 +72,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 12916186..bfac6c20 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -118,6 +118,7 @@ namespace MinecraftClient ProtocolHandler.LoginResult result; Settings.Username = Settings.Login; string sessionID = ""; + string clientID = ""; string UUID = ""; if (Settings.Password == "-") @@ -129,21 +130,21 @@ namespace MinecraftClient else { Console.WriteLine("Connecting to Minecraft.net..."); - result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref UUID); + result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref clientID, ref UUID); } if (result == ProtocolHandler.LoginResult.Success) { if (Settings.ConsoleTitle != "") Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - + if (Settings.playerHeadAsIcon) ConsoleIcon.setPlayerIconAsync(Settings.Username); - + Console.WriteLine("Success. (session ID: " + sessionID + ')'); //ProtocolHandler.RealmsListWorlds(Settings.Username, UUID, sessionID); //TODO REMOVE - + if (Settings.ServerIP == "") { Console.Write("Server IP : "); @@ -267,7 +268,7 @@ namespace MinecraftClient /// Error message to display and optionally pass to AutoRelog bot /// Specify if the error is related to an incompatible or unkown server version /// If set, the error message will be processed by the AutoRelog bot - + public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null) { if (!String.IsNullOrEmpty(errorMessage)) From 57c53be09f2107b1bbe0da209d9a6a2536d9e512 Mon Sep 17 00:00:00 2001 From: initsuj Date: Wed, 2 Mar 2016 17:11:15 -0700 Subject: [PATCH 08/10] caching works. needs documentation and testing --- MinecraftClient/Cache/AuthCacheHandler.cs | 12 -- MinecraftClient/Cache/CacheType.cs | 4 + MinecraftClient/Cache/SessionCache.cs | 139 ++++++++++++++++++++ MinecraftClient/MinecraftClient.csproj | 5 +- MinecraftClient/Program.cs | 83 ++++++++---- MinecraftClient/Protocol/ProtocolHandler.cs | 70 +++++----- MinecraftClient/Protocol/SessionToken.cs | 14 ++ MinecraftClient/Settings.cs | 8 +- 8 files changed, 262 insertions(+), 73 deletions(-) delete mode 100644 MinecraftClient/Cache/AuthCacheHandler.cs create mode 100644 MinecraftClient/Cache/CacheType.cs create mode 100644 MinecraftClient/Cache/SessionCache.cs create mode 100644 MinecraftClient/Protocol/SessionToken.cs diff --git a/MinecraftClient/Cache/AuthCacheHandler.cs b/MinecraftClient/Cache/AuthCacheHandler.cs deleted file mode 100644 index 8b1748b1..00000000 --- a/MinecraftClient/Cache/AuthCacheHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace MinecraftClient.Cache -{ - public static class AuthCacheHandler - { - public enum Type { NONE, MEMORY, DISK }; - } -} diff --git a/MinecraftClient/Cache/CacheType.cs b/MinecraftClient/Cache/CacheType.cs new file mode 100644 index 00000000..9f49cd1c --- /dev/null +++ b/MinecraftClient/Cache/CacheType.cs @@ -0,0 +1,4 @@ +namespace MinecraftClient.Cache +{ + public enum CacheType { NONE, MEMORY, DISK }; +} diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Cache/SessionCache.cs new file mode 100644 index 00000000..3d83be40 --- /dev/null +++ b/MinecraftClient/Cache/SessionCache.cs @@ -0,0 +1,139 @@ +using MinecraftClient.Protocol; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; + +namespace MinecraftClient.Cache +{ + public static class SessionCache + { + const string filename = "cache.bin"; + private static Dictionary sessions = new Dictionary(); + private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); + + private static BinaryFormatter formatter = new BinaryFormatter(); + + public static bool Contains(string login) + { + return sessions.ContainsKey(login); + } + + public static void Store(string login, SessionToken session) + { + if (Contains(login)) + { + sessions[login] = session; + } + else + { + sessions.Add(login, session); + } + + if (Settings.CacheType == CacheType.DISK) + { + SaveToDisk(); + } + } + + public static SessionToken Get(string login) + { + return sessions[login]; + } + + public static bool LoadFromDisk() + { + cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; + cachemonitor.IncludeSubdirectories = false; + cachemonitor.Filter = filename; + cachemonitor.NotifyFilter = NotifyFilters.LastWrite; + cachemonitor.Changed += new FileSystemEventHandler(OnChanged); + cachemonitor.EnableRaisingEvents = true; + + return ReadCacheFile(); + } + + public static void OnChanged(object source, FileSystemEventArgs e) + { + ReadCacheFile(); + } + + private static bool ReadCacheFile() + { + if (File.Exists(filename)) + { + try + { + using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + sessions = (Dictionary)formatter.Deserialize(fs); + return true; + } + } + catch (IOException ex) + { + Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); + } + catch (SerializationException) + { + Console.WriteLine("Error getting sessions from cache file "); + } + } + return false; + } + + public static void SaveToDisk() + { + bool fileexists = File.Exists(filename); + + using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + cachemonitor.EnableRaisingEvents = false; + if (fileexists) + { + fs.SetLength(0); + fs.Flush(); + } + + formatter.Serialize(fs, sessions); + cachemonitor.EnableRaisingEvents = true; + } + + } + + private static byte[] GetHash(FileStream fs, bool resetposition = true) + { + using (var md5 = MD5.Create()) + { + long pos = fs.Position; + byte[] hash = md5.ComputeHash(fs); + + fs.Position = resetposition ? pos : fs.Position; + return hash; + } + } + + private static bool HashesEqual(byte[] hash1, byte[] hash2) + { + if (hash1.Length != hash2.Length) + { + return false; + } + + for (int i = 0; i < hash1.Length; i++) + { + if (hash1[i] != hash2[i]) + { + return false; + } + } + + return true; + } + + } +} diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 538e2066..be23d2ed 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -63,6 +63,7 @@ + @@ -72,7 +73,8 @@ - + + @@ -149,6 +151,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index bfac6c20..083e0d29 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -85,6 +85,12 @@ namespace MinecraftClient Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); } + //Load cached sessions from disk if necessary + if (Settings.CacheType == Cache.CacheType.DISK) + { + ConsoleIO.WriteLineFormatted(Cache.SessionCache.LoadFromDisk() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); + } + //Asking the user to type in missing data such as Username and Password if (Settings.Login == "") @@ -92,58 +98,91 @@ namespace MinecraftClient Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); Settings.Login = Console.ReadLine(); } - if (Settings.Password == "") + if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) { - Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); - Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); - if (Settings.Password == "") { Settings.Password = "-"; } - if (!ConsoleIO.basicIO) - { - //Hide password length - Console.CursorTop--; Console.Write("Password : <******>"); - for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } - } + RequestPassword(); } startupargs = args; InitializeClient(); } + /// + /// Reduest user to submit password. + /// + private static void RequestPassword() + { + Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); + Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); + if (Settings.Password == "") { Settings.Password = "-"; } + if (!ConsoleIO.basicIO) + { + //Hide password length + Console.CursorTop--; Console.Write("Password : <******>"); + for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } + } + } + /// /// Start a new Client /// private static void InitializeClient() { - ProtocolHandler.LoginResult result; - Settings.Username = Settings.Login; - string sessionID = ""; - string clientID = ""; - string UUID = ""; + SessionToken session = new SessionToken(); + + ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired; if (Settings.Password == "-") { ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode."); result = ProtocolHandler.LoginResult.Success; - sessionID = "0"; + session.PlayerID = "0"; + session.PlayerName = Settings.Login; } else { - Console.WriteLine("Connecting to Minecraft.net..."); - result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref clientID, ref UUID); + // Validate cached session or login new session. + if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) + { + session = Cache.SessionCache.Get(Settings.Login); + result = ProtocolHandler.GetTokenValidation(session); + + if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") + { + RequestPassword(); + } + + Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); + + } + + if (result != ProtocolHandler.LoginResult.Success) + { + Console.WriteLine("Connecting to Minecraft.net..."); + result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); + + if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) + { + Cache.SessionCache.Store(Settings.Login, session); + } + } + } if (result == ProtocolHandler.LoginResult.Success) { + Settings.Username = session.PlayerName; + if (Settings.ConsoleTitle != "") Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); if (Settings.playerHeadAsIcon) ConsoleIcon.setPlayerIconAsync(Settings.Username); - Console.WriteLine("Success. (session ID: " + sessionID + ')'); + Console.WriteLine("Success. (session ID: " + session.ID + ')'); - //ProtocolHandler.RealmsListWorlds(Settings.Username, UUID, sessionID); //TODO REMOVE + //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE if (Settings.ServerIP == "") { @@ -194,9 +233,9 @@ namespace MinecraftClient //Start the main TCP client if (Settings.SingleCommand != "") { - Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); + Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); } - else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); + else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); //Update console title if (Settings.ConsoleTitle != "") diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index e3e43016..6fa2a5e4 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -141,7 +141,7 @@ namespace MinecraftClient.Protocol } } - public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium }; + public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, NullError }; /// /// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme. @@ -150,19 +150,18 @@ namespace MinecraftClient.Protocol /// Password /// Will contain the access token returned by Minecraft.net, if the login is successful /// Will contain the client token generated before sending to Minecraft.net - /// Will contain the player's UUID, needed for multiplayer + /// Will contain the player's PlayerID, needed for multiplayer /// Returns the status of the login (Success, Failure, etc.) - public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string clienttoken, ref string uuid) + public static LoginResult GetLogin(string user, string pass, out SessionToken session) { + session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") }; + try { - string result = ""; - if (clienttoken == string.Empty) - { - clienttoken = Guid.NewGuid().ToString().Replace("-",""); - } - string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; + string result = ""; + + string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); if (code == 200) { @@ -173,11 +172,11 @@ namespace MinecraftClient.Protocol else { string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { session.ID = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { user = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { session.PlayerName = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { session.PlayerID = temp[1].Split('"')[0]; } return LoginResult.Success; } } @@ -226,33 +225,33 @@ namespace MinecraftClient.Protocol /// Will contain the cached client token created on login /// Returns the status of the token (Valid, Invalid, etc.) /// - public static ValidationResult GetTokenValidation(string accesstoken, string clienttoken) + public static LoginResult GetTokenValidation(SessionToken session) { try { string result = ""; - string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; + string json_request = "{\"accessToken\": \"" + jsonEncode(session.ID) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); if (code == 204) { - return ValidationResult.Validated; + return LoginResult.Success; } else if (code == 403) { - return ValidationResult.NewTokenRequired; + return LoginResult.LoginRequired; } else { - return ValidationResult.Error; + return LoginResult.OtherError; } } catch { - return ValidationResult.Error; + return LoginResult.OtherError; } } - public enum NewTokenResult { Success, InvalidToken, NullError, OtherError} + public enum NewTokenResult { Success, InvalidToken, NullError, OtherError } /// /// Refreshes invalid token @@ -260,45 +259,48 @@ namespace MinecraftClient.Protocol /// Login /// Will contain the new access token returned by Minecraft.net, if the refresh is successful /// Will contain the client token generated before sending to Minecraft.net - /// Will contain the player's UUID, needed for multiplayer + /// Will contain the player's PlayerID, needed for multiplayer /// Returns the status of the new token request (Success, Failure, etc.) /// - public static NewTokenResult GetNewToken(ref string user, ref string accesstoken, ref string clienttoken, ref string uuid) + public static LoginResult GetNewToken(SessionToken currentsession, out SessionToken newsession) { + newsession = new SessionToken(); try { string result = ""; - string json_request = "{ \"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(uuid) + "\", \"name\": \"" + jsonEncode(user) + "\" } }"; + string json_request = "{ \"accessToken\": \"" + jsonEncode(currentsession.ID) + "\", \"clientToken\": \"" + jsonEncode(currentsession.ClientID) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(currentsession.PlayerID) + "\", \"name\": \"" + jsonEncode(currentsession.PlayerName) + "\" } }"; int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result); if (code == 200) { - if (result == null) { - return NewTokenResult.NullError; - }else{ + if (result == null) + { + return LoginResult.NullError; + } + else { string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { newsession.ID = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { clienttoken = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { newsession.ClientID = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { user = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { newsession.PlayerName = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; } - return NewTokenResult.Success; + if (temp.Length >= 2) { newsession.PlayerID = temp[1].Split('"')[0]; } + return LoginResult.Success; } } - else if(code == 403 && result.Contains("InvalidToken")) + else if (code == 403 && result.Contains("InvalidToken")) { - return NewTokenResult.InvalidToken; + return LoginResult.InvalidToken; } else { ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code); - return NewTokenResult.OtherError; + return LoginResult.OtherError; } } catch { - return NewTokenResult.OtherError; + return LoginResult.OtherError; } } diff --git a/MinecraftClient/Protocol/SessionToken.cs b/MinecraftClient/Protocol/SessionToken.cs new file mode 100644 index 00000000..2d48a704 --- /dev/null +++ b/MinecraftClient/Protocol/SessionToken.cs @@ -0,0 +1,14 @@ +using System; + + +namespace MinecraftClient.Protocol +{ + [Serializable] + public class SessionToken + { + public string ID { get; set; } = string.Empty; + public string PlayerName { get; set; } = string.Empty; + public string PlayerID { get; set; } = string.Empty; + public string ClientID { get; set; } = string.Empty; + } +} diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 51972956..0d86cdf2 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -30,7 +30,7 @@ namespace MinecraftClient public static string ConsoleTitle = ""; //Cache Settings - public static Cache.AuthCacheHandler.Type CacheType = Cache.AuthCacheHandler.Type.NONE; + public static Cache.CacheType CacheType = Cache.CacheType.NONE; //Proxy Settings public static bool ProxyEnabledLogin = false; @@ -204,9 +204,9 @@ namespace MinecraftClient break; case "accountcache": - if(argValue == "none") { CacheType = Cache.AuthCacheHandler.Type.NONE; } - else if(argValue == "memory") { CacheType = Cache.AuthCacheHandler.Type.MEMORY; } - else if(argValue == "disk") { CacheType = Cache.AuthCacheHandler.Type.DISK; } + if (argValue == "none") { CacheType = Cache.CacheType.NONE; } + else if (argValue == "memory") { CacheType = Cache.CacheType.MEMORY; } + else if (argValue == "disk") { CacheType = Cache.CacheType.DISK; } break; case "accountlist": From fec1687cb7e2a0c7c056e78f20e16665ef6627d5 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Wed, 2 Mar 2016 18:16:19 -0700 Subject: [PATCH 09/10] Updated docs and cleaned up. --- MinecraftClient/Cache/SessionCache.cs | 288 +++---- MinecraftClient/Program.cs | 828 ++++++++++---------- MinecraftClient/Protocol/ProtocolHandler.cs | 4 - 3 files changed, 563 insertions(+), 557 deletions(-) diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Cache/SessionCache.cs index 3d83be40..9064d232 100644 --- a/MinecraftClient/Cache/SessionCache.cs +++ b/MinecraftClient/Cache/SessionCache.cs @@ -1,139 +1,149 @@ -using MinecraftClient.Protocol; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security.Cryptography; -using System.Text; - -namespace MinecraftClient.Cache -{ - public static class SessionCache - { - const string filename = "cache.bin"; - private static Dictionary sessions = new Dictionary(); - private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); - - private static BinaryFormatter formatter = new BinaryFormatter(); - - public static bool Contains(string login) - { - return sessions.ContainsKey(login); - } - - public static void Store(string login, SessionToken session) - { - if (Contains(login)) - { - sessions[login] = session; - } - else - { - sessions.Add(login, session); - } - - if (Settings.CacheType == CacheType.DISK) - { - SaveToDisk(); - } - } - - public static SessionToken Get(string login) - { - return sessions[login]; - } - - public static bool LoadFromDisk() - { - cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; - cachemonitor.IncludeSubdirectories = false; - cachemonitor.Filter = filename; - cachemonitor.NotifyFilter = NotifyFilters.LastWrite; - cachemonitor.Changed += new FileSystemEventHandler(OnChanged); - cachemonitor.EnableRaisingEvents = true; - - return ReadCacheFile(); - } - - public static void OnChanged(object source, FileSystemEventArgs e) - { - ReadCacheFile(); - } - - private static bool ReadCacheFile() - { - if (File.Exists(filename)) - { - try - { - using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - sessions = (Dictionary)formatter.Deserialize(fs); - return true; - } - } - catch (IOException ex) - { - Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); - } - catch (SerializationException) - { - Console.WriteLine("Error getting sessions from cache file "); - } - } - return false; - } - - public static void SaveToDisk() - { - bool fileexists = File.Exists(filename); - - using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) - { - cachemonitor.EnableRaisingEvents = false; - if (fileexists) - { - fs.SetLength(0); - fs.Flush(); - } - - formatter.Serialize(fs, sessions); - cachemonitor.EnableRaisingEvents = true; - } - - } - - private static byte[] GetHash(FileStream fs, bool resetposition = true) - { - using (var md5 = MD5.Create()) - { - long pos = fs.Position; - byte[] hash = md5.ComputeHash(fs); - - fs.Position = resetposition ? pos : fs.Position; - return hash; - } - } - - private static bool HashesEqual(byte[] hash1, byte[] hash2) - { - if (hash1.Length != hash2.Length) - { - return false; - } - - for (int i = 0; i < hash1.Length; i++) - { - if (hash1[i] != hash2[i]) - { - return false; - } - } - - return true; - } - - } -} +using MinecraftClient.Protocol; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace MinecraftClient.Cache +{ + /// + /// Handle sessions caching and storage. + /// + + public static class SessionCache + { + const string filename = "cache.bin"; + private static Dictionary sessions = new Dictionary(); + private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); + + private static BinaryFormatter formatter = new BinaryFormatter(); + + /// + /// Retrieve whether SessionCache contains a session for the given login. + /// + /// User login used with Minecraft.net + /// TRUE if session is available + + public static bool Contains(string login) + { + return sessions.ContainsKey(login); + } + + /// + /// Store a session and save it to disk if required. + /// + /// User login used with Minecraft.net + /// User session token used with Minecraft.net + + public static void Store(string login, SessionToken session) + { + if (Contains(login)) + { + sessions[login] = session; + } + else + { + sessions.Add(login, session); + } + + if (Settings.CacheType == CacheType.DISK) + { + SaveToDisk(); + } + } + + /// + /// Retrieve a session token for the given login. + /// + /// User login used with Minecraft.net + /// SessionToken for given login + + public static SessionToken Get(string login) + { + return sessions[login]; + } + + /// + /// Initialize cache monitoring to keep cache updated with external changes. + /// + /// TRUE if session tokens are seeded from file + + public static bool InitializeDiskCache() + { + cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; + cachemonitor.IncludeSubdirectories = false; + cachemonitor.Filter = filename; + cachemonitor.NotifyFilter = NotifyFilters.LastWrite; + cachemonitor.Changed += new FileSystemEventHandler(OnChanged); + cachemonitor.EnableRaisingEvents = true; + + return LoadFromDisk(); + } + + /// + /// Reloads cache on external cache file change. + /// + /// Sender + /// Event data + + private static void OnChanged(object source, FileSystemEventArgs e) + { + LoadFromDisk(); + } + + /// + /// Reads cache file and loads SessionTokens into SessionCache. + /// + /// True if data is successfully loaded + + private static bool LoadFromDisk() + { + if (File.Exists(filename)) + { + try + { + using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + sessions = (Dictionary)formatter.Deserialize(fs); + return true; + } + } + catch (IOException ex) + { + Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); + } + catch (SerializationException) + { + Console.WriteLine("Malformed sessions from cache file "); + } + } + return false; + } + + /// + /// Saves SessionToken's from SessionCache into cache file. + /// + + private static void SaveToDisk() + { + bool fileexists = File.Exists(filename); + + using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + cachemonitor.EnableRaisingEvents = false; + + // delete existing file contents + if (fileexists) + { + fs.SetLength(0); + fs.Flush(); + } + + formatter.Serialize(fs, sessions); + cachemonitor.EnableRaisingEvents = true; + } + + } + } +} diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 083e0d29..c8d15486 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -1,414 +1,414 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using MinecraftClient.Protocol; -using System.Reflection; -using System.Threading; -using MinecraftClient.Protocol.Handlers.Forge; - -namespace MinecraftClient -{ - /// - /// Minecraft Console Client by ORelio (c) 2012-2014. - /// Allows to connect to any Minecraft server, send and receive text, automated scripts. - /// This source code is released under the CDDL 1.0 License. - /// - - static class Program - { - private static McTcpClient Client; - public static string[] startupargs; - - public const string Version = "1.8.2"; - public const string MCLowestVersion = "1.4.6"; - public const string MCHighestVersion = "1.8.8"; - - private static Thread offlinePrompt = null; - private static bool useMcVersionOnce = false; - - /// - /// The main entry point of Minecraft Console Client - /// - - static void Main(string[] args) - { - Console.WriteLine("Console Client for MC {0} to {1} - v{2} - By ORelio & Contributors", MCLowestVersion, MCHighestVersion, Version); - - //Basic Input/Output ? - if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") - { - ConsoleIO.basicIO = true; - Console.OutputEncoding = Console.InputEncoding = Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage); - args = args.Where(o => !Object.ReferenceEquals(o, args[args.Length - 1])).ToArray(); - } - - //Process ini configuration file - if (args.Length >= 1 && System.IO.File.Exists(args[0]) && System.IO.Path.GetExtension(args[0]).ToLower() == ".ini") - { - Settings.LoadSettings(args[0]); - - //remove ini configuration file from arguments array - List args_tmp = args.ToList(); - args_tmp.RemoveAt(0); - args = args_tmp.ToArray(); - } - else if (System.IO.File.Exists("MinecraftClient.ini")) - { - Settings.LoadSettings("MinecraftClient.ini"); - } - else Settings.WriteDefaultSettings("MinecraftClient.ini"); - - //Other command-line arguments - if (args.Length >= 1) - { - Settings.Login = args[0]; - if (args.Length >= 2) - { - Settings.Password = args[1]; - if (args.Length >= 3) - { - Settings.SetServerIP(args[2]); - - //Single command? - if (args.Length >= 4) - { - Settings.SingleCommand = args[3]; - } - } - } - } - - if (Settings.ConsoleTitle != "") - { - Settings.Username = "New Window"; - Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - } - - //Load cached sessions from disk if necessary - if (Settings.CacheType == Cache.CacheType.DISK) - { - ConsoleIO.WriteLineFormatted(Cache.SessionCache.LoadFromDisk() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); - } - - //Asking the user to type in missing data such as Username and Password - - if (Settings.Login == "") - { - Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); - Settings.Login = Console.ReadLine(); - } - if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) - { - RequestPassword(); - } - - startupargs = args; - InitializeClient(); - } - - /// - /// Reduest user to submit password. - /// - private static void RequestPassword() - { - Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); - Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); - if (Settings.Password == "") { Settings.Password = "-"; } - if (!ConsoleIO.basicIO) - { - //Hide password length - Console.CursorTop--; Console.Write("Password : <******>"); - for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } - } - } - - /// - /// Start a new Client - /// - - private static void InitializeClient() - { - SessionToken session = new SessionToken(); - - ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired; - - if (Settings.Password == "-") - { - ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode."); - result = ProtocolHandler.LoginResult.Success; - session.PlayerID = "0"; - session.PlayerName = Settings.Login; - } - else - { - // Validate cached session or login new session. - if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) - { - session = Cache.SessionCache.Get(Settings.Login); - result = ProtocolHandler.GetTokenValidation(session); - - if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") - { - RequestPassword(); - } - - Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); - - } - - if (result != ProtocolHandler.LoginResult.Success) - { - Console.WriteLine("Connecting to Minecraft.net..."); - result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); - - if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) - { - Cache.SessionCache.Store(Settings.Login, session); - } - } - - } - - if (result == ProtocolHandler.LoginResult.Success) - { - Settings.Username = session.PlayerName; - - if (Settings.ConsoleTitle != "") - Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - - if (Settings.playerHeadAsIcon) - ConsoleIcon.setPlayerIconAsync(Settings.Username); - - Console.WriteLine("Success. (session ID: " + session.ID + ')'); - - //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE - - if (Settings.ServerIP == "") - { - Console.Write("Server IP : "); - Settings.SetServerIP(Console.ReadLine()); - } - - //Get server version - int protocolversion = 0; - ForgeInfo forgeInfo = null; - - if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto") - { - protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion); - - if (protocolversion != 0) - { - ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')'); - } - else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode."); - - if (useMcVersionOnce) - { - useMcVersionOnce = false; - Settings.ServerVersion = ""; - } - } - - if (protocolversion == 0) - { - Console.WriteLine("Retrieving Server Info..."); - if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo)) - { - HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); - return; - } - } - - if (forgeInfo != null && !forgeInfo.Mods.Any()) - { - forgeInfo = null; - } - - if (protocolversion != 0) - { - try - { - //Start the main TCP client - if (Settings.SingleCommand != "") - { - Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); - } - else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); - - //Update console title - if (Settings.ConsoleTitle != "") - Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - } - catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); } - } - else HandleFailure("Failed to determine server version.", true); - } - else - { - Console.ForegroundColor = ConsoleColor.Gray; - string failureMessage = "Minecraft Login failed : "; - switch (result) - { - case ProtocolHandler.LoginResult.AccountMigrated: failureMessage += "Account migrated, use e-mail as username."; break; - case ProtocolHandler.LoginResult.ServiceUnavailable: failureMessage += "Login servers are unavailable. Please try again later."; break; - case ProtocolHandler.LoginResult.WrongPassword: failureMessage += "Incorrect password."; break; - case ProtocolHandler.LoginResult.NotPremium: failureMessage += "User not premium."; break; - case ProtocolHandler.LoginResult.OtherError: failureMessage += "Network error."; break; - case ProtocolHandler.LoginResult.SSLError: failureMessage += "SSL Error."; break; - default: failureMessage += "Unknown Error."; break; - } - if (result == ProtocolHandler.LoginResult.SSLError && isUsingMono) - { - ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program." - + '\n' + "The first time, you have to import HTTPS certificates using:" - + '\n' + "mozroots --import --ask-remove"); - return; - } - HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); - } - } - - /// - /// Disconnect the current client from the server and restart it - /// - - public static void Restart() - { - new Thread(new ThreadStart(delegate - { - if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } - if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } - Console.WriteLine("Restarting Minecraft Console Client..."); - InitializeClient(); - })).Start(); - } - - /// - /// Disconnect the current client from the server and exit the app - /// - - public static void Exit() - { - new Thread(new ThreadStart(delegate - { - if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } - if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } - if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); } - Environment.Exit(0); - })).Start(); - } - - /// - /// Handle fatal errors such as ping failure, login failure, server disconnection, and so on. - /// Allows AutoRelog to perform on fatal errors, prompt for server version, and offline commands. - /// - /// Error message to display and optionally pass to AutoRelog bot - /// Specify if the error is related to an incompatible or unkown server version - /// If set, the error message will be processed by the AutoRelog bot - - public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null) - { - if (!String.IsNullOrEmpty(errorMessage)) - { - ConsoleIO.Reset(); - while (Console.KeyAvailable) - Console.ReadKey(true); - Console.WriteLine(errorMessage); - - if (disconnectReason.HasValue) - { - if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) - return; //AutoRelog is triggering a restart of the client - } - } - - if (Settings.interactiveMode) - { - if (versionError) - { - Console.Write("Server version : "); - Settings.ServerVersion = Console.ReadLine(); - if (Settings.ServerVersion != "") - { - useMcVersionOnce = true; - Restart(); - return; - } - } - - if (offlinePrompt == null) - { - offlinePrompt = new Thread(new ThreadStart(delegate - { - string command = " "; - ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."); - ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client."); - while (command.Length > 0) - { - if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); } - command = Console.ReadLine().Trim(); - if (command.Length > 0) - { - string message = ""; - - if (Settings.internalCmdChar != ' ' - && command[0] == Settings.internalCmdChar) - command = command.Substring(1); - - if (command.StartsWith("reco")) - { - message = new Commands.Reco().Run(null, Settings.ExpandVars(command)); - } - else if (command.StartsWith("connect")) - { - message = new Commands.Connect().Run(null, Settings.ExpandVars(command)); - } - else if (command.StartsWith("exit") || command.StartsWith("quit")) - { - message = new Commands.Exit().Run(null, Settings.ExpandVars(command)); - } - else if (command.StartsWith("help")) - { - ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc); - ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc); - } - else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'."); - - if (message != "") - ConsoleIO.WriteLineFormatted("§8MCC: " + message); - } - } - })); - offlinePrompt.Start(); - } - } - } - - /// - /// Detect if the user is running Minecraft Console Client through Mono - /// - - public static bool isUsingMono - { - get - { - return Type.GetType("Mono.Runtime") != null; - } - } - - /// - /// Enumerate types in namespace through reflection - /// - /// Namespace to process - /// Assembly to use. Default is Assembly.GetExecutingAssembly() - /// - - public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null) - { - if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); } - return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MinecraftClient.Protocol; +using System.Reflection; +using System.Threading; +using MinecraftClient.Protocol.Handlers.Forge; + +namespace MinecraftClient +{ + /// + /// Minecraft Console Client by ORelio (c) 2012-2014. + /// Allows to connect to any Minecraft server, send and receive text, automated scripts. + /// This source code is released under the CDDL 1.0 License. + /// + + static class Program + { + private static McTcpClient Client; + public static string[] startupargs; + + public const string Version = "1.8.2"; + public const string MCLowestVersion = "1.4.6"; + public const string MCHighestVersion = "1.8.8"; + + private static Thread offlinePrompt = null; + private static bool useMcVersionOnce = false; + + /// + /// The main entry point of Minecraft Console Client + /// + + static void Main(string[] args) + { + Console.WriteLine("Console Client for MC {0} to {1} - v{2} - By ORelio & Contributors", MCLowestVersion, MCHighestVersion, Version); + + //Basic Input/Output ? + if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") + { + ConsoleIO.basicIO = true; + Console.OutputEncoding = Console.InputEncoding = Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage); + args = args.Where(o => !Object.ReferenceEquals(o, args[args.Length - 1])).ToArray(); + } + + //Process ini configuration file + if (args.Length >= 1 && System.IO.File.Exists(args[0]) && System.IO.Path.GetExtension(args[0]).ToLower() == ".ini") + { + Settings.LoadSettings(args[0]); + + //remove ini configuration file from arguments array + List args_tmp = args.ToList(); + args_tmp.RemoveAt(0); + args = args_tmp.ToArray(); + } + else if (System.IO.File.Exists("MinecraftClient.ini")) + { + Settings.LoadSettings("MinecraftClient.ini"); + } + else Settings.WriteDefaultSettings("MinecraftClient.ini"); + + //Other command-line arguments + if (args.Length >= 1) + { + Settings.Login = args[0]; + if (args.Length >= 2) + { + Settings.Password = args[1]; + if (args.Length >= 3) + { + Settings.SetServerIP(args[2]); + + //Single command? + if (args.Length >= 4) + { + Settings.SingleCommand = args[3]; + } + } + } + } + + if (Settings.ConsoleTitle != "") + { + Settings.Username = "New Window"; + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); + } + + //Load cached sessions from disk if necessary + if (Settings.CacheType == Cache.CacheType.DISK) + { + Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); + } + + //Asking the user to type in missing data such as Username and Password + + if (Settings.Login == "") + { + Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); + Settings.Login = Console.ReadLine(); + } + if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) + { + RequestPassword(); + } + + startupargs = args; + InitializeClient(); + } + + /// + /// Reduest user to submit password. + /// + private static void RequestPassword() + { + Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); + Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); + if (Settings.Password == "") { Settings.Password = "-"; } + if (!ConsoleIO.basicIO) + { + //Hide password length + Console.CursorTop--; Console.Write("Password : <******>"); + for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } + } + } + + /// + /// Start a new Client + /// + + private static void InitializeClient() + { + SessionToken session = new SessionToken(); + + ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired; + + if (Settings.Password == "-") + { + ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode."); + result = ProtocolHandler.LoginResult.Success; + session.PlayerID = "0"; + session.PlayerName = Settings.Login; + } + else + { + // Validate cached session or login new session. + if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) + { + session = Cache.SessionCache.Get(Settings.Login); + result = ProtocolHandler.GetTokenValidation(session); + + if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") + { + RequestPassword(); + } + + Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); + + } + + if (result != ProtocolHandler.LoginResult.Success) + { + Console.WriteLine("Connecting to Minecraft.net..."); + result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); + + if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) + { + Cache.SessionCache.Store(Settings.Login, session); + } + } + + } + + if (result == ProtocolHandler.LoginResult.Success) + { + Settings.Username = session.PlayerName; + + if (Settings.ConsoleTitle != "") + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); + + if (Settings.playerHeadAsIcon) + ConsoleIcon.setPlayerIconAsync(Settings.Username); + + Console.WriteLine("Success. (session ID: " + session.ID + ')'); + + //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE + + if (Settings.ServerIP == "") + { + Console.Write("Server IP : "); + Settings.SetServerIP(Console.ReadLine()); + } + + //Get server version + int protocolversion = 0; + ForgeInfo forgeInfo = null; + + if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto") + { + protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion); + + if (protocolversion != 0) + { + ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')'); + } + else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode."); + + if (useMcVersionOnce) + { + useMcVersionOnce = false; + Settings.ServerVersion = ""; + } + } + + if (protocolversion == 0) + { + Console.WriteLine("Retrieving Server Info..."); + if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo)) + { + HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); + return; + } + } + + if (forgeInfo != null && !forgeInfo.Mods.Any()) + { + forgeInfo = null; + } + + if (protocolversion != 0) + { + try + { + //Start the main TCP client + if (Settings.SingleCommand != "") + { + Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); + } + else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); + + //Update console title + if (Settings.ConsoleTitle != "") + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); + } + catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); } + } + else HandleFailure("Failed to determine server version.", true); + } + else + { + Console.ForegroundColor = ConsoleColor.Gray; + string failureMessage = "Minecraft Login failed : "; + switch (result) + { + case ProtocolHandler.LoginResult.AccountMigrated: failureMessage += "Account migrated, use e-mail as username."; break; + case ProtocolHandler.LoginResult.ServiceUnavailable: failureMessage += "Login servers are unavailable. Please try again later."; break; + case ProtocolHandler.LoginResult.WrongPassword: failureMessage += "Incorrect password."; break; + case ProtocolHandler.LoginResult.NotPremium: failureMessage += "User not premium."; break; + case ProtocolHandler.LoginResult.OtherError: failureMessage += "Network error."; break; + case ProtocolHandler.LoginResult.SSLError: failureMessage += "SSL Error."; break; + default: failureMessage += "Unknown Error."; break; + } + if (result == ProtocolHandler.LoginResult.SSLError && isUsingMono) + { + ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program." + + '\n' + "The first time, you have to import HTTPS certificates using:" + + '\n' + "mozroots --import --ask-remove"); + return; + } + HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); + } + } + + /// + /// Disconnect the current client from the server and restart it + /// + + public static void Restart() + { + new Thread(new ThreadStart(delegate + { + if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } + if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } + Console.WriteLine("Restarting Minecraft Console Client..."); + InitializeClient(); + })).Start(); + } + + /// + /// Disconnect the current client from the server and exit the app + /// + + public static void Exit() + { + new Thread(new ThreadStart(delegate + { + if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } + if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } + if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); } + Environment.Exit(0); + })).Start(); + } + + /// + /// Handle fatal errors such as ping failure, login failure, server disconnection, and so on. + /// Allows AutoRelog to perform on fatal errors, prompt for server version, and offline commands. + /// + /// Error message to display and optionally pass to AutoRelog bot + /// Specify if the error is related to an incompatible or unkown server version + /// If set, the error message will be processed by the AutoRelog bot + + public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null) + { + if (!String.IsNullOrEmpty(errorMessage)) + { + ConsoleIO.Reset(); + while (Console.KeyAvailable) + Console.ReadKey(true); + Console.WriteLine(errorMessage); + + if (disconnectReason.HasValue) + { + if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) + return; //AutoRelog is triggering a restart of the client + } + } + + if (Settings.interactiveMode) + { + if (versionError) + { + Console.Write("Server version : "); + Settings.ServerVersion = Console.ReadLine(); + if (Settings.ServerVersion != "") + { + useMcVersionOnce = true; + Restart(); + return; + } + } + + if (offlinePrompt == null) + { + offlinePrompt = new Thread(new ThreadStart(delegate + { + string command = " "; + ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."); + ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client."); + while (command.Length > 0) + { + if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); } + command = Console.ReadLine().Trim(); + if (command.Length > 0) + { + string message = ""; + + if (Settings.internalCmdChar != ' ' + && command[0] == Settings.internalCmdChar) + command = command.Substring(1); + + if (command.StartsWith("reco")) + { + message = new Commands.Reco().Run(null, Settings.ExpandVars(command)); + } + else if (command.StartsWith("connect")) + { + message = new Commands.Connect().Run(null, Settings.ExpandVars(command)); + } + else if (command.StartsWith("exit") || command.StartsWith("quit")) + { + message = new Commands.Exit().Run(null, Settings.ExpandVars(command)); + } + else if (command.StartsWith("help")) + { + ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc); + ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc); + } + else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'."); + + if (message != "") + ConsoleIO.WriteLineFormatted("§8MCC: " + message); + } + } + })); + offlinePrompt.Start(); + } + } + } + + /// + /// Detect if the user is running Minecraft Console Client through Mono + /// + + public static bool isUsingMono + { + get + { + return Type.GetType("Mono.Runtime") != null; + } + } + + /// + /// Enumerate types in namespace through reflection + /// + /// Namespace to process + /// Assembly to use. Default is Assembly.GetExecutingAssembly() + /// + + public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null) + { + if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); } + return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray(); + } + } +} diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 6fa2a5e4..6adad9ed 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -216,8 +216,6 @@ namespace MinecraftClient.Protocol } } - public enum ValidationResult { Validated, NewTokenRequired, Error }; - /// /// Validates whether accessToken must be refreshed /// @@ -250,8 +248,6 @@ namespace MinecraftClient.Protocol return LoginResult.OtherError; } } - - public enum NewTokenResult { Success, InvalidToken, NullError, OtherError } /// /// Refreshes invalid token From 98b3ce7304e1b059056f7f615933ace26f5da21d Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Wed, 2 Mar 2016 19:08:24 -0700 Subject: [PATCH 10/10] added timer to reduce file access collisions and cleaned some text up. --- MinecraftClient/Cache/SessionCache.cs | 31 ++++++++++++++++++++++++--- MinecraftClient/Program.cs | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Cache/SessionCache.cs index 9064d232..a943958b 100644 --- a/MinecraftClient/Cache/SessionCache.cs +++ b/MinecraftClient/Cache/SessionCache.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; +using System.Timers; namespace MinecraftClient.Cache { @@ -16,6 +17,8 @@ namespace MinecraftClient.Cache const string filename = "cache.bin"; private static Dictionary sessions = new Dictionary(); private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); + private static Timer updatetimer = new Timer(100); + private static List> pendingadds = new List>(); private static BinaryFormatter formatter = new BinaryFormatter(); @@ -47,7 +50,9 @@ namespace MinecraftClient.Cache sessions.Add(login, session); } - if (Settings.CacheType == CacheType.DISK) + if (Settings.CacheType == CacheType.DISK && updatetimer.Enabled == true) { + pendingadds.Add(new KeyValuePair(login, session)); + }else if (Settings.CacheType == CacheType.DISK) { SaveToDisk(); } @@ -78,18 +83,38 @@ namespace MinecraftClient.Cache cachemonitor.Changed += new FileSystemEventHandler(OnChanged); cachemonitor.EnableRaisingEvents = true; + updatetimer.Elapsed += HandlePending; + return LoadFromDisk(); } /// /// Reloads cache on external cache file change. /// - /// Sender + /// Sender /// Event data - private static void OnChanged(object source, FileSystemEventArgs e) + private static void OnChanged(object sender, FileSystemEventArgs e) + { + updatetimer.Stop(); + updatetimer.Start(); + } + + /// + /// Called after timer elapsed. Reads disk cache and adds new/modified sessions back. + /// + /// Sender + /// Event data + + private static void HandlePending(object sender, ElapsedEventArgs e) { LoadFromDisk(); + + foreach(KeyValuePair pending in pendingadds.ToArray()) + { + Store(pending.Key, pending.Value); + pendingadds.Remove(pending); + } } /// diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index c8d15486..402046e4 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -88,7 +88,7 @@ namespace MinecraftClient //Load cached sessions from disk if necessary if (Settings.CacheType == Cache.CacheType.DISK) { - Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); + Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "Cached sessions could not be loaded from disk"); } //Asking the user to type in missing data such as Username and Password