mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Implement Microsoft account login (#1397)
* Implement Microsoft account login
* Create proxied web request class
* Whole bunch of code that doesn't work
* I finally FIXED IT
It took me 2 hours to resolve the problem
* Fill the missed method summary
* Remove some unused code
* Revert http version
* Remove JSON parsing bug workaround
Not needed anymore as per e06438b582
* Remove comment asking about clientID
Client ID is used for session token refreshes. Random UUID without hyphens
Co-authored-by: ORelio <ORelio@users.noreply.github.com>
This commit is contained in:
parent
479f80ccf1
commit
c04c597aba
8 changed files with 690 additions and 4 deletions
|
|
@ -214,6 +214,8 @@
|
|||
<Compile Include="Protocol\DataTypeGenerator.cs" />
|
||||
<Compile Include="FileMonitor.cs" />
|
||||
<Compile Include="Inventory\VillagerTrade.cs" />
|
||||
<Compile Include="Protocol\MicrosoftAuthentication.cs" />
|
||||
<Compile Include="Protocol\ProxiedWebRequest.cs" />
|
||||
<Compile Include="Protocol\ReplayHandler.cs" />
|
||||
<Compile Include="Translations.cs" />
|
||||
<Compile Include="Inventory\VillagerInfo.cs" />
|
||||
|
|
|
|||
|
|
@ -201,8 +201,8 @@ namespace MinecraftClient
|
|||
|
||||
if (result != ProtocolHandler.LoginResult.Success)
|
||||
{
|
||||
Translations.WriteLine("mcc.connecting");
|
||||
result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session);
|
||||
Translations.WriteLine("mcc.connecting", Settings.AccountType == ProtocolHandler.AccountType.Mojang ? "Minecraft.net" : "Microsoft");
|
||||
result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, Settings.AccountType, out session);
|
||||
|
||||
if (result == ProtocolHandler.LoginResult.Success && Settings.SessionCaching != CacheType.None)
|
||||
{
|
||||
|
|
|
|||
345
MinecraftClient/Protocol/MicrosoftAuthentication.cs
Normal file
345
MinecraftClient/Protocol/MicrosoftAuthentication.cs
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
class XboxLive
|
||||
{
|
||||
private readonly string authorize = "https://login.live.com/oauth20_authorize.srf?client_id=000000004C12AE6F&redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=service::user.auth.xboxlive.com::MBI_SSL&display=touch&response_type=token&locale=en";
|
||||
private readonly string xbl = "https://user.auth.xboxlive.com/user/authenticate";
|
||||
private readonly string xsts = "https://xsts.auth.xboxlive.com/xsts/authorize";
|
||||
|
||||
private readonly string userAgent = "Mozilla/5.0 (XboxReplay; XboxLiveAuth/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";
|
||||
|
||||
private Regex ppft = new Regex("sFTTag:'.*value=\"(.*)\"\\/>'");
|
||||
private Regex urlPost = new Regex("urlPost:'(.+?(?=\'))");
|
||||
private Regex confirm = new Regex("identity\\/confirm");
|
||||
|
||||
/// <summary>
|
||||
/// Pre-authentication
|
||||
/// </summary>
|
||||
/// <remarks>This step is to get the login page for later use</remarks>
|
||||
/// <returns></returns>
|
||||
public PreAuthResponse PreAuth()
|
||||
{
|
||||
var request = new ProxiedWebRequest(authorize);
|
||||
request.UserAgent = userAgent;
|
||||
var response = request.Get();
|
||||
|
||||
string html = response.Body;
|
||||
|
||||
string PPFT = ppft.Match(html).Groups[1].Value;
|
||||
string urlPost = this.urlPost.Match(html).Groups[1].Value;
|
||||
|
||||
if (string.IsNullOrEmpty(PPFT) || string.IsNullOrEmpty(urlPost))
|
||||
{
|
||||
throw new Exception("Fail to extract PPFT or urlPost");
|
||||
}
|
||||
//Console.WriteLine("PPFT: {0}", PPFT);
|
||||
//Console.WriteLine();
|
||||
//Console.WriteLine("urlPost: {0}", urlPost);
|
||||
|
||||
return new PreAuthResponse()
|
||||
{
|
||||
UrlPost = urlPost,
|
||||
PPFT = PPFT,
|
||||
Cookie = response.Cookies
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform login request
|
||||
/// </summary>
|
||||
/// <remarks>This step is to send the login request by using the PreAuth response</remarks>
|
||||
/// <param name="email">Microsoft account email</param>
|
||||
/// <param name="password">Account password</param>
|
||||
/// <param name="preAuth"></param>
|
||||
/// <returns></returns>
|
||||
public UserLoginResponse UserLogin(string email, string password, PreAuthResponse preAuth)
|
||||
{
|
||||
var request = new ProxiedWebRequest(preAuth.UrlPost, preAuth.Cookie);
|
||||
request.UserAgent = userAgent;
|
||||
|
||||
string postData = "login=" + Uri.EscapeDataString(email)
|
||||
+ "&loginfmt=" + Uri.EscapeDataString(email)
|
||||
+ "&passwd=" + Uri.EscapeDataString(password)
|
||||
+ "&PPFT=" + Uri.EscapeDataString(preAuth.PPFT);
|
||||
|
||||
var response = request.Post("application/x-www-form-urlencoded", postData);
|
||||
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine(response.ToString());
|
||||
}
|
||||
|
||||
if (response.StatusCode >= 300 && response.StatusCode <= 399)
|
||||
{
|
||||
string url = response.Headers.Get("Location");
|
||||
string hash = url.Split('#')[1];
|
||||
|
||||
var request2 = new ProxiedWebRequest(url);
|
||||
var response2 = request2.Get();
|
||||
|
||||
if (response2.StatusCode != 200)
|
||||
{
|
||||
throw new Exception("Authentication failed");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(hash))
|
||||
{
|
||||
if (confirm.IsMatch(response2.Body))
|
||||
{
|
||||
throw new Exception("Activity confirmation required");
|
||||
}
|
||||
else throw new Exception("Invalid credentials or 2FA enabled");
|
||||
}
|
||||
var dict = Request.ParseQueryString(hash);
|
||||
|
||||
//foreach (var pair in dict)
|
||||
//{
|
||||
// Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
|
||||
//}
|
||||
|
||||
return new UserLoginResponse()
|
||||
{
|
||||
AccessToken = dict["access_token"],
|
||||
RefreshToken = dict["refresh_token"],
|
||||
ExpiresIn = int.Parse(dict["expires_in"])
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unexpected response. Check your credentials. Response code: " + response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Xbox Live Authenticate
|
||||
/// </summary>
|
||||
/// <param name="loginResponse"></param>
|
||||
/// <returns></returns>
|
||||
public XblAuthenticateResponse XblAuthenticate(UserLoginResponse loginResponse)
|
||||
{
|
||||
var request = new ProxiedWebRequest(xbl);
|
||||
request.UserAgent = userAgent;
|
||||
request.Accept = "application/json";
|
||||
request.Headers.Add("x-xbl-contract-version", "0");
|
||||
|
||||
string payload = "{"
|
||||
+ "\"Properties\": {"
|
||||
+ "\"AuthMethod\": \"RPS\","
|
||||
+ "\"SiteName\": \"user.auth.xboxlive.com\","
|
||||
+ "\"RpsTicket\": \"" + loginResponse.AccessToken + "\""
|
||||
+ "},"
|
||||
+ "\"RelyingParty\": \"http://auth.xboxlive.com\","
|
||||
+ "\"TokenType\": \"JWT\""
|
||||
+ "}";
|
||||
var response = request.Post("application/json", payload);
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine(response.ToString());
|
||||
}
|
||||
if (response.StatusCode == 200)
|
||||
{
|
||||
string jsonString = response.Body;
|
||||
//Console.WriteLine(jsonString);
|
||||
|
||||
Json.JSONData json = Json.ParseJson(jsonString);
|
||||
string token = json.Properties["Token"].StringValue;
|
||||
string userHash = json.Properties["DisplayClaims"].Properties["xui"].DataArray[0].Properties["uhs"].StringValue;
|
||||
return new XblAuthenticateResponse()
|
||||
{
|
||||
Token = token,
|
||||
UserHash = userHash
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("XBL Authentication failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XSTS Authenticate
|
||||
/// </summary>
|
||||
/// <remarks>(Don't ask me what is XSTS, I DONT KNOW)</remarks>
|
||||
/// <param name="xblResponse"></param>
|
||||
/// <returns></returns>
|
||||
public XSTSAuthenticateResponse XSTSAuthenticate(XblAuthenticateResponse xblResponse)
|
||||
{
|
||||
var request = new ProxiedWebRequest(xsts);
|
||||
request.UserAgent = userAgent;
|
||||
request.Accept = "application/json";
|
||||
request.Headers.Add("x-xbl-contract-version", "1");
|
||||
|
||||
string payload = "{"
|
||||
+ "\"Properties\": {"
|
||||
+ "\"SandboxId\": \"RETAIL\","
|
||||
+ "\"UserTokens\": ["
|
||||
+ "\"" + xblResponse.Token + "\""
|
||||
+ "]"
|
||||
+ "},"
|
||||
+ "\"RelyingParty\": \"rp://api.minecraftservices.com/\","
|
||||
+ "\"TokenType\": \"JWT\""
|
||||
+ "}";
|
||||
var response = request.Post("application/json", payload);
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine(response.ToString());
|
||||
}
|
||||
if (response.StatusCode == 200)
|
||||
{
|
||||
string jsonString = response.Body;
|
||||
Json.JSONData json = Json.ParseJson(jsonString);
|
||||
string token = json.Properties["Token"].StringValue;
|
||||
string userHash = json.Properties["DisplayClaims"].Properties["xui"].DataArray[0].Properties["uhs"].StringValue;
|
||||
return new XSTSAuthenticateResponse()
|
||||
{
|
||||
Token = token,
|
||||
UserHash = userHash
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (response.StatusCode == 401)
|
||||
{
|
||||
Json.JSONData json = Json.ParseJson(response.Body);
|
||||
if (json.Properties["XErr"].StringValue == "2148916233")
|
||||
{
|
||||
throw new Exception("The account doesn't have an Xbox account");
|
||||
}
|
||||
else if (json.Properties["XErr"].StringValue == "2148916238")
|
||||
{
|
||||
throw new Exception("The account is a child (under 18) and cannot proceed unless the account is added to a Family by an adult");
|
||||
}
|
||||
else throw new Exception("Unknown XSTS error code: " + json.Properties["XErr"].StringValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("XSTS Authentication failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct PreAuthResponse
|
||||
{
|
||||
public string UrlPost;
|
||||
public string PPFT;
|
||||
public NameValueCollection Cookie;
|
||||
}
|
||||
|
||||
public struct UserLoginResponse
|
||||
{
|
||||
public string AccessToken;
|
||||
public string RefreshToken;
|
||||
public int ExpiresIn;
|
||||
}
|
||||
|
||||
public struct XblAuthenticateResponse
|
||||
{
|
||||
public string Token;
|
||||
public string UserHash;
|
||||
}
|
||||
|
||||
public struct XSTSAuthenticateResponse
|
||||
{
|
||||
public string Token;
|
||||
public string UserHash;
|
||||
}
|
||||
}
|
||||
|
||||
class MinecraftWithXbox
|
||||
{
|
||||
private readonly string loginWithXbox = "https://api.minecraftservices.com/authentication/login_with_xbox";
|
||||
private readonly string ownership = "https://api.minecraftservices.com/entitlements/mcstore";
|
||||
private readonly string profile = "https://api.minecraftservices.com/minecraft/profile";
|
||||
|
||||
/// <summary>
|
||||
/// Login to Minecraft using the XSTS token and user hash obtained before
|
||||
/// </summary>
|
||||
/// <param name="userHash"></param>
|
||||
/// <param name="xstsToken"></param>
|
||||
/// <returns></returns>
|
||||
public string LoginWithXbox(string userHash, string xstsToken)
|
||||
{
|
||||
var request = new ProxiedWebRequest(loginWithXbox);
|
||||
request.Accept = "application/json";
|
||||
|
||||
string payload = "{\"identityToken\": \"XBL3.0 x=" + userHash + ";" + xstsToken + "\"}";
|
||||
var response = request.Post("application/json", payload);
|
||||
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine(response.ToString());
|
||||
}
|
||||
|
||||
string jsonString = response.Body;
|
||||
Json.JSONData json = Json.ParseJson(jsonString);
|
||||
return json.Properties["access_token"].StringValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user own Minecraft by access token
|
||||
/// </summary>
|
||||
/// <param name="accessToken"></param>
|
||||
/// <returns>True if the user own the game</returns>
|
||||
public bool UserHasGame(string accessToken)
|
||||
{
|
||||
var request = new ProxiedWebRequest(ownership);
|
||||
request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));
|
||||
var response = request.Get();
|
||||
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine(response.ToString());
|
||||
}
|
||||
|
||||
string jsonString = response.Body;
|
||||
Json.JSONData json = Json.ParseJson(jsonString);
|
||||
return json.Properties["items"].DataArray.Count > 0;
|
||||
}
|
||||
|
||||
public UserProfile GetUserProfile(string accessToken)
|
||||
{
|
||||
var request = new ProxiedWebRequest(profile);
|
||||
request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));
|
||||
var response = request.Get();
|
||||
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLine(response.ToString());
|
||||
}
|
||||
|
||||
string jsonString = response.Body;
|
||||
Json.JSONData json = Json.ParseJson(jsonString);
|
||||
return new UserProfile()
|
||||
{
|
||||
UUID = json.Properties["id"].StringValue,
|
||||
UserName = json.Properties["name"].StringValue
|
||||
};
|
||||
}
|
||||
|
||||
public struct UserProfile
|
||||
{
|
||||
public string UUID;
|
||||
public string UserName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class
|
||||
/// </summary>
|
||||
static class Request
|
||||
{
|
||||
static public Dictionary<string, string> ParseQueryString(string query)
|
||||
{
|
||||
return query.Split('&')
|
||||
.ToDictionary(c => c.Split('=')[0],
|
||||
c => Uri.UnescapeDataString(c.Split('=')[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -328,6 +328,7 @@ namespace MinecraftClient.Protocol
|
|||
}
|
||||
|
||||
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, InvalidResponse, NullError };
|
||||
public enum AccountType { Mojang, Microsoft };
|
||||
|
||||
/// <summary>
|
||||
/// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
|
||||
|
|
@ -336,7 +337,20 @@ namespace MinecraftClient.Protocol
|
|||
/// <param name="pass">Password</param>
|
||||
/// <param name="session">In case of successful login, will contain session information for multiplayer</param>
|
||||
/// <returns>Returns the status of the login (Success, Failure, etc.)</returns>
|
||||
public static LoginResult GetLogin(string user, string pass, out SessionToken session)
|
||||
public static LoginResult GetLogin(string user, string pass, AccountType type, out SessionToken session)
|
||||
{
|
||||
if (type == AccountType.Microsoft)
|
||||
{
|
||||
return MicrosoftLogin(user, pass, out session);
|
||||
}
|
||||
else if (type == AccountType.Mojang)
|
||||
{
|
||||
return MojangLogin(user, pass, out session);
|
||||
}
|
||||
else throw new InvalidOperationException("Account type must be Mojang or Microsoft");
|
||||
}
|
||||
|
||||
private static LoginResult MojangLogin(string user, string pass, out SessionToken session)
|
||||
{
|
||||
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
|
||||
|
|
@ -415,6 +429,44 @@ namespace MinecraftClient.Protocol
|
|||
}
|
||||
}
|
||||
|
||||
private static LoginResult MicrosoftLogin(string email, string password, out SessionToken session)
|
||||
{
|
||||
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
|
||||
var ms = new XboxLive();
|
||||
var mc = new MinecraftWithXbox();
|
||||
|
||||
try
|
||||
{
|
||||
var msaResponse = ms.UserLogin(email, password, ms.PreAuth());
|
||||
var xblResponse = ms.XblAuthenticate(msaResponse);
|
||||
var xsts = ms.XSTSAuthenticate(xblResponse); // Might throw even password correct
|
||||
|
||||
string accessToken = mc.LoginWithXbox(xsts.UserHash, xsts.Token);
|
||||
bool hasGame = mc.UserHasGame(accessToken);
|
||||
if (hasGame)
|
||||
{
|
||||
var profile = mc.GetUserProfile(accessToken);
|
||||
session.PlayerName = profile.UserName;
|
||||
session.PlayerID = profile.UUID;
|
||||
session.ID = accessToken;
|
||||
return LoginResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return LoginResult.NotPremium;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§cMicrosoft authenticate failed: " + e.Message);
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§c" + e.StackTrace);
|
||||
}
|
||||
return LoginResult.WrongPassword; // Might not always be wrong password
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates whether accessToken must be refreshed
|
||||
/// </summary>
|
||||
|
|
|
|||
282
MinecraftClient/Protocol/ProxiedWebRequest.cs
Normal file
282
MinecraftClient/Protocol/ProxiedWebRequest.cs
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net.Sockets;
|
||||
using MinecraftClient.Proxy;
|
||||
using System.Net.Security;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new http request and optionally with proxy according to setting
|
||||
/// </summary>
|
||||
public class ProxiedWebRequest
|
||||
{
|
||||
private readonly string httpVersion = "HTTP/1.0"; // Use 1.0 here because 1.1 server may send chunked data
|
||||
|
||||
private Uri uri;
|
||||
private string host { get => uri.Host; }
|
||||
private int port { get => uri.Port; }
|
||||
private string path { get => uri.PathAndQuery; }
|
||||
private bool isSecure { get => uri.Scheme == "https"; }
|
||||
|
||||
public NameValueCollection Headers = new NameValueCollection();
|
||||
|
||||
public string UserAgent { get => Headers.Get("User-Agent"); set => Headers.Set("User-Agent", value); }
|
||||
public string Accept { get => Headers.Get("Accept"); set => Headers.Set("Accept", value); }
|
||||
public string Cookie { set => Headers.Set("Cookie", value); }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new http request
|
||||
/// </summary>
|
||||
/// <param name="url">Target URL</param>
|
||||
public ProxiedWebRequest(string url)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
SetupBasicHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new http request with cookies
|
||||
/// </summary>
|
||||
/// <param name="url">Target URL</param>
|
||||
/// <param name="cookies">Cookies to use</param>
|
||||
public ProxiedWebRequest(string url, NameValueCollection cookies)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
Headers.Add("Cookie", GetCookieString(cookies));
|
||||
SetupBasicHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup some basic headers
|
||||
/// </summary>
|
||||
private void SetupBasicHeaders()
|
||||
{
|
||||
Headers.Add("Host", host);
|
||||
Headers.Add("User-Agent", "MCC/" + Program.Version);
|
||||
Headers.Add("Accept", "*/*");
|
||||
Headers.Add("Connection", "close");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform GET request and get the response. Proxy is handled automatically
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Response Get()
|
||||
{
|
||||
return Send("GET");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform POST request and get the response. Proxy is handled automatically
|
||||
/// </summary>
|
||||
/// <param name="contentType">The content type of request body</param>
|
||||
/// <param name="body">Request body</param>
|
||||
/// <returns></returns>
|
||||
public Response Post(string contentType, string body)
|
||||
{
|
||||
Headers.Add("Content-Type", contentType);
|
||||
// Calculate length
|
||||
Headers.Add("Content-Length", Encoding.UTF8.GetBytes(body).Length.ToString());
|
||||
return Send("POST", body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a http request to the server. Proxy is handled automatically
|
||||
/// </summary>
|
||||
/// <param name="method">Method in string representation</param>
|
||||
/// <param name="body">Optional request body</param>
|
||||
/// <returns></returns>
|
||||
private Response Send(string method, string body = "")
|
||||
{
|
||||
List<string> requestMessage = new List<string>()
|
||||
{
|
||||
string.Format("{0} {1} {2}", method.ToUpper(), path, httpVersion) // Request line
|
||||
};
|
||||
foreach (string key in Headers) // Headers
|
||||
{
|
||||
var value = Headers[key];
|
||||
requestMessage.Add(string.Format("{0}: {1}", key, value));
|
||||
}
|
||||
requestMessage.Add(""); // <CR><LF>
|
||||
if (body != "")
|
||||
{
|
||||
requestMessage.Add(body);
|
||||
}
|
||||
else requestMessage.Add(""); // <CR><LF>
|
||||
if (Settings.DebugMessages)
|
||||
{
|
||||
foreach (string l in requestMessage)
|
||||
{
|
||||
ConsoleIO.WriteLine("< " + l);
|
||||
}
|
||||
}
|
||||
Response response = Response.Empty();
|
||||
AutoTimeout.Perform(() =>
|
||||
{
|
||||
TcpClient client = ProxyHandler.newTcpClient(host, port, true);
|
||||
Stream stream;
|
||||
if (isSecure)
|
||||
{
|
||||
stream = new SslStream(client.GetStream());
|
||||
((SslStream)stream).AuthenticateAsClient(host);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = client.GetStream();
|
||||
}
|
||||
string h = string.Join("\r\n", requestMessage.ToArray());
|
||||
byte[] data = Encoding.ASCII.GetBytes(h);
|
||||
stream.Write(data, 0, data.Length);
|
||||
stream.Flush();
|
||||
StreamReader sr = new StreamReader(stream);
|
||||
string rawResult = sr.ReadToEnd();
|
||||
response = ParseResponse(rawResult);
|
||||
try
|
||||
{
|
||||
sr.Close();
|
||||
stream.Close();
|
||||
client.Close();
|
||||
} catch { }
|
||||
},
|
||||
TimeSpan.FromSeconds(30));
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a raw response string to response object
|
||||
/// </summary>
|
||||
/// <param name="raw">raw response string</param>
|
||||
/// <returns></returns>
|
||||
private Response ParseResponse(string raw)
|
||||
{
|
||||
int statusCode;
|
||||
string responseBody = "";
|
||||
NameValueCollection headers = new NameValueCollection();
|
||||
NameValueCollection cookies = new NameValueCollection();
|
||||
if (raw.StartsWith("HTTP/1.1") || raw.StartsWith("HTTP/1.0"))
|
||||
{
|
||||
Queue<string> msg = new Queue<string>(raw.Split(new string[] { "\r\n" }, StringSplitOptions.None));
|
||||
statusCode = int.Parse(msg.Dequeue().Split(' ')[1]);
|
||||
|
||||
while (msg.Peek() != "")
|
||||
{
|
||||
string[] header = msg.Dequeue().Split(new char[] { ':' }, 2); // Split first ':' only
|
||||
string key = header[0].ToLower(); // Key is case-insensitive
|
||||
string value = header[1];
|
||||
if (key == "set-cookie")
|
||||
{
|
||||
string[] cookie = value.Split(';'); // cookie options are ignored
|
||||
string[] tmp = cookie[0].Split(new char[] { '=' }, 2); // Split first '=' only
|
||||
string cname = tmp[0].Trim();
|
||||
string cvalue = tmp[1].Trim();
|
||||
cookies.Add(cname, cvalue);
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.Add(key, value.Trim());
|
||||
}
|
||||
}
|
||||
msg.Dequeue();
|
||||
if (msg.Count > 0)
|
||||
responseBody = msg.Dequeue();
|
||||
|
||||
return new Response()
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Body = responseBody,
|
||||
Headers = headers,
|
||||
Cookies = cookies
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Response()
|
||||
{
|
||||
StatusCode = 520, // 502 - Web Server Returned an Unknown Error
|
||||
Body = "",
|
||||
Headers = headers,
|
||||
Cookies = cookies
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the cookie string representation to use in header
|
||||
/// </summary>
|
||||
/// <param name="cookies"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetCookieString(NameValueCollection cookies)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (string key in cookies)
|
||||
{
|
||||
var value = cookies[key];
|
||||
sb.Append(string.Format("{0}={1}; ", key, value));
|
||||
}
|
||||
string result = sb.ToString();
|
||||
return result.Remove(result.Length - 2); // Remove "; " at the end
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic response object
|
||||
/// </summary>
|
||||
public class Response
|
||||
{
|
||||
public int StatusCode;
|
||||
public string Body;
|
||||
public NameValueCollection Headers;
|
||||
public NameValueCollection Cookies;
|
||||
|
||||
/// <summary>
|
||||
/// Get an empty response object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Response Empty()
|
||||
{
|
||||
return new Response()
|
||||
{
|
||||
StatusCode = 204, // 204 - No content
|
||||
Body = "",
|
||||
Headers = new NameValueCollection(),
|
||||
Cookies = new NameValueCollection()
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Status code: " + StatusCode);
|
||||
sb.AppendLine("Headers:");
|
||||
foreach (string key in Headers)
|
||||
{
|
||||
sb.AppendLine(string.Format(" {0}: {1}", key, Headers[key]));
|
||||
}
|
||||
if (Cookies.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Cookies: ");
|
||||
foreach (string key in Cookies)
|
||||
{
|
||||
sb.AppendLine(string.Format(" {0}={1}", key, Cookies[key]));
|
||||
}
|
||||
}
|
||||
if (Body != "")
|
||||
{
|
||||
sb.AppendLine();
|
||||
if (Body.Length > 200)
|
||||
{
|
||||
sb.AppendLine("Body: (Truncated to 200 characters)");
|
||||
}
|
||||
else sb.AppendLine("Body: ");
|
||||
sb.AppendLine(Body.Length > 200 ? Body.Substring(0, 200) + "..." : Body);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
login=
|
||||
password=
|
||||
serverip=
|
||||
type=mojang # Account type. mojang or microsoft
|
||||
|
||||
# Advanced settings
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ mcc.password_hidden=Password : {0}
|
|||
mcc.offline=§8You chose to run in offline mode.
|
||||
mcc.session_invalid=§8Cached session is invalid or expired.
|
||||
mcc.session_valid=§8Cached session is still valid for {0}.
|
||||
mcc.connecting=Connecting to Minecraft.net...
|
||||
mcc.connecting=Connecting to {0}...
|
||||
mcc.ip=Server IP :
|
||||
mcc.use_version=§8Using Minecraft version {0} (protocol v{1})
|
||||
mcc.unknown_version=§8Unknown or not supported MC version {0}.\nSwitching to autodetection mode.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ namespace MinecraftClient
|
|||
public static string Login = "";
|
||||
public static string Username = "";
|
||||
public static string Password = "";
|
||||
public static ProtocolHandler.AccountType AccountType = ProtocolHandler.AccountType.Mojang;
|
||||
public static string ServerIP = "";
|
||||
public static ushort ServerPort = 25565;
|
||||
public static string ServerVersion = "";
|
||||
|
|
@ -262,6 +263,9 @@ namespace MinecraftClient
|
|||
{
|
||||
case "login": Login = argValue; break;
|
||||
case "password": Password = argValue; break;
|
||||
case "type": AccountType = argValue == "mojang"
|
||||
? ProtocolHandler.AccountType.Mojang
|
||||
: ProtocolHandler.AccountType.Microsoft; break;
|
||||
case "serverip": if (!SetServerIP(argValue)) serverAlias = argValue; ; break;
|
||||
case "singlecommand": SingleCommand = argValue; break;
|
||||
case "language": Language = argValue; break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue