using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Protocol.Handlers;
using MinecraftClient.Proxy;
using System.Net.Sockets;
using System.Net.Security;
namespace MinecraftClient.Protocol
{
///
/// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server.
///
public static class ProtocolHandler
{
///
/// Retrieve information about a Minecraft server
///
/// Server IP to ping
/// Server Port to ping
/// Will contain protocol version, if ping successful
/// TRUE if ping was successful
public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion)
{
bool success = false;
int protocolversionTmp = 0;
if (AutoTimeout.Perform(() =>
{
try
{
if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp)
|| Protocol17Handler.doPing(serverIP, serverPort, ref protocolversionTmp))
{
success = true;
}
else ConsoleIO.WriteLineFormatted("§8Unexpected response from the server (is that a Minecraft server?)");
}
catch (Exception e)
{
ConsoleIO.WriteLineFormatted("§8" + e.Message);
}
}, TimeSpan.FromSeconds(30)))
{
protocolversion = protocolversionTmp;
return success;
}
else
{
ConsoleIO.WriteLineFormatted("§8A timeout occured while attempting to connect to this IP.");
return false;
}
}
///
/// Get a protocol handler for the specified Minecraft version
///
/// Tcp Client connected to the server
/// Protocol version to handle
/// Handler with the appropriate callbacks
///
public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
int[] supportedVersions_Protocol16 = { 51, 60, 61, 72, 73, 74, 78 };
if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1)
return new Protocol16Handler(Client, ProtocolVersion, Handler);
int[] supportedVersions_Protocol17 = { 4, 5 };
if (Array.IndexOf(supportedVersions_Protocol17, ProtocolVersion) > -1)
return new Protocol17Handler(Client, ProtocolVersion, Handler);
int[] supportedVersions_Protocol18 = { 47 };
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
return new Protocol18Handler(Client, ProtocolVersion, Handler);
throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported.");
}
///
/// Convert a human-readable Minecraft version number to network protocol version number
///
/// The Minecraft version number
/// The protocol version number or 0 if could not determine protocol version: error, unknown, not supported
public static int MCVer2ProtocolVersion(string MCVersion)
{
if (MCVersion.Contains('.'))
{
switch (MCVersion.Split(' ')[0].Trim())
{
case "1.4.6":
case "1.4.7":
return 51;
case "1.5.1":
return 60;
case "1.5.2":
return 61;
case "1.6.0":
return 72;
case "1.6.1":
case "1.6.2":
case "1.6.3":
case "1.6.4":
return 73;
case "1.7.2":
case "1.7.3":
case "1.7.4":
case "1.7.5":
return 4;
case "1.7.6":
case "1.7.7":
case "1.7.8":
case "1.7.9":
case "1.7.10":
return 5;
case "1.8.0":
case "1.8.1":
case "1.8.2":
case "1.8.3":
case "1.8.4":
case "1.8.5":
case "1.8.6":
case "1.8.7":
return 47;
default:
return 0;
}
}
else
{
try
{
return Int32.Parse(MCVersion);
}
catch
{
return 0;
}
}
}
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium };
///
/// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
///
/// Login
/// Password
/// Will contain the access token returned by Minecraft.net, if the login is successful
/// Will contain the player's UUID, needed for multiplayer
/// Returns the status of the login (Success, Failure, etc.)
public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string uuid)
{
try
{
string result = "";
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\" }";
int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result);
if (code == 200)
{
if (result.Contains("availableProfiles\":[]}"))
{
return LoginResult.NotPremium;
}
else
{
string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { user = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; }
return LoginResult.Success;
}
}
else if (code == 403)
{
if (result.Contains("UserMigratedException"))
{
return LoginResult.AccountMigrated;
}
else return LoginResult.WrongPassword;
}
else if (code == 503)
{
return LoginResult.ServiceUnavailable;
}
else
{
ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code);
return LoginResult.OtherError;
}
}
catch (System.Security.Authentication.AuthenticationException)
{
return LoginResult.SSLError;
}
catch (System.IO.IOException e)
{
if (e.Message.Contains("authentication"))
{
return LoginResult.SSLError;
}
else return LoginResult.OtherError;
}
catch
{
return LoginResult.OtherError;
}
}
///
/// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
///
/// Username
/// Session ID
/// Server ID
/// TRUE if session was successfully checked
public static bool SessionCheck(string uuid, string accesstoken, string serverhash)
{
try
{
string result = "";
string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}";
int code = doHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request, ref result);
return (result == "");
}
catch { return false; }
}
///
/// Manual HTTPS request since we must directly use a TcpClient because of the proxy.
/// This method connects to the server, enables SSL, do the request and read the response.
///
/// Host to connect to
/// Endpoint for making the request
/// Request payload
/// Request result
/// HTTP Status code
private static int doHTTPSPost(string host, string endpoint, string request, ref string result)
{
string postResult = null;
int statusCode = 520;
AutoTimeout.Perform(() =>
{
TcpClient client = ProxyHandler.newTcpClient(host, 443);
SslStream stream = new SslStream(client.GetStream());
stream.AuthenticateAsClient(host);
List http_request = new List();
http_request.Add("POST " + endpoint + " HTTP/1.1");
http_request.Add("Host: " + host);
http_request.Add("User-Agent: MCC/" + Program.Version);
http_request.Add("Content-Type: application/json");
http_request.Add("Content-Length: " + Encoding.ASCII.GetBytes(request).Length);
http_request.Add("Connection: close");
http_request.Add("");
http_request.Add(request);
stream.Write(Encoding.ASCII.GetBytes(String.Join("\r\n", http_request.ToArray())));
System.IO.StreamReader sr = new System.IO.StreamReader(stream);
string raw_result = sr.ReadToEnd();
if (raw_result.StartsWith("HTTP/1.1"))
{
postResult = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
statusCode = Settings.str2int(raw_result.Split(' ')[1]);
}
else statusCode = 520; //Web server is returning an unknown error
}, TimeSpan.FromSeconds(30));
result = postResult;
return statusCode;
}
///
/// Encode a string to a json string.
/// Will convert special chars to \u0000 unicode escape sequences.
///
/// Source text
/// Encoded text
private static string jsonEncode(string text)
{
StringBuilder result = new StringBuilder();
foreach (char c in text)
{
if (char.IsLetterOrDigit(c))
{
result.Append(c);
}
else
{
result.Append("\\u");
result.Append(((int)c).ToString("x4"));
}
}
return result.ToString();
}
}
}