mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Auth async refactor
This commit is contained in:
parent
3a4d8951d5
commit
3b7c9c8510
4 changed files with 252 additions and 179 deletions
|
|
@ -74,7 +74,7 @@ namespace MinecraftClient
|
|||
/// <summary>
|
||||
/// The main entry point of Minecraft Console Client
|
||||
/// </summary>
|
||||
static void Main(string[] args)
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
// [SENTRY] Initialize Sentry SDK only if the DSN is not empty
|
||||
if (SentryDSN != string.Empty)
|
||||
|
|
@ -94,7 +94,7 @@ namespace MinecraftClient
|
|||
};
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
// "ToLower" require "CultureInfo" to be initialized on first run, which can take a lot of time.
|
||||
_ = "a".ToLower();
|
||||
|
|
@ -208,7 +208,7 @@ namespace MinecraftClient
|
|||
// Wait for this issue to be fixed before enabling it: https://github.com/Consolonia/Consolonia/issues/602
|
||||
// MaybePrintClassicModeTuiRecommendation();
|
||||
|
||||
RunStartupSequence(args);
|
||||
await RunStartupSequenceAsync(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -381,7 +381,10 @@ namespace MinecraftClient
|
|||
/// Called from Main() for classic/basic mode, or from TuiConsoleBackend on a
|
||||
/// background thread after the Avalonia UI loop has started.
|
||||
/// </summary>
|
||||
internal static void RunStartupSequence(string[] args)
|
||||
internal static void RunStartupSequence(string[] args) =>
|
||||
RunStartupSequenceAsync(args).GetAwaiter().GetResult();
|
||||
|
||||
internal static async Task RunStartupSequenceAsync(string[] args)
|
||||
{
|
||||
//Other command-line arguments
|
||||
if (args.Length >= 1)
|
||||
|
|
@ -578,7 +581,7 @@ namespace MinecraftClient
|
|||
}
|
||||
|
||||
startupargs = args;
|
||||
InitializeClient();
|
||||
await InitializeClientAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -597,7 +600,7 @@ namespace MinecraftClient
|
|||
/// <summary>
|
||||
/// Start a new Client
|
||||
/// </summary>
|
||||
private static void InitializeClient()
|
||||
private static async Task InitializeClientAsync()
|
||||
{
|
||||
// Ensure that we use the provided Minecraft version if we can't connect automatically.
|
||||
//
|
||||
|
|
@ -634,7 +637,9 @@ namespace MinecraftClient
|
|||
{
|
||||
try
|
||||
{
|
||||
result = ProtocolHandler.MicrosoftLoginRefresh(session.RefreshToken, out session);
|
||||
var refreshResult = await ProtocolHandler.MicrosoftLoginRefreshAsync(session.RefreshToken);
|
||||
result = refreshResult.Result;
|
||||
session = refreshResult.Session;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -654,7 +659,9 @@ namespace MinecraftClient
|
|||
if (result != ProtocolHandler.LoginResult.Success)
|
||||
{
|
||||
ConsoleIO.WriteLine(string.Format(Translations.mcc_connecting, Config.Main.General.AccountType == LoginType.mojang ? "Minecraft.net" : (Config.Main.General.AccountType == LoginType.microsoft ? "Microsoft" : Config.Main.General.AuthServer.Host)));
|
||||
result = ProtocolHandler.GetLogin(InternalConfig.Account.Login, InternalConfig.Account.Password, Config.Main.General.AccountType, out session);
|
||||
var loginResult = await ProtocolHandler.GetLoginAsync(InternalConfig.Account.Login, InternalConfig.Account.Password, Config.Main.General.AccountType);
|
||||
result = loginResult.Result;
|
||||
session = loginResult.Session;
|
||||
}
|
||||
|
||||
if (result == ProtocolHandler.LoginResult.Success && Config.Main.Advanced.SessionCache != CacheType.none)
|
||||
|
|
@ -680,7 +687,7 @@ namespace MinecraftClient
|
|||
|
||||
List<string> availableWorlds = new();
|
||||
if (Config.Main.Advanced.MinecraftRealms && !String.IsNullOrEmpty(session.ID))
|
||||
availableWorlds = ProtocolHandler.RealmsListWorlds(InternalConfig.Username, session.PlayerID, session.ID);
|
||||
availableWorlds = await ProtocolHandler.RealmsListWorldsAsync(InternalConfig.Username, session.PlayerID, session.ID);
|
||||
|
||||
if (InternalConfig.ServerIP == string.Empty)
|
||||
{
|
||||
|
|
@ -700,7 +707,7 @@ namespace MinecraftClient
|
|||
worldId = availableWorlds[worldIndex];
|
||||
if (availableWorlds.Contains(worldId))
|
||||
{
|
||||
string realmsAddress = ProtocolHandler.GetRealmsWorldServerAddress(worldId, InternalConfig.Username, session.PlayerID, session.ID);
|
||||
string realmsAddress = await ProtocolHandler.GetRealmsWorldServerAddressAsync(worldId, InternalConfig.Username, session.PlayerID, session.ID);
|
||||
if (realmsAddress != "")
|
||||
{
|
||||
addressInput = realmsAddress;
|
||||
|
|
@ -909,7 +916,7 @@ namespace MinecraftClient
|
|||
}
|
||||
ConsoleIO.WriteLine(Translations.mcc_restart);
|
||||
ReloadSettings(keepAccountAndServerSettings);
|
||||
InitializeClient();
|
||||
InitializeClientAsync().GetAwaiter().GetResult();
|
||||
})).Start();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -402,10 +402,7 @@ namespace MinecraftClient.Protocol.Message
|
|||
private static string TranslateString(string rulename, List<string> using_data)
|
||||
{
|
||||
if (!RulesInitialized)
|
||||
{
|
||||
InitRules();
|
||||
RulesInitialized = true;
|
||||
}
|
||||
InitTranslations();
|
||||
|
||||
if (TranslationRules.ContainsKey(rulename))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// !!! ATTENTION !!!
|
||||
|
|
@ -103,14 +104,15 @@ namespace MinecraftClient.Protocol
|
|||
/// </summary>
|
||||
/// <param name="name">Playername</param>
|
||||
/// <returns>UUID as string</returns>
|
||||
public static string NameToUuid(string name)
|
||||
public static string NameToUuid(string name) =>
|
||||
NameToUuidAsync(name).GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<string> NameToUuidAsync(string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Task<string> fetchTask = httpClient.GetStringAsync("https://api.mojang.com/users/profiles/minecraft/" + name);
|
||||
fetchTask.Wait();
|
||||
string result = Json.ParseJson(fetchTask.Result)!["id"]!.GetStringValue();
|
||||
fetchTask.Dispose();
|
||||
string responseBody = await httpClient.GetStringAsync("https://api.mojang.com/users/profiles/minecraft/" + name, cancellationToken);
|
||||
string result = Json.ParseJson(responseBody)!["id"]!.GetStringValue();
|
||||
return result;
|
||||
}
|
||||
catch (Exception) { return string.Empty; }
|
||||
|
|
@ -121,15 +123,16 @@ namespace MinecraftClient.Protocol
|
|||
/// </summary>
|
||||
/// <param name="uuid">UUID of a player</param>
|
||||
/// <returns>Players UUID</returns>
|
||||
public static string UuidToCurrentName(string uuid)
|
||||
public static string UuidToCurrentName(string uuid) =>
|
||||
UuidToCurrentNameAsync(uuid).GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<string> UuidToCurrentNameAsync(string uuid, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Perform web request
|
||||
try
|
||||
{
|
||||
Task<string> fetchTask = httpClient.GetStringAsync("https://api.mojang.com/user/profiles/" + uuid + "/names");
|
||||
fetchTask.Wait();
|
||||
var nameChanges = Json.ParseJson(fetchTask.Result)!.AsArray();
|
||||
fetchTask.Dispose();
|
||||
string responseBody = await httpClient.GetStringAsync("https://api.mojang.com/user/profiles/" + uuid + "/names", cancellationToken);
|
||||
var nameChanges = Json.ParseJson(responseBody)!.AsArray();
|
||||
|
||||
// Names are sorted from past to most recent. We need to get the last name in the list
|
||||
return nameChanges[^1]!["name"]!.GetStringValue();
|
||||
|
|
@ -142,7 +145,10 @@ namespace MinecraftClient.Protocol
|
|||
/// </summary>
|
||||
/// <param name="uuid">UUID of a player</param>
|
||||
/// <returns>Name history, as a dictionary</returns>
|
||||
public static Dictionary<string, DateTime> UuidToNameHistory(string uuid)
|
||||
public static Dictionary<string, DateTime> UuidToNameHistory(string uuid) =>
|
||||
UuidToNameHistoryAsync(uuid).GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<Dictionary<string, DateTime>> UuidToNameHistoryAsync(string uuid, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Dictionary<string, DateTime> tempDict = new();
|
||||
System.Text.Json.Nodes.JsonArray jsonDataList;
|
||||
|
|
@ -150,10 +156,8 @@ namespace MinecraftClient.Protocol
|
|||
// Perform web request
|
||||
try
|
||||
{
|
||||
Task<string> fetchTask = httpClient.GetStringAsync("https://api.mojang.com/user/profiles/" + uuid + "/names");
|
||||
fetchTask.Wait();
|
||||
jsonDataList = Json.ParseJson(fetchTask.Result)!.AsArray();
|
||||
fetchTask.Dispose();
|
||||
string responseBody = await httpClient.GetStringAsync("https://api.mojang.com/user/profiles/" + uuid + "/names", cancellationToken);
|
||||
jsonDataList = Json.ParseJson(responseBody)!.AsArray();
|
||||
}
|
||||
catch (Exception) { return tempDict; }
|
||||
|
||||
|
|
@ -181,17 +185,18 @@ namespace MinecraftClient.Protocol
|
|||
/// Get the Mojang API status
|
||||
/// </summary>
|
||||
/// <returns>Dictionary of the Mojang services</returns>
|
||||
public static MojangServiceStatus GetMojangServiceStatus()
|
||||
public static MojangServiceStatus GetMojangServiceStatus() =>
|
||||
GetMojangServiceStatusAsync().GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<MojangServiceStatus> GetMojangServiceStatusAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
System.Text.Json.Nodes.JsonArray jsonDataList;
|
||||
|
||||
// Perform web request
|
||||
try
|
||||
{
|
||||
Task<string> fetchTask = httpClient.GetStringAsync("https://status.mojang.com/check");
|
||||
fetchTask.Wait();
|
||||
jsonDataList = Json.ParseJson(fetchTask.Result)!.AsArray();
|
||||
fetchTask.Dispose();
|
||||
string responseBody = await httpClient.GetStringAsync("https://status.mojang.com/check", cancellationToken);
|
||||
jsonDataList = Json.ParseJson(responseBody)!.AsArray();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
@ -215,7 +220,10 @@ namespace MinecraftClient.Protocol
|
|||
/// </summary>
|
||||
/// <param uuid="uuid">UUID of a player</param>
|
||||
/// <returns>Dictionary with a link to the skin and cape of a player.</returns>
|
||||
public static SkinInfo GetSkinInfo(string uuid)
|
||||
public static SkinInfo GetSkinInfo(string uuid) =>
|
||||
GetSkinInfoAsync(uuid).GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<SkinInfo> GetSkinInfoAsync(string uuid, CancellationToken cancellationToken = default)
|
||||
{
|
||||
System.Text.Json.Nodes.JsonObject textureObj;
|
||||
string base64SkinInfo;
|
||||
|
|
@ -224,11 +232,9 @@ namespace MinecraftClient.Protocol
|
|||
// Perform web request
|
||||
try
|
||||
{
|
||||
Task<string> fetchTask = httpClient.GetStringAsync("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid);
|
||||
fetchTask.Wait();
|
||||
string responseBody = await httpClient.GetStringAsync("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid, cancellationToken);
|
||||
// Obtain the Base64 encoded skin information from the API. Discard the rest, since it can be obtained easier through other requests.
|
||||
base64SkinInfo = Json.ParseJson(fetchTask.Result)!["properties"]![0]!["value"]!.GetStringValue();
|
||||
fetchTask.Dispose();
|
||||
base64SkinInfo = Json.ParseJson(responseBody)!["properties"]![0]!["value"]!.GetStringValue();
|
||||
}
|
||||
catch (Exception) { return new SkinInfo(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -592,24 +592,23 @@ namespace MinecraftClient.Protocol
|
|||
/// <returns>Returns the status of the login (Success, Failure, etc.)</returns>
|
||||
public static LoginResult GetLogin(string user, string pass, LoginType type, out SessionToken session)
|
||||
{
|
||||
if (type == LoginType.microsoft)
|
||||
var login = GetLoginAsync(user, pass, type).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
public static Task<(LoginResult Result, SessionToken Session)> GetLoginAsync(string user, string pass, LoginType type, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
if (Config.Main.General.Method == LoginMethod.mcc)
|
||||
return MicrosoftMCCLogin(user, pass, out session);
|
||||
else
|
||||
return MicrosoftBrowserLogin(out session, user);
|
||||
}
|
||||
else if (type == LoginType.mojang)
|
||||
{
|
||||
return MojangLogin(user, pass, out session);
|
||||
}
|
||||
else if (type == LoginType.yggdrasil)
|
||||
{
|
||||
return YggdrasiLogin(user, pass, out session);
|
||||
}
|
||||
else
|
||||
throw new InvalidOperationException(
|
||||
"Account type must be Mojang or Microsoft or valid authlib 3rd Servers!");
|
||||
LoginType.microsoft => Config.Main.General.Method == LoginMethod.mcc
|
||||
? MicrosoftMCCLoginAsync(user, pass, cancellationToken)
|
||||
: MicrosoftBrowserLoginAsync(user, cancellationToken),
|
||||
LoginType.mojang => MojangLoginAsync(user, pass, cancellationToken),
|
||||
LoginType.yggdrasil => YggdrasiLoginAsync(user, pass, cancellationToken),
|
||||
_ => throw new InvalidOperationException(
|
||||
"Account type must be Mojang or Microsoft or valid authlib 3rd Servers!")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -621,20 +620,29 @@ namespace MinecraftClient.Protocol
|
|||
/// <returns></returns>
|
||||
private static LoginResult MojangLogin(string user, string pass, out SessionToken session)
|
||||
{
|
||||
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
var login = MojangLoginAsync(user, pass).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
private static async Task<(LoginResult Result, SessionToken Session)> MojangLoginAsync(string user, string pass, CancellationToken cancellationToken = default)
|
||||
{
|
||||
SessionToken session = new() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
|
||||
try
|
||||
{
|
||||
string result = "";
|
||||
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", 443, "/authenticate", json_request, ref result);
|
||||
var response = await DoHTTPSPostAsync("authserver.mojang.com", 443, "/authenticate", json_request, cancellationToken);
|
||||
int code = response.StatusCode;
|
||||
result = response.Result;
|
||||
if (code == 200)
|
||||
{
|
||||
if (result.Contains("availableProfiles\":[]}"))
|
||||
{
|
||||
return LoginResult.NotPremium;
|
||||
return (LoginResult.NotPremium, session);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -647,27 +655,27 @@ namespace MinecraftClient.Protocol
|
|||
session.PlayerID = loginResponse["selectedProfile"]!["id"]!.GetStringValue();
|
||||
session.PlayerName = loginResponse["selectedProfile"]!["name"]!
|
||||
.GetStringValue();
|
||||
return LoginResult.Success;
|
||||
return (LoginResult.Success, session);
|
||||
}
|
||||
else return LoginResult.InvalidResponse;
|
||||
else return (LoginResult.InvalidResponse, session);
|
||||
}
|
||||
}
|
||||
else if (code == 403)
|
||||
{
|
||||
if (result.Contains("UserMigratedException"))
|
||||
{
|
||||
return LoginResult.AccountMigrated;
|
||||
return (LoginResult.AccountMigrated, session);
|
||||
}
|
||||
else return LoginResult.WrongPassword;
|
||||
else return (LoginResult.WrongPassword, session);
|
||||
}
|
||||
else if (code == 503)
|
||||
{
|
||||
return LoginResult.ServiceUnavailable;
|
||||
return (LoginResult.ServiceUnavailable, session);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.error_http_code, code));
|
||||
return LoginResult.OtherError;
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
}
|
||||
catch (System.Security.Authentication.AuthenticationException e)
|
||||
|
|
@ -677,7 +685,7 @@ namespace MinecraftClient.Protocol
|
|||
ConsoleIO.WriteLineFormatted("§8" + e.ToString());
|
||||
}
|
||||
|
||||
return LoginResult.SSLError;
|
||||
return (LoginResult.SSLError, session);
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
|
|
@ -688,9 +696,9 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
if (e.Message.Contains("authentication"))
|
||||
{
|
||||
return LoginResult.SSLError;
|
||||
return (LoginResult.SSLError, session);
|
||||
}
|
||||
else return LoginResult.OtherError;
|
||||
else return (LoginResult.OtherError, session);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -699,28 +707,41 @@ namespace MinecraftClient.Protocol
|
|||
ConsoleIO.WriteLineFormatted("§8" + e.ToString());
|
||||
}
|
||||
|
||||
return LoginResult.OtherError;
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
}
|
||||
|
||||
private static LoginResult YggdrasiLogin(string user, string pass, out SessionToken session)
|
||||
{
|
||||
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
var login = YggdrasiLoginAsync(user, pass).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
private static async Task<(LoginResult Result, SessionToken Session)> YggdrasiLoginAsync(string user, string pass, CancellationToken cancellationToken = default)
|
||||
{
|
||||
SessionToken session = new() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
|
||||
try
|
||||
{
|
||||
string result = "";
|
||||
string result;
|
||||
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" +
|
||||
JsonEncode(user) + "\", \"password\": \"" + JsonEncode(pass) +
|
||||
"\", \"clientToken\": \"" + JsonEncode(session.ClientID) + "\" }";
|
||||
int code = DoHTTPSPost(Config.Main.General.AuthServer.Host, Config.Main.General.AuthServer.Port,
|
||||
Config.Main.General.AuthServer.AuthlibInjectorAPIPath + "/authserver/authenticate", json_request,
|
||||
Config.Main.General.AuthServer.UseHttps, ref result);
|
||||
var response = await DoHTTPSPostAsync(
|
||||
Config.Main.General.AuthServer.Host,
|
||||
Config.Main.General.AuthServer.Port,
|
||||
Config.Main.General.AuthServer.AuthlibInjectorAPIPath + "/authserver/authenticate",
|
||||
json_request,
|
||||
Config.Main.General.AuthServer.UseHttps,
|
||||
cancellationToken);
|
||||
int code = response.StatusCode;
|
||||
result = response.Result;
|
||||
if (code == 200)
|
||||
{
|
||||
if (result.Contains("availableProfiles\":[]}"))
|
||||
{
|
||||
return LoginResult.NotPremium;
|
||||
return (LoginResult.NotPremium, session);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -735,7 +756,7 @@ namespace MinecraftClient.Protocol
|
|||
.GetStringValue();
|
||||
session.PlayerName = loginResponse["selectedProfile"]!["name"]!
|
||||
.GetStringValue();
|
||||
return LoginResult.Success;
|
||||
return (LoginResult.Success, session);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -771,33 +792,33 @@ namespace MinecraftClient.Protocol
|
|||
session.PlayerID = selectedProfile["id"]!.GetStringValue();
|
||||
session.PlayerName = selectedProfile["name"]!.GetStringValue();
|
||||
SessionToken currentsession = session;
|
||||
return GetNewYggdrasilToken(currentsession, out session);
|
||||
return await GetNewYggdrasilTokenAsync(currentsession, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return LoginResult.WrongSelection;
|
||||
return (LoginResult.WrongSelection, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
else return LoginResult.InvalidResponse;
|
||||
else return (LoginResult.InvalidResponse, session);
|
||||
}
|
||||
}
|
||||
else if (code == 403)
|
||||
{
|
||||
if (result.Contains("UserMigratedException"))
|
||||
{
|
||||
return LoginResult.AccountMigrated;
|
||||
return (LoginResult.AccountMigrated, session);
|
||||
}
|
||||
else return LoginResult.WrongPassword;
|
||||
else return (LoginResult.WrongPassword, session);
|
||||
}
|
||||
else if (code == 503)
|
||||
{
|
||||
return LoginResult.ServiceUnavailable;
|
||||
return (LoginResult.ServiceUnavailable, session);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.error_http_code, code));
|
||||
return LoginResult.OtherError;
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
}
|
||||
catch (System.Security.Authentication.AuthenticationException e)
|
||||
|
|
@ -807,7 +828,7 @@ namespace MinecraftClient.Protocol
|
|||
ConsoleIO.WriteLineFormatted("§8" + e.ToString());
|
||||
}
|
||||
|
||||
return LoginResult.SSLError;
|
||||
return (LoginResult.SSLError, session);
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
|
|
@ -818,9 +839,9 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
if (e.Message.Contains("authentication"))
|
||||
{
|
||||
return LoginResult.SSLError;
|
||||
return (LoginResult.SSLError, session);
|
||||
}
|
||||
else return LoginResult.OtherError;
|
||||
else return (LoginResult.OtherError, session);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -829,7 +850,7 @@ namespace MinecraftClient.Protocol
|
|||
ConsoleIO.WriteLineFormatted("§8" + e.ToString());
|
||||
}
|
||||
|
||||
return LoginResult.OtherError;
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -842,10 +863,17 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="session"></param>
|
||||
/// <returns></returns>
|
||||
private static LoginResult MicrosoftMCCLogin(string email, string password, out SessionToken session)
|
||||
{
|
||||
var login = MicrosoftMCCLoginAsync(email, password).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
private static async Task<(LoginResult Result, SessionToken Session)> MicrosoftMCCLoginAsync(string email, string password, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deviceCode = Microsoft.RequestDeviceCode();
|
||||
var deviceCode = await Microsoft.RequestDeviceCodeAsync(cancellationToken);
|
||||
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_device_code_prompt, deviceCode.VerificationUri, deviceCode.UserCode));
|
||||
|
||||
|
|
@ -854,19 +882,19 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
ConsoleIO.WriteLineFormatted(Translations.mcc_device_code_waiting);
|
||||
|
||||
var msaResponse = Microsoft.PollDeviceCodeToken(deviceCode.DeviceCode, deviceCode.ExpiresIn, deviceCode.Interval);
|
||||
return MicrosoftLogin(msaResponse, out session);
|
||||
var msaResponse = await Microsoft.PollDeviceCodeTokenAsync(deviceCode.DeviceCode, deviceCode.ExpiresIn, deviceCode.Interval, cancellationToken);
|
||||
return await MicrosoftLoginAsync(msaResponse, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
SessionToken session = new() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
ConsoleIO.WriteLineFormatted("§cMicrosoft authenticate failed: " + e.Message);
|
||||
if (Settings.Config.Logging.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§c" + e.StackTrace);
|
||||
}
|
||||
|
||||
return LoginResult.OtherError;
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -881,6 +909,13 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="session"></param>
|
||||
/// <returns></returns>
|
||||
public static LoginResult MicrosoftBrowserLogin(out SessionToken session, string loginHint = "")
|
||||
{
|
||||
var login = MicrosoftBrowserLoginAsync(loginHint).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
public static async Task<(LoginResult Result, SessionToken Session)> MicrosoftBrowserLoginAsync(string loginHint = "", CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(loginHint))
|
||||
Microsoft.OpenBrowser(Microsoft.SignInUrl);
|
||||
|
|
@ -893,40 +928,54 @@ namespace MinecraftClient.Protocol
|
|||
string code = ConsoleIO.ReadLine();
|
||||
ConsoleIO.WriteLine(string.Format(Translations.mcc_connecting, "Microsoft"));
|
||||
|
||||
var msaResponse = Microsoft.RequestAccessToken(code);
|
||||
return MicrosoftLogin(msaResponse, out session);
|
||||
var msaResponse = await Microsoft.RequestAccessTokenAsync(code);
|
||||
return await MicrosoftLoginAsync(msaResponse, cancellationToken);
|
||||
}
|
||||
|
||||
public static LoginResult MicrosoftLoginRefresh(string refreshToken, out SessionToken session)
|
||||
{
|
||||
var msaResponse = Microsoft.RefreshAccessToken(refreshToken);
|
||||
return MicrosoftLogin(msaResponse, out session);
|
||||
var login = MicrosoftLoginRefreshAsync(refreshToken).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
public static async Task<(LoginResult Result, SessionToken Session)> MicrosoftLoginRefreshAsync(string refreshToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var msaResponse = await Microsoft.RefreshAccessTokenAsync(refreshToken);
|
||||
return await MicrosoftLoginAsync(msaResponse, cancellationToken);
|
||||
}
|
||||
|
||||
private static LoginResult MicrosoftLogin(Microsoft.LoginResponse msaResponse, out SessionToken session)
|
||||
{
|
||||
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
var login = MicrosoftLoginAsync(msaResponse).GetAwaiter().GetResult();
|
||||
session = login.Session;
|
||||
return login.Result;
|
||||
}
|
||||
|
||||
private static async Task<(LoginResult Result, SessionToken Session)> MicrosoftLoginAsync(Microsoft.LoginResponse msaResponse, CancellationToken cancellationToken = default)
|
||||
{
|
||||
SessionToken session = new() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
|
||||
try
|
||||
{
|
||||
var xblResponse = XboxLive.XblAuthenticate(msaResponse);
|
||||
var xsts = XboxLive.XSTSAuthenticate(xblResponse); // Might throw even password correct
|
||||
var xblResponse = await XboxLive.XblAuthenticateAsync(msaResponse, cancellationToken);
|
||||
var xsts = await XboxLive.XSTSAuthenticateAsync(xblResponse, cancellationToken); // Might throw even password correct
|
||||
|
||||
string accessToken = MinecraftWithXbox.LoginWithXbox(xsts.UserHash, xsts.Token);
|
||||
bool hasGame = MinecraftWithXbox.UserHasGame(accessToken);
|
||||
string accessToken = await MinecraftWithXbox.LoginWithXboxAsync(xsts.UserHash, xsts.Token, cancellationToken);
|
||||
bool hasGame = await MinecraftWithXbox.UserHasGameAsync(accessToken, cancellationToken);
|
||||
if (hasGame)
|
||||
{
|
||||
var profile = MinecraftWithXbox.GetUserProfile(accessToken);
|
||||
var profile = await MinecraftWithXbox.GetUserProfileAsync(accessToken, cancellationToken);
|
||||
session.PlayerName = profile.UserName;
|
||||
session.PlayerID = profile.UUID;
|
||||
session.ID = accessToken;
|
||||
session.RefreshToken = msaResponse.RefreshToken;
|
||||
InternalConfig.Account.Login = msaResponse.Email;
|
||||
return LoginResult.Success;
|
||||
return (LoginResult.Success, session);
|
||||
}
|
||||
else
|
||||
{
|
||||
return LoginResult.NotPremium;
|
||||
return (LoginResult.NotPremium, session);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -937,7 +986,7 @@ namespace MinecraftClient.Protocol
|
|||
ConsoleIO.WriteLineFormatted("§c" + e.StackTrace);
|
||||
}
|
||||
|
||||
return LoginResult.WrongPassword; // Might not always be wrong password
|
||||
return (LoginResult.WrongPassword, session); // Might not always be wrong password
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1077,6 +1126,52 @@ namespace MinecraftClient.Protocol
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task<(LoginResult Result, SessionToken Session)> GetNewYggdrasilTokenAsync(SessionToken currentsession, CancellationToken cancellationToken = default)
|
||||
{
|
||||
SessionToken session = new();
|
||||
try
|
||||
{
|
||||
string json_request = "{ \"accessToken\": \"" + JsonEncode(currentsession.ID) +
|
||||
"\", \"clientToken\": \"" + JsonEncode(currentsession.ClientID) +
|
||||
"\", \"selectedProfile\": { \"id\": \"" + JsonEncode(currentsession.PlayerID) +
|
||||
"\", \"name\": \"" + JsonEncode(currentsession.PlayerName) + "\" } }";
|
||||
var response = await DoHTTPSPostAsync(
|
||||
Config.Main.General.AuthServer.Host,
|
||||
Config.Main.General.AuthServer.Port,
|
||||
Config.Main.General.AuthServer.AuthlibInjectorAPIPath + "/authserver/refresh",
|
||||
json_request,
|
||||
Config.Main.General.AuthServer.UseHttps,
|
||||
cancellationToken);
|
||||
string result = response.Result;
|
||||
int code = response.StatusCode;
|
||||
if (code == 200)
|
||||
{
|
||||
var loginResponse = Json.ParseJson(result);
|
||||
if (loginResponse?["accessToken"] is not null
|
||||
&& loginResponse["selectedProfile"]?["id"] is not null
|
||||
&& loginResponse["selectedProfile"]?["name"] is not null)
|
||||
{
|
||||
session.ID = loginResponse["accessToken"]!.GetStringValue();
|
||||
session.PlayerID = loginResponse["selectedProfile"]!["id"]!.GetStringValue();
|
||||
session.PlayerName = loginResponse["selectedProfile"]!["name"]!.GetStringValue();
|
||||
return (LoginResult.Success, session);
|
||||
}
|
||||
|
||||
return (LoginResult.InvalidResponse, session);
|
||||
}
|
||||
|
||||
if (code == 403 && result.Contains("InvalidToken"))
|
||||
return (LoginResult.InvalidToken, session);
|
||||
|
||||
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.error_auth, code));
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (LoginResult.OtherError, session);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
|
||||
/// </summary>
|
||||
|
|
@ -1154,15 +1249,18 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="uuid">Player UUID</param>
|
||||
/// <param name="accesstoken">Access token</param>
|
||||
/// <returns>List of ID of available Realms worlds</returns>
|
||||
public static List<string> RealmsListWorlds(string username, string uuid, string accesstoken)
|
||||
public static List<string> RealmsListWorlds(string username, string uuid, string accesstoken) =>
|
||||
RealmsListWorldsAsync(username, uuid, accesstoken).GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<List<string>> RealmsListWorldsAsync(string username, string uuid, string accesstoken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
List<string> realmsWorldsResult = new(); // Store world ID
|
||||
try
|
||||
{
|
||||
string result = "";
|
||||
string result;
|
||||
string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username,
|
||||
Program.MCHighestVersion);
|
||||
DoHTTPSGet("pc.realms.minecraft.net", 443, "/worlds", cookies, ref result);
|
||||
(_, result) = await DoHTTPSGetAsync("pc.realms.minecraft.net", 443, "/worlds", cookies, cancellationToken);
|
||||
var realmsWorlds = Json.ParseJson(result);
|
||||
if (realmsWorlds?["servers"] is System.Text.Json.Nodes.JsonArray serversArray
|
||||
&& serversArray.Count > 0)
|
||||
|
|
@ -1218,15 +1316,21 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="accesstoken">Access token</param>
|
||||
/// <returns>Server address (host:port) or empty string if failure</returns>
|
||||
public static string GetRealmsWorldServerAddress(string worldId, string username, string uuid,
|
||||
string accesstoken)
|
||||
string accesstoken) =>
|
||||
GetRealmsWorldServerAddressAsync(worldId, username, uuid, accesstoken).GetAwaiter().GetResult();
|
||||
|
||||
public static async Task<string> GetRealmsWorldServerAddressAsync(string worldId, string username, string uuid,
|
||||
string accesstoken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
string result = "";
|
||||
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", 443, "/worlds/v1/" + worldId + "/join/pc",
|
||||
cookies, ref result);
|
||||
var response = await DoHTTPSGetAsync("pc.realms.minecraft.net", 443, "/worlds/v1/" + worldId + "/join/pc",
|
||||
cookies, cancellationToken);
|
||||
int statusCode = response.StatusCode;
|
||||
result = response.Result;
|
||||
if (statusCode == 200)
|
||||
{
|
||||
var serverAddress = Json.ParseJson(result);
|
||||
|
|
@ -1266,6 +1370,13 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="result">Request result</param>
|
||||
/// <returns>HTTP Status code</returns>
|
||||
private static int DoHTTPSGet(string host, int port, string path, string cookies, ref string result)
|
||||
{
|
||||
var response = DoHTTPSGetAsync(host, port, path, cookies).GetAwaiter().GetResult();
|
||||
result = response.Result;
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
private static Task<(int StatusCode, string Result)> DoHTTPSGetAsync(string host, int port, string path, string cookies, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Dictionary<string, string> headers = new()
|
||||
{
|
||||
|
|
@ -1274,7 +1385,7 @@ namespace MinecraftClient.Protocol
|
|||
{ "Pragma", "no-cache" },
|
||||
{ "User-Agent", "Java/1.6.0_27" }
|
||||
};
|
||||
return DoHTTPSRequest(HttpMethod.Get, host, port, path, headers, null, useHttps: true, ref result);
|
||||
return DoHTTPSRequestAsync(HttpMethod.Get, host, port, path, headers, null, useHttps: true, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1300,13 +1411,23 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="result">Request result</param>
|
||||
/// <returns>HTTP Status code</returns>
|
||||
private static int DoHTTPSPost(string host, int port, string path, string body, bool useHttps, ref string result)
|
||||
{
|
||||
var response = DoHTTPSPostAsync(host, port, path, body, useHttps).GetAwaiter().GetResult();
|
||||
result = response.Result;
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
private static Task<(int StatusCode, string Result)> DoHTTPSPostAsync(string host, int port, string path, string body, CancellationToken cancellationToken = default) =>
|
||||
DoHTTPSPostAsync(host, port, path, body, useHttps: true, cancellationToken);
|
||||
|
||||
private static Task<(int StatusCode, string Result)> DoHTTPSPostAsync(string host, int port, string path, string body, bool useHttps, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Dictionary<string, string> headers = new()
|
||||
{
|
||||
{ "User-Agent", "MCC/" + Program.Version },
|
||||
{ "Content-Type", "application/json" }
|
||||
};
|
||||
return DoHTTPSRequest(HttpMethod.Post, host, port, path, headers, body, useHttps, ref result);
|
||||
return DoHTTPSRequestAsync(HttpMethod.Post, host, port, path, headers, body, useHttps, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1323,69 +1444,11 @@ namespace MinecraftClient.Protocol
|
|||
/// <returns>HTTP Status code</returns>
|
||||
private static int DoHTTPSRequest(HttpMethod method, string host, int port, string path, Dictionary<string, string> headers, string? body, bool useHttps, ref string result)
|
||||
{
|
||||
string? postResult = null;
|
||||
int statusCode = 520;
|
||||
Exception? exception = null;
|
||||
AutoTimeout.Perform(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.Config.Logging.DebugMessages)
|
||||
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.debug_request, host));
|
||||
|
||||
using SocketsHttpHandler handler = new SocketsHttpHandler();
|
||||
handler.ConnectCallback = async (ctx, ct) =>
|
||||
{
|
||||
TcpClient client = ProxyHandler.NewTcpClient(host, port, true);
|
||||
return client.GetStream();
|
||||
};
|
||||
|
||||
using HttpClient client = new HttpClient(handler);
|
||||
|
||||
string scheme = useHttps ? "https" : "http";
|
||||
var request = new HttpRequestMessage(method, scheme + "://" + host + ":" + port + path);
|
||||
|
||||
var contentType = "text/plain";
|
||||
foreach (var header in headers)
|
||||
{
|
||||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
if (header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
|
||||
contentType = header.Value;
|
||||
}
|
||||
|
||||
if (body is not null)
|
||||
request.Content = new StringContent(body, Encoding.UTF8, contentType);
|
||||
|
||||
if (Settings.Config.Logging.DebugMessages)
|
||||
ConsoleIO.WriteLineFormatted("§8> " + request);
|
||||
|
||||
HttpResponseMessage response = client.SendAsync(request).GetAwaiter().GetResult();
|
||||
statusCode = (int)response.StatusCode;
|
||||
|
||||
postResult = statusCode == 204
|
||||
? "No Content"
|
||||
: response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
|
||||
if (Settings.Config.Logging.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine("");
|
||||
foreach (string line in postResult.Split('\n'))
|
||||
ConsoleIO.WriteLineFormatted("§8< " + line);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is not System.Threading.ThreadAbortException)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
}, TimeSpan.FromSeconds(30));
|
||||
if (postResult is not null)
|
||||
result = postResult;
|
||||
if (exception is not null)
|
||||
throw exception;
|
||||
return statusCode;
|
||||
var response = DoHTTPSRequestAsync(method, host, port, path, headers, body, useHttps, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
result = response.Result;
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
private static async Task<(int StatusCode, string Result)> DoHTTPSRequestAsync(HttpMethod method, string host, int port, string path, Dictionary<string, string> headers, string? body, bool useHttps, CancellationToken cancellationToken)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue