mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
feat: add guided login and authlib URL setup
This commit is contained in:
parent
47bc47786a
commit
630a5fabef
10 changed files with 528 additions and 66 deletions
|
|
@ -58,6 +58,7 @@ namespace MinecraftClient
|
||||||
private static int offlinePromptActive;
|
private static int offlinePromptActive;
|
||||||
private static int exitOnFailurePending;
|
private static int exitOnFailurePending;
|
||||||
private static string settingsIniPath = "MinecraftClient.ini";
|
private static string settingsIniPath = "MinecraftClient.ini";
|
||||||
|
private static AuthenticationSelection? pendingAuthenticationSelection;
|
||||||
|
|
||||||
// [SENTRY]
|
// [SENTRY]
|
||||||
// Setting this string to an empty string will disable Sentry
|
// Setting this string to an empty string will disable Sentry
|
||||||
|
|
@ -565,15 +566,19 @@ namespace MinecraftClient
|
||||||
// Setup exit cleaning code
|
// Setup exit cleaning code
|
||||||
ExitCleanUp.Add(() => { DoExit(); });
|
ExitCleanUp.Add(() => { DoExit(); });
|
||||||
|
|
||||||
|
if (HasNoConfiguredLoginDetails())
|
||||||
|
{
|
||||||
|
if (!PromptForAuthenticationSelection())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Asking the user to type in missing data such as Username and Password
|
//Asking the user to type in missing data such as Username and Password
|
||||||
bool useBrowser = Config.Main.General.AccountType == LoginType.microsoft && Config.Main.General.Method == LoginMethod.browser;
|
bool useBrowser = Config.Main.General.AccountType == LoginType.microsoft && Config.Main.General.Method == LoginMethod.browser;
|
||||||
bool useDeviceCode = Config.Main.General.AccountType == LoginType.microsoft && Config.Main.General.Method == LoginMethod.mcc;
|
bool useDeviceCode = Config.Main.General.AccountType == LoginType.microsoft && Config.Main.General.Method == LoginMethod.mcc;
|
||||||
bool skipPassword = useBrowser || useDeviceCode;
|
bool skipPassword = useBrowser || useDeviceCode;
|
||||||
if (string.IsNullOrWhiteSpace(InternalConfig.Account.Login) && !useBrowser)
|
if (string.IsNullOrWhiteSpace(InternalConfig.Account.Login) && !skipPassword)
|
||||||
{
|
{
|
||||||
ConsoleIO.WriteLine(ConsoleIO.BasicIO ? Translations.mcc_login_basic_io : Translations.mcc_login);
|
if (!RequestLogin())
|
||||||
InternalConfig.Account.Login = ConsoleIO.ReadLine().Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(InternalConfig.Account.Login))
|
|
||||||
{
|
{
|
||||||
HandleFailure(Translations.error_login_blocked, false, ChatBot.DisconnectReason.LoginRejected);
|
HandleFailure(Translations.error_login_blocked, false, ChatBot.DisconnectReason.LoginRejected);
|
||||||
return;
|
return;
|
||||||
|
|
@ -603,6 +608,145 @@ namespace MinecraftClient
|
||||||
InternalConfig.Account.Password = password;
|
InternalConfig.Account.Password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasNoConfiguredLoginDetails()
|
||||||
|
=> string.IsNullOrWhiteSpace(InternalConfig.Account.Login)
|
||||||
|
&& string.IsNullOrWhiteSpace(InternalConfig.Account.Password);
|
||||||
|
|
||||||
|
private static bool PromptForAuthenticationSelection()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_auth_method_prompt);
|
||||||
|
string selection = ConsoleIO.ReadLine().Trim();
|
||||||
|
|
||||||
|
switch (selection.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
|
case "offline":
|
||||||
|
BeginAuthenticationSelection(LoginType.mojang, LoginMethod.mcc);
|
||||||
|
if (!RequestLogin())
|
||||||
|
{
|
||||||
|
DiscardAuthenticationSelection();
|
||||||
|
HandleFailure(Translations.error_login_blocked, false, ChatBot.DisconnectReason.LoginRejected);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
InternalConfig.Account.Password = "-";
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "2":
|
||||||
|
case "online":
|
||||||
|
case "microsoft":
|
||||||
|
BeginAuthenticationSelection(LoginType.microsoft, LoginMethod.mcc);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "3":
|
||||||
|
case "yggdrasil":
|
||||||
|
BeginAuthenticationSelection(LoginType.yggdrasil, LoginMethod.mcc);
|
||||||
|
if (!RequestLogin() || !RequestRequiredPassword())
|
||||||
|
{
|
||||||
|
DiscardAuthenticationSelection();
|
||||||
|
HandleFailure(Translations.error_login_blocked, false, ChatBot.DisconnectReason.LoginRejected);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RequestAuthlibServer())
|
||||||
|
{
|
||||||
|
DiscardAuthenticationSelection();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_auth_method_invalid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BeginAuthenticationSelection(LoginType accountType, LoginMethod method)
|
||||||
|
{
|
||||||
|
pendingAuthenticationSelection ??= new AuthenticationSelection(
|
||||||
|
Config.Main.General.AccountType,
|
||||||
|
Config.Main.General.Method,
|
||||||
|
Config.Main.General.AuthServerUrl);
|
||||||
|
|
||||||
|
Config.Main.General.AccountType = accountType;
|
||||||
|
Config.Main.General.Method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RequestLogin()
|
||||||
|
{
|
||||||
|
ConsoleIO.WriteLine(ConsoleIO.BasicIO ? Translations.mcc_login_basic_io : Translations.mcc_login);
|
||||||
|
InternalConfig.Account.Login = ConsoleIO.ReadLine().Trim();
|
||||||
|
return !string.IsNullOrWhiteSpace(InternalConfig.Account.Login);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RequestRequiredPassword()
|
||||||
|
{
|
||||||
|
ConsoleIO.WriteLine(ConsoleIO.BasicIO ? string.Format(Translations.mcc_password_basic_io, InternalConfig.Account.Login) + "\n" : Translations.mcc_password_hidden);
|
||||||
|
string? password = ConsoleIO.BasicIO ? Console.ReadLine() : ConsoleIO.ReadPassword();
|
||||||
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
InternalConfig.Account.Password = password;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RequestAuthlibServer()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_yggdrasil_url);
|
||||||
|
string authServerUrl = ConsoleIO.ReadLine().Trim();
|
||||||
|
if (!Config.Main.General.TrySetAuthServerUrl(authServerUrl)
|
||||||
|
|| !Config.Main.General.TryGetAuthServerUri(out Uri? authServerUri))
|
||||||
|
{
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_yggdrasil_invalid_url);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ProtocolHandler.ValidateAuthlibServer(authServerUri))
|
||||||
|
{
|
||||||
|
case ProtocolHandler.AuthlibServerValidationResult.Valid:
|
||||||
|
return true;
|
||||||
|
case ProtocolHandler.AuthlibServerValidationResult.Unreachable:
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_yggdrasil_server_unreachable);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_yggdrasil_server_invalid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PersistAuthenticationSelection(SessionToken session)
|
||||||
|
{
|
||||||
|
if (pendingAuthenticationSelection is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(InternalConfig.Account.Login))
|
||||||
|
InternalConfig.Account.Login = session.PlayerName;
|
||||||
|
|
||||||
|
Config.Main.General.Account = InternalConfig.Account;
|
||||||
|
WriteBackSettings();
|
||||||
|
pendingAuthenticationSelection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DiscardAuthenticationSelection()
|
||||||
|
{
|
||||||
|
if (pendingAuthenticationSelection is not AuthenticationSelection selection)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Config.Main.General.AccountType = selection.AccountType;
|
||||||
|
Config.Main.General.Method = selection.Method;
|
||||||
|
Config.Main.General.AuthServerUrl = selection.AuthServerUrl;
|
||||||
|
pendingAuthenticationSelection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record AuthenticationSelection(LoginType AccountType, LoginMethod Method, string AuthServerUrl);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start a new Client
|
/// Start a new Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -664,17 +808,26 @@ namespace MinecraftClient
|
||||||
|
|
||||||
if (result != ProtocolHandler.LoginResult.Success)
|
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)));
|
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.AuthServerUrl)));
|
||||||
result = ProtocolHandler.GetLogin(InternalConfig.Account.Login, InternalConfig.Account.Password, Config.Main.General.AccountType, out session);
|
result = ProtocolHandler.GetLogin(InternalConfig.Account.Login, InternalConfig.Account.Password, Config.Main.General.AccountType, out session);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == ProtocolHandler.LoginResult.Success && Config.Main.Advanced.SessionCache != CacheType.none)
|
if (result == ProtocolHandler.LoginResult.Success)
|
||||||
SessionCache.Store(loginLower, session);
|
{
|
||||||
|
PersistAuthenticationSelection(session);
|
||||||
|
loginLower = ToLowerIfNeed(InternalConfig.Account.Login);
|
||||||
|
|
||||||
|
if (Config.Main.Advanced.SessionCache != CacheType.none)
|
||||||
|
SessionCache.Store(loginLower, session);
|
||||||
|
}
|
||||||
|
|
||||||
if (result == ProtocolHandler.LoginResult.Success)
|
if (result == ProtocolHandler.LoginResult.Success)
|
||||||
session.SessionPreCheckTask = Task.Factory.StartNew(() => session.SessionPreCheck(Config.Main.General.AccountType));
|
session.SessionPreCheckTask = Task.Factory.StartNew(() => session.SessionPreCheck(Config.Main.General.AccountType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result == ProtocolHandler.LoginResult.Success)
|
||||||
|
PersistAuthenticationSelection(session);
|
||||||
|
|
||||||
if (result == ProtocolHandler.LoginResult.Success)
|
if (result == ProtocolHandler.LoginResult.Success)
|
||||||
{
|
{
|
||||||
InternalConfig.Username = session.PlayerName;
|
InternalConfig.Username = session.PlayerName;
|
||||||
|
|
@ -859,6 +1012,7 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
DiscardAuthenticationSelection();
|
||||||
string failureMessage = Translations.error_login;
|
string failureMessage = Translations.error_login;
|
||||||
string failureReason = result switch
|
string failureReason = result switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ namespace MinecraftClient.Protocol
|
||||||
// Extract email from JWT id_token
|
// Extract email from JWT id_token
|
||||||
string payload = JwtPayloadDecode.GetPayload(jsonData["id_token"]!.GetStringValue());
|
string payload = JwtPayloadDecode.GetPayload(jsonData["id_token"]!.GetStringValue());
|
||||||
var jsonPayload = Json.ParseJson(payload);
|
var jsonPayload = Json.ParseJson(payload);
|
||||||
string email = jsonPayload!["email"]!.GetStringValue();
|
string email = jsonPayload?["email"]?.GetStringValue() ?? string.Empty;
|
||||||
|
|
||||||
return new LoginResponse()
|
return new LoginResponse()
|
||||||
{
|
{
|
||||||
|
|
@ -195,7 +195,7 @@ namespace MinecraftClient.Protocol
|
||||||
// Extract email from JWT
|
// Extract email from JWT
|
||||||
string payload = JwtPayloadDecode.GetPayload(jsonData["id_token"]!.GetStringValue());
|
string payload = JwtPayloadDecode.GetPayload(jsonData["id_token"]!.GetStringValue());
|
||||||
var jsonPayload = Json.ParseJson(payload);
|
var jsonPayload = Json.ParseJson(payload);
|
||||||
string email = jsonPayload!["email"]!.GetStringValue();
|
string email = jsonPayload?["email"]?.GetStringValue() ?? string.Empty;
|
||||||
return new LoginResponse()
|
return new LoginResponse()
|
||||||
{
|
{
|
||||||
Email = email,
|
Email = email,
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@ namespace MinecraftClient.Protocol.ProfileKey
|
||||||
ProxiedWebRequest.Response? response = null;
|
ProxiedWebRequest.Response? response = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var authServer = Settings.Config.Main.General.AuthServer;
|
if (!Settings.Config.Main.General.TryGetAuthServerUri(out Uri? authServerUri))
|
||||||
var request = new ProxiedWebRequest(
|
return false;
|
||||||
(authServer.UseHttps ? "https" : "http") + "://" + authServer.Host + ":" + authServer.Port + authServer.AuthlibInjectorAPIPath)
|
|
||||||
|
var request = new ProxiedWebRequest(authServerUri.AbsoluteUri)
|
||||||
{
|
{
|
||||||
Accept = "application/json"
|
Accept = "application/json"
|
||||||
};
|
};
|
||||||
|
|
@ -66,9 +67,10 @@ namespace MinecraftClient.Protocol.ProfileKey
|
||||||
string certificatesURL = "https://api.minecraftservices.com/player/certificates";
|
string certificatesURL = "https://api.minecraftservices.com/player/certificates";
|
||||||
if (isYggdrasil)
|
if (isYggdrasil)
|
||||||
{
|
{
|
||||||
var authServer = Settings.Config.Main.General.AuthServer;
|
if (!Settings.Config.Main.General.TryGetAuthServerUri(out Uri? authServerUri))
|
||||||
certificatesURL = (authServer.UseHttps ? "https" : "http") + "://" + authServer.Host + ":" + authServer.Port +
|
return null;
|
||||||
authServer.AuthlibInjectorAPIPath + "/minecraftservices/player/certificates";
|
|
||||||
|
certificatesURL = new Uri(authServerUri, "minecraftservices/player/certificates").AbsoluteUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxiedWebRequest.Response? response = null;
|
ProxiedWebRequest.Response? response = null;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ namespace MinecraftClient.Protocol
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class ProtocolHandler
|
public static class ProtocolHandler
|
||||||
{
|
{
|
||||||
|
public enum AuthlibServerValidationResult
|
||||||
|
{
|
||||||
|
Valid,
|
||||||
|
Unreachable,
|
||||||
|
InvalidResponse
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform a DNS lookup for a Minecraft Service using the specified domain name
|
/// Perform a DNS lookup for a Minecraft Service using the specified domain name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -134,6 +141,37 @@ namespace MinecraftClient.Protocol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that an authlib-injector URL is reachable and returns its metadata document.
|
||||||
|
/// </summary>
|
||||||
|
public static AuthlibServerValidationResult ValidateAuthlibServer(Uri authServerUri)
|
||||||
|
{
|
||||||
|
string result = string.Empty;
|
||||||
|
int statusCode;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
statusCode = DoHTTPSGet(authServerUri, ref result);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return AuthlibServerValidationResult.Unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode != 200)
|
||||||
|
return AuthlibServerValidationResult.InvalidResponse;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Json.ParseJson(result)?["meta"]?["implementationName"]?.GetStringValue() is { Length: > 0 }
|
||||||
|
? AuthlibServerValidationResult.Valid
|
||||||
|
: AuthlibServerValidationResult.InvalidResponse;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return AuthlibServerValidationResult.InvalidResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a protocol handler for the specified Minecraft version
|
/// Get a protocol handler for the specified Minecraft version
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -714,9 +752,10 @@ namespace MinecraftClient.Protocol
|
||||||
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" +
|
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" +
|
||||||
JsonEncode(user) + "\", \"password\": \"" + JsonEncode(pass) +
|
JsonEncode(user) + "\", \"password\": \"" + JsonEncode(pass) +
|
||||||
"\", \"clientToken\": \"" + JsonEncode(session.ClientID) + "\" }";
|
"\", \"clientToken\": \"" + JsonEncode(session.ClientID) + "\" }";
|
||||||
int code = DoHTTPSPost(Config.Main.General.AuthServer.Host, Config.Main.General.AuthServer.Port,
|
if (!Config.Main.General.TryGetAuthServerUri(out Uri? authServerUri))
|
||||||
Config.Main.General.AuthServer.AuthlibInjectorAPIPath + "/authserver/authenticate", json_request,
|
return LoginResult.OtherError;
|
||||||
Config.Main.General.AuthServer.UseHttps, ref result);
|
|
||||||
|
int code = DoHTTPSPost(authServerUri, "authserver/authenticate", json_request, ref result);
|
||||||
if (code == 200)
|
if (code == 200)
|
||||||
{
|
{
|
||||||
if (result.Contains("availableProfiles\":[]}"))
|
if (result.Contains("availableProfiles\":[]}"))
|
||||||
|
|
@ -922,7 +961,8 @@ namespace MinecraftClient.Protocol
|
||||||
session.PlayerID = profile.UUID;
|
session.PlayerID = profile.UUID;
|
||||||
session.ID = accessToken;
|
session.ID = accessToken;
|
||||||
session.RefreshToken = msaResponse.RefreshToken;
|
session.RefreshToken = msaResponse.RefreshToken;
|
||||||
InternalConfig.Account.Login = msaResponse.Email;
|
if (!string.IsNullOrWhiteSpace(msaResponse.Email))
|
||||||
|
InternalConfig.Account.Login = msaResponse.Email;
|
||||||
return LoginResult.Success;
|
return LoginResult.Success;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1037,9 +1077,10 @@ namespace MinecraftClient.Protocol
|
||||||
"\", \"clientToken\": \"" + JsonEncode(currentsession.ClientID) +
|
"\", \"clientToken\": \"" + JsonEncode(currentsession.ClientID) +
|
||||||
"\", \"selectedProfile\": { \"id\": \"" + JsonEncode(currentsession.PlayerID) +
|
"\", \"selectedProfile\": { \"id\": \"" + JsonEncode(currentsession.PlayerID) +
|
||||||
"\", \"name\": \"" + JsonEncode(currentsession.PlayerName) + "\" } }";
|
"\", \"name\": \"" + JsonEncode(currentsession.PlayerName) + "\" } }";
|
||||||
int code = DoHTTPSPost(Config.Main.General.AuthServer.Host, Config.Main.General.AuthServer.Port,
|
if (!Config.Main.General.TryGetAuthServerUri(out Uri? authServerUri))
|
||||||
Config.Main.General.AuthServer.AuthlibInjectorAPIPath + "/authserver/refresh", json_request,
|
return LoginResult.OtherError;
|
||||||
Config.Main.General.AuthServer.UseHttps, ref result);
|
|
||||||
|
int code = DoHTTPSPost(authServerUri, "authserver/refresh", json_request, ref result);
|
||||||
if (code == 200)
|
if (code == 200)
|
||||||
{
|
{
|
||||||
if (result is null)
|
if (result is null)
|
||||||
|
|
@ -1093,16 +1134,18 @@ namespace MinecraftClient.Protocol
|
||||||
string result = "";
|
string result = "";
|
||||||
string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid +
|
string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid +
|
||||||
"\",\"serverId\":\"" + serverhash + "\"}";
|
"\",\"serverId\":\"" + serverhash + "\"}";
|
||||||
string host = type == LoginType.yggdrasil
|
int code;
|
||||||
? Config.Main.General.AuthServer.Host
|
if (type == LoginType.yggdrasil)
|
||||||
: "sessionserver.mojang.com";
|
{
|
||||||
int port = type == LoginType.yggdrasil ? Config.Main.General.AuthServer.Port : 443;
|
if (!Config.Main.General.TryGetAuthServerUri(out Uri? authServerUri))
|
||||||
string endpoint = type == LoginType.yggdrasil
|
return false;
|
||||||
? Config.Main.General.AuthServer.AuthlibInjectorAPIPath + "/sessionserver/session/minecraft/join"
|
|
||||||
: "/session/minecraft/join";
|
|
||||||
|
|
||||||
bool useHttps = type == LoginType.yggdrasil ? Config.Main.General.AuthServer.UseHttps : true;
|
code = DoHTTPSPost(authServerUri, "sessionserver/session/minecraft/join", json_request, ref result);
|
||||||
int code = DoHTTPSPost(host, port, endpoint, json_request, useHttps, ref result);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
code = DoHTTPSPost("sessionserver.mojang.com", 443, "/session/minecraft/join", json_request, ref result);
|
||||||
|
}
|
||||||
return (code >= 200 && code < 300);
|
return (code >= 200 && code < 300);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -1241,6 +1284,16 @@ namespace MinecraftClient.Protocol
|
||||||
return DoHTTPSRequest(HttpMethod.Get, host, port, path, headers, null, useHttps: true, ref result);
|
return DoHTTPSRequest(HttpMethod.Get, host, port, path, headers, null, useHttps: true, ref result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int DoHTTPSGet(Uri requestUri, ref string result)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> headers = new()
|
||||||
|
{
|
||||||
|
{ "User-Agent", "MCC/" + Program.Version }
|
||||||
|
};
|
||||||
|
return DoHTTPSRequest(HttpMethod.Get, requestUri.Host, requestUri.Port, requestUri.PathAndQuery, headers,
|
||||||
|
null, requestUri.Scheme == Uri.UriSchemeHttps, ref result);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make a POST request to the specified endpoint of the Mojang API
|
/// Make a POST request to the specified endpoint of the Mojang API
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1253,6 +1306,13 @@ namespace MinecraftClient.Protocol
|
||||||
private static int DoHTTPSPost(string host, int port, string path, string body, ref string result)
|
private static int DoHTTPSPost(string host, int port, string path, string body, ref string result)
|
||||||
=> DoHTTPSPost(host, port, path, body, useHttps: true, ref result);
|
=> DoHTTPSPost(host, port, path, body, useHttps: true, ref result);
|
||||||
|
|
||||||
|
private static int DoHTTPSPost(Uri baseUri, string relativePath, string body, ref string result)
|
||||||
|
{
|
||||||
|
Uri requestUri = new(baseUri, relativePath);
|
||||||
|
return DoHTTPSPost(requestUri.Host, requestUri.Port, requestUri.PathAndQuery, body,
|
||||||
|
requestUri.Scheme == Uri.UriSchemeHttps, ref result);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make a POST request to the specified endpoint of the Mojang API
|
/// Make a POST request to the specified endpoint of the Mojang API
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1392,4 +1452,4 @@ namespace MinecraftClient.Protocol
|
||||||
return dateTime;
|
return dateTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2011,6 +2011,15 @@ namespace MinecraftClient {
|
||||||
return ResourceManager.GetString("Main.General.AuthlibServer", resourceCulture);
|
return ResourceManager.GetString("Main.General.AuthlibServer", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Authlib-injector URL to use for Yggdrasil accounts. It must use http or https and include any required path..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Main_General_AuthServerUrl {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Main.General.AuthServerUrl", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Yggdrasil authlib multi-user selection..
|
/// Looks up a localized string similar to Yggdrasil authlib multi-user selection..
|
||||||
|
|
|
||||||
|
|
@ -983,6 +983,9 @@ Note: This does NOT require a Bot Token, only an Application ID. Discord must be
|
||||||
<data name="Main.General.AuthlibServer" xml:space="preserve">
|
<data name="Main.General.AuthlibServer" xml:space="preserve">
|
||||||
<value>authlib-injector authentication server to use for Yggdrasil accounts</value>
|
<value>authlib-injector authentication server to use for Yggdrasil accounts</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Main.General.AuthServerUrl" xml:space="preserve">
|
||||||
|
<value>Authlib-injector URL to use for Yggdrasil accounts. It must use http or https and include any required path.</value>
|
||||||
|
</data>
|
||||||
<data name="AuthlibServer.Host" xml:space="preserve">
|
<data name="AuthlibServer.Host" xml:space="preserve">
|
||||||
<value>Domain name or IP address</value>
|
<value>Domain name or IP address</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -5998,6 +5998,24 @@ namespace MinecraftClient {
|
||||||
return ResourceManager.GetString("mcc.connecting", resourceCulture);
|
return ResourceManager.GetString("mcc.connecting", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Select a login method: [1] Offline, [2] Online (Microsoft), [3] Yggdrasil.
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_auth_method_prompt {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.auth_method_prompt", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Please choose 1, 2, or 3..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_auth_method_invalid {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.auth_method_invalid", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Tip: try TUI mode for a cleaner interface, mouse-friendly container actions, and a nicer layout. Run {0}feature tui§8 to switch [Console.General] ConsoleMode to "tui" for the next restart..
|
/// Looks up a localized string similar to Tip: try TUI mode for a cleaner interface, mouse-friendly container actions, and a nicer layout. Run {0}feature tui§8 to switch [Console.General] ConsoleMode to "tui" for the next restart..
|
||||||
|
|
@ -6271,6 +6289,114 @@ namespace MinecraftClient {
|
||||||
return ResourceManager.GetString("mcc.password_hidden", resourceCulture);
|
return ResourceManager.GetString("mcc.password_hidden", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Authlib server host:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_host {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_host", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Authlib server port [{0}]:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_port {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_port", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Authlib-Injector API path [{0}]:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_api_path {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_api_path", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Use HTTPS? [Y/n].
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_use_https {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_use_https", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The authlib server host cannot be empty..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_invalid_host {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_invalid_host", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The authlib server port must be between 1 and 65535..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_invalid_port {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_invalid_port", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Please answer yes or no..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_invalid_yes_no {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_invalid_yes_no", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Authlib-injector URL:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_url {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_url", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The authlib-injector URL must be an absolute HTTP or HTTPS URL..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_invalid_url {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_invalid_url", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Could not reach the authlib-injector server. Check the URL and try again..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_server_unreachable {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_server_unreachable", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The server did not return valid authlib-injector metadata. Check the URL and try again..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_yggdrasil_server_invalid {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.yggdrasil_server_invalid", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The configured authlib-injector URL must be an absolute HTTP or HTTPS URL..
|
||||||
|
/// </summary>
|
||||||
|
internal static string config_auth_server_url_invalid {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("config.auth_server_url_invalid", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to You are dead. Type '{0}respawn' to respawn..
|
/// Looks up a localized string similar to You are dead. Type '{0}respawn' to respawn..
|
||||||
|
|
|
||||||
|
|
@ -2004,6 +2004,27 @@ You can use "/chunk status {0:0.0} {1:0.0} {2:0.0}" to check the chunk loading s
|
||||||
<data name="mcc.connecting" xml:space="preserve">
|
<data name="mcc.connecting" xml:space="preserve">
|
||||||
<value>Connecting to {0}...</value>
|
<value>Connecting to {0}...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="mcc.auth_method_prompt" xml:space="preserve">
|
||||||
|
<value>Select a login method: [1] Offline, [2] Online (Microsoft), [3] Yggdrasil</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.auth_method_invalid" xml:space="preserve">
|
||||||
|
<value>Please choose 1, 2, or 3.</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_url" xml:space="preserve">
|
||||||
|
<value>Authlib-injector URL:</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_invalid_url" xml:space="preserve">
|
||||||
|
<value>The authlib-injector URL must be an absolute HTTP or HTTPS URL.</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_server_unreachable" xml:space="preserve">
|
||||||
|
<value>Could not reach the authlib-injector server. Check the URL and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_server_invalid" xml:space="preserve">
|
||||||
|
<value>The server did not return valid authlib-injector metadata. Check the URL and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="config.auth_server_url_invalid" xml:space="preserve">
|
||||||
|
<value>The configured authlib-injector URL must be an absolute HTTP or HTTPS URL.</value>
|
||||||
|
</data>
|
||||||
<data name="mcc.console_mode_tui_recommendation" xml:space="preserve">
|
<data name="mcc.console_mode_tui_recommendation" xml:space="preserve">
|
||||||
<value>Tip: try TUI mode for a cleaner interface, mouse-friendly inventory actions, and a nicer layout. Run {0}tryout tui§8 to switch [Console.General] ConsoleMode to "tui" for the next restart.</value>
|
<value>Tip: try TUI mode for a cleaner interface, mouse-friendly inventory actions, and a nicer layout. Run {0}tryout tui§8 to switch [Console.General] ConsoleMode to "tui" for the next restart.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -2097,6 +2118,27 @@ Type '{0}quit' to leave the server.</value>
|
||||||
<data name="mcc.password_hidden" xml:space="preserve">
|
<data name="mcc.password_hidden" xml:space="preserve">
|
||||||
<value>Password(invisible): </value>
|
<value>Password(invisible): </value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_host" xml:space="preserve">
|
||||||
|
<value>Authlib server host:</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_port" xml:space="preserve">
|
||||||
|
<value>Authlib server port [{0}]:</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_api_path" xml:space="preserve">
|
||||||
|
<value>Authlib-Injector API path [{0}]:</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_use_https" xml:space="preserve">
|
||||||
|
<value>Use HTTPS? [Y/n]</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_invalid_host" xml:space="preserve">
|
||||||
|
<value>The authlib server host cannot be empty.</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_invalid_port" xml:space="preserve">
|
||||||
|
<value>The authlib server port must be between 1 and 65535.</value>
|
||||||
|
</data>
|
||||||
|
<data name="mcc.yggdrasil_invalid_yes_no" xml:space="preserve">
|
||||||
|
<value>Please answer yes or no.</value>
|
||||||
|
</data>
|
||||||
<data name="mcc.player_dead" xml:space="preserve">
|
<data name="mcc.player_dead" xml:space="preserve">
|
||||||
<value>You are dead. Type '{0}respawn' to respawn.</value>
|
<value>You are dead. Type '{0}respawn' to respawn.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,7 @@ namespace MinecraftClient
|
||||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||||
string tomlString = TomletMain.TomlStringFrom(Config);
|
string tomlString = TomletMain.TomlStringFrom(Config);
|
||||||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||||
|
tomlString = RemoveLegacyAuthServerSection(tomlString);
|
||||||
|
|
||||||
string[] tomlList = tomlString.Split('\n');
|
string[] tomlList = tomlString.Split('\n');
|
||||||
StringBuilder newConfig = new();
|
StringBuilder newConfig = new();
|
||||||
|
|
@ -272,6 +273,19 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string RemoveLegacyAuthServerSection(string tomlString)
|
||||||
|
{
|
||||||
|
const string legacySection = "[Main.General.AuthServer]";
|
||||||
|
int sectionStart = tomlString.IndexOf(legacySection, StringComparison.Ordinal);
|
||||||
|
if (sectionStart < 0)
|
||||||
|
return tomlString;
|
||||||
|
|
||||||
|
int nextSection = tomlString.IndexOf("\n[", sectionStart + legacySection.Length, StringComparison.Ordinal);
|
||||||
|
return nextSection < 0
|
||||||
|
? tomlString[..sectionStart]
|
||||||
|
: tomlString.Remove(sectionStart, nextSection - sectionStart + 1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load settings from the command line
|
/// Load settings from the command line
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -656,6 +670,8 @@ namespace MinecraftClient
|
||||||
|
|
||||||
General.Account.Login ??= string.Empty;
|
General.Account.Login ??= string.Empty;
|
||||||
General.Account.Password ??= string.Empty;
|
General.Account.Password ??= string.Empty;
|
||||||
|
if (!General.MigrateLegacyAuthServer())
|
||||||
|
ConsoleIO.WriteLogLine(Translations.config_auth_server_url_invalid);
|
||||||
if (!InternalConfig.KeepAccountSettings)
|
if (!InternalConfig.KeepAccountSettings)
|
||||||
{
|
{
|
||||||
if (Advanced.AccountList.TryGetValue(General.Account.Login, out AccountInfoConfig account))
|
if (Advanced.AccountList.TryGetValue(General.Account.Login, out AccountInfoConfig account))
|
||||||
|
|
@ -753,12 +769,72 @@ namespace MinecraftClient
|
||||||
|
|
||||||
[TomlInlineComment("$Main.General.method$")]
|
[TomlInlineComment("$Main.General.method$")]
|
||||||
public LoginMethod Method = LoginMethod.mcc;
|
public LoginMethod Method = LoginMethod.mcc;
|
||||||
|
|
||||||
|
[TomlInlineComment("$Main.General.AuthServerUrl$")]
|
||||||
|
public string AuthServerUrl = string.Empty;
|
||||||
|
|
||||||
|
// Retained only to deserialize pre-URL configs. WriteToFile() removes this legacy section.
|
||||||
[TomlInlineComment("$Main.General.AuthlibServer$")]
|
[TomlInlineComment("$Main.General.AuthlibServer$")]
|
||||||
public AuthlibServer AuthServer = new();
|
public AuthlibServer AuthServer = new();
|
||||||
|
|
||||||
[TomlInlineComment("$Main.General.AuthlibUser$")]
|
[TomlInlineComment("$Main.General.AuthlibUser$")]
|
||||||
public string AuthUser = "";
|
public string AuthUser = "";
|
||||||
|
|
||||||
|
public bool MigrateLegacyAuthServer()
|
||||||
|
{
|
||||||
|
AuthServerUrl ??= string.Empty;
|
||||||
|
if (TrySetAuthServerUrl(AuthServerUrl))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(AuthServerUrl))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(AuthServer.Host))
|
||||||
|
{
|
||||||
|
string path = AuthServer.AuthlibInjectorAPIPath ?? string.Empty;
|
||||||
|
if (!path.StartsWith('/'))
|
||||||
|
path = '/' + path;
|
||||||
|
|
||||||
|
string legacyUrl = $"{(AuthServer.UseHttps ? "https" : "http")}://{AuthServer.Host}:{AuthServer.Port}{path}";
|
||||||
|
return TrySetAuthServerUrl(legacyUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySetAuthServerUrl(string url)
|
||||||
|
{
|
||||||
|
if (!TryGetNormalizedAuthServerUri(url, out Uri? authServerUri))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AuthServerUrl = authServerUri.AbsoluteUri;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetAuthServerUri([NotNullWhen(true)] out Uri? authServerUri)
|
||||||
|
=> TryGetNormalizedAuthServerUri(AuthServerUrl, out authServerUri);
|
||||||
|
|
||||||
|
private static bool TryGetNormalizedAuthServerUri(string? url, [NotNullWhen(true)] out Uri? authServerUri)
|
||||||
|
{
|
||||||
|
authServerUri = null;
|
||||||
|
if (!Uri.TryCreate(url?.Trim(), UriKind.Absolute, out Uri? parsedUri)
|
||||||
|
|| (!parsedUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !parsedUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|| string.IsNullOrWhiteSpace(parsedUri.Host)
|
||||||
|
|| !string.IsNullOrEmpty(parsedUri.UserInfo)
|
||||||
|
|| !string.IsNullOrEmpty(parsedUri.Query)
|
||||||
|
|| !string.IsNullOrEmpty(parsedUri.Fragment))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new UriBuilder(parsedUri)
|
||||||
|
{
|
||||||
|
Path = parsedUri.AbsolutePath.TrimEnd('/') + "/"
|
||||||
|
};
|
||||||
|
authServerUri = builder.Uri;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public enum LoginType { mojang, microsoft, yggdrasil };
|
public enum LoginType { mojang, microsoft, yggdrasil };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,16 @@ Coordinate = { x = 145, y = 64, z = 2045 }
|
||||||
AccountType = "microsoft"
|
AccountType = "microsoft"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Interactive login
|
||||||
|
|
||||||
|
If both `Account.Login` and `Account.Password` are empty when MCC starts, it asks which account type to use instead of assuming Microsoft login.
|
||||||
|
|
||||||
|
- Choose Offline to enter an in-game username. MCC saves the account as an offline account.
|
||||||
|
- Choose Online (Microsoft) to continue with the device-code sign-in. MCC shows the code and link, then opens the sign-in page in your browser.
|
||||||
|
- Choose Yggdrasil to enter the username, password, and authlib-injector URL for the account server. MCC checks the URL before it continues. If the address is malformed, unreachable, or does not return authlib-injector metadata, MCC explains the problem and asks for another URL.
|
||||||
|
|
||||||
|
After a successful login, MCC saves the account details and selected account type in the configuration file. Later starts use those saved details and do not show this prompt. To choose a different method, edit or clear the account settings in the configuration file.
|
||||||
|
|
||||||
#### `Method`
|
#### `Method`
|
||||||
|
|
||||||
- **Description:**
|
- **Description:**
|
||||||
|
|
@ -154,52 +164,32 @@ Coordinate = { x = 145, y = 64, z = 2045 }
|
||||||
Method = "mcc"
|
Method = "mcc"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `AuthServer`
|
#### `AuthServerUrl`
|
||||||
|
|
||||||
- **Description:**
|
- **Description:**
|
||||||
|
|
||||||
This subsection is used when `AccountType` is set to `yggdrasil`. It points MCC at the authlib/Yggdrasil server used for login, session checks, and profile key requests.
|
Use this setting with `AccountType = "yggdrasil"`. It is the complete base URL of the authlib-injector or Yggdrasil service MCC uses for login, session checks, and profile key requests.
|
||||||
|
|
||||||
MCC now writes this as a dedicated TOML subsection instead of an inline table:
|
The URL must start with `http://` or `https://` and include any path used by the service. For example, an authlib-injector server may use `/authlib-injector/` as its base path.
|
||||||
|
|
||||||
```toml
|
An existing multi-field Yggdrasil configuration is converted to this URL automatically. When MCC writes the configuration after migration, it removes the previous Yggdrasil section.
|
||||||
[Main.General.AuthServer]
|
|
||||||
```
|
|
||||||
|
|
||||||
`Host` accepts either a plain host name or a `host:port` pair. If you include the port there, MCC updates `Port` to match.
|
- **Type:** `string`
|
||||||
|
|
||||||
`AuthlibInjectorAPIPath` defaults to `/api/yggdrasil`. Change it if your authlib-injector server uses a different prefix, such as `/authlib-injector`.
|
- **Default:** `""`
|
||||||
|
|
||||||
`UseHttps` defaults to `true`. Set it to `false` if your local or development auth server only exposes plain HTTP.
|
|
||||||
|
|
||||||
- **Type:** `section`
|
|
||||||
|
|
||||||
- **Default:**
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[Main.General.AuthServer]
|
|
||||||
Port = 443
|
|
||||||
AuthlibInjectorAPIPath = "/api/yggdrasil"
|
|
||||||
UseHttps = true
|
|
||||||
Host = ""
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Example:**
|
- **Example:**
|
||||||
|
|
||||||
```
|
```toml
|
||||||
[Main.General.AuthServer]
|
Account = { Login = "player@example.com", Password = "password" }
|
||||||
Host = "auth.example.com"
|
AccountType = "yggdrasil"
|
||||||
Port = 443
|
AuthServerUrl = "https://auth.example.com/api/yggdrasil/"
|
||||||
AuthlibInjectorAPIPath = "/api/yggdrasil"
|
|
||||||
UseHttps = true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```toml
|
||||||
[Main.General.AuthServer]
|
Account = { Login = "player", Password = "password" }
|
||||||
Host = "127.0.0.1"
|
AccountType = "yggdrasil"
|
||||||
Port = 25585
|
AuthServerUrl = "http://127.0.0.1:25585/authlib-injector/"
|
||||||
AuthlibInjectorAPIPath = "/authlib-injector"
|
|
||||||
UseHttps = false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `AuthUser`
|
#### `AuthUser`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue