App refactoring almost done

- Created specific namespaces and folders for each app brick
- Added proxy support using Starksoft's Biko Library
- App bricks: Main, ChatBots, Crypto, Protocol, Proxy
- Each class is now in its own file (Aes streams, chatbots)
- Used "Bridge" design pattern for Crypto, Protocol, Proxy
- Added back support for Minecraft 1.4.6 to 1.6.4 (MCC 1.6.2)
- Need to fully re-test everything and fix bugs
- To Fix : Server pinging is slow on SpigotMC
- To Do : Add Minecraft 1.2.5 (MCC 1.3) and maybe 1.3 to 1.4.5
This commit is contained in:
ORelio 2014-05-31 01:59:03 +02:00
parent 9be1d99ca0
commit d2ec2f48b7
43 changed files with 6039 additions and 2178 deletions

View file

@ -0,0 +1,387 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// This class parses JSON chat data from MC 1.6+ and returns the appropriate string to be printed.
/// </summary>
static class ChatParser
{
/// <summary>
/// The main function to convert text from MC 1.6+ JSON to MC 1.5.2 formatted text
/// </summary>
/// <param name="json">JSON serialized text</param>
/// <returns>Returns the translated text</returns>
public static string ParseText(string json)
{
int cursorpos = 0;
JSONData jsonData = String2Data(json, ref cursorpos);
return JSONData2String(jsonData, "");
}
/// <summary>
/// An internal class to store unserialized JSON data
/// The data can be an object, an array or a string
/// </summary>
private class JSONData
{
public enum DataType { Object, Array, String };
private DataType type;
public DataType Type { get { return type; } }
public Dictionary<string, JSONData> Properties;
public List<JSONData> DataArray;
public string StringValue;
public JSONData(DataType datatype)
{
type = datatype;
Properties = new Dictionary<string, JSONData>();
DataArray = new List<JSONData>();
StringValue = String.Empty;
}
}
/// <summary>
/// Get the classic color tag corresponding to a color name
/// </summary>
/// <param name="colorname">Color Name</param>
/// <returns>Color code</returns>
private static string color2tag(string colorname)
{
switch (colorname.ToLower())
{
case "black": return "§0";
case "dark_blue": return "§1";
case "dark_green": return "§2";
case "dark_aqua": return "§3";
case "dark_red": return "§4";
case "dark_purple": return "§5";
case "gold": return "§6";
case "gray": return "§7";
case "dark_gray": return "§8";
case "blue": return "§9";
case "green": return "§a";
case "aqua": return "§b";
case "red": return "§c";
case "light_purple": return "§d";
case "yellow": return "§e";
case "white": return "§f";
default: return "";
}
}
/// <summary>
/// Rules for text translation
/// </summary>
private static bool init = false;
private static Dictionary<string, string> TranslationRules = new Dictionary<string, string>();
public static void InitTranslations() { if (!init) { InitRules(); init = true; } }
private static void InitRules()
{
//Small default dictionnary of translation rules
TranslationRules["chat.type.admin"] = "[%s: %s]";
TranslationRules["chat.type.announcement"] = "§d[%s] %s";
TranslationRules["chat.type.emote"] = " * %s %s";
TranslationRules["chat.type.text"] = "<%s> %s";
TranslationRules["multiplayer.player.joined"] = "§e%s joined the game.";
TranslationRules["multiplayer.player.left"] = "§e%s left the game.";
TranslationRules["commands.message.display.incoming"] = "§7%s whispers to you: %s";
TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s";
//Language file in a subfolder, depending on the language setting
if (!System.IO.Directory.Exists("lang"))
System.IO.Directory.CreateDirectory("lang");
string Language_File = "lang\\" + Settings.Language + ".lang";
//File not found? Try downloading language file from Mojang's servers?
if (!System.IO.File.Exists(Language_File))
{
Console.ForegroundColor = ConsoleColor.DarkGray;
ConsoleIO.WriteLine("Downloading '" + Settings.Language + ".lang' from Mojang servers...");
try
{
string assets_index = downloadString(Settings.TranslationsFile_Website_Index);
string[] tmp = assets_index.Split(new string[] { "lang/" + Settings.Language + ".lang" }, StringSplitOptions.None);
tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None);
string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers
System.IO.File.WriteAllText(Language_File, downloadString(Settings.TranslationsFile_Website_Download + '/' + hash.Substring(0, 2) + '/' + hash));
ConsoleIO.WriteLine("Done. File saved as '" + Language_File + '\'');
}
catch
{
ConsoleIO.WriteLine("Failed to download the file.");
}
Console.ForegroundColor = ConsoleColor.Gray;
}
//Download Failed? Defaulting to en_GB.lang if the game is installed
if (!System.IO.File.Exists(Language_File) //Try en_GB.lang
&& System.IO.File.Exists(Settings.TranslationsFile_FromMCDir))
{
Language_File = Settings.TranslationsFile_FromMCDir;
ConsoleIO.WriteLineFormatted("§8Defaulting to en_GB.lang from your Minecraft directory.", false);
}
//Load the external dictionnary of translation rules or display an error message
if (System.IO.File.Exists(Language_File))
{
string[] translations = System.IO.File.ReadAllLines(Language_File);
foreach (string line in translations)
{
if (line.Length > 0)
{
string[] splitted = line.Split('=');
if (splitted.Length == 2)
{
TranslationRules[splitted[0]] = splitted[1];
}
}
}
ConsoleIO.WriteLineFormatted("§8Translations file loaded.", false);
}
else //No external dictionnary found.
{
ConsoleIO.WriteLineFormatted("§8Translations file not found: \"" + Language_File + "\""
+ "\nSome messages won't be properly printed without this file.", true);
}
}
/// <summary>
/// Format text using a specific formatting rule.
/// Example : * %s %s + ["ORelio", "is doing something"] = * ORelio is doing something
/// </summary>
/// <param name="rulename">Name of the rule, chosen by the server</param>
/// <param name="using_data">Data to be used in the rule</param>
/// <returns>Returns the formatted text according to the given data</returns>
private static string TranslateString(string rulename, List<string> using_data)
{
if (!init) { InitRules(); init = true; }
if (TranslationRules.ContainsKey(rulename))
{
if ((TranslationRules[rulename].IndexOf("%1$s") >= 0 && TranslationRules[rulename].IndexOf("%2$s") >= 0)
&& (TranslationRules[rulename].IndexOf("%1$s") > TranslationRules[rulename].IndexOf("%2$s")))
{
while (using_data.Count < 2) { using_data.Add(""); }
string tmp = using_data[0];
using_data[0] = using_data[1];
using_data[1] = tmp;
}
string[] syntax = TranslationRules[rulename].Split(new string[] { "%s", "%d", "%1$s", "%2$s" }, StringSplitOptions.None);
while (using_data.Count < syntax.Length - 1) { using_data.Add(""); }
string[] using_array = using_data.ToArray();
string translated = "";
for (int i = 0; i < syntax.Length - 1; i++)
{
translated += syntax[i];
translated += using_array[i];
}
translated += syntax[syntax.Length - 1];
return translated;
}
else return "[" + rulename + "] " + String.Join(" ", using_data);
}
/// <summary>
/// Parse a JSON string to build a JSON object
/// </summary>
/// <param name="toparse">String to parse</param>
/// <param name="cursorpos">Cursor start (set to 0 for function init)</param>
/// <returns></returns>
private static JSONData String2Data(string toparse, ref int cursorpos)
{
try
{
JSONData data;
switch (toparse[cursorpos])
{
//Object
case '{':
data = new JSONData(JSONData.DataType.Object);
cursorpos++;
while (toparse[cursorpos] != '}')
{
if (toparse[cursorpos] == '"')
{
JSONData propertyname = String2Data(toparse, ref cursorpos);
if (toparse[cursorpos] == ':') { cursorpos++; } else { /* parse error ? */ }
JSONData propertyData = String2Data(toparse, ref cursorpos);
data.Properties[propertyname.StringValue] = propertyData;
}
else cursorpos++;
}
cursorpos++;
break;
//Array
case '[':
data = new JSONData(JSONData.DataType.Array);
cursorpos++;
while (toparse[cursorpos] != ']')
{
if (toparse[cursorpos] == ',') { cursorpos++; }
JSONData arrayItem = String2Data(toparse, ref cursorpos);
data.DataArray.Add(arrayItem);
}
cursorpos++;
break;
//String
case '"':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
while (toparse[cursorpos] != '"')
{
if (toparse[cursorpos] == '\\')
{
try //Unicode character \u0123
{
if (toparse[cursorpos + 1] == 'u'
&& isHex(toparse[cursorpos + 2])
&& isHex(toparse[cursorpos + 3])
&& isHex(toparse[cursorpos + 4])
&& isHex(toparse[cursorpos + 5]))
{
//"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string
data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber));
cursorpos += 6; continue;
}
else cursorpos++; //Normal character escapement \"
}
catch (IndexOutOfRangeException) { cursorpos++; } // \u01<end of string>
catch (ArgumentOutOfRangeException) { cursorpos++; } // Unicode index 0123 was invalid
}
data.StringValue += toparse[cursorpos];
cursorpos++;
}
cursorpos++;
break;
//Boolean : true
case 't':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
if (toparse[cursorpos] == 'r') { cursorpos++; }
if (toparse[cursorpos] == 'u') { cursorpos++; }
if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "true"; }
break;
//Boolean : false
case 'f':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
if (toparse[cursorpos] == 'a') { cursorpos++; }
if (toparse[cursorpos] == 'l') { cursorpos++; }
if (toparse[cursorpos] == 's') { cursorpos++; }
if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "false"; }
break;
//Unknown data
default:
cursorpos++;
return String2Data(toparse, ref cursorpos);
}
return data;
}
catch (IndexOutOfRangeException)
{
return new JSONData(JSONData.DataType.String);
}
}
/// <summary>
/// Use a JSON Object to build the corresponding string
/// </summary>
/// <param name="data">JSON object to convert</param>
/// <param name="colorcode">Allow parent color code to affect child elements (set to "" for function init)</param>
/// <returns>returns the Minecraft-formatted string</returns>
private static string JSONData2String(JSONData data, string colorcode)
{
string extra_result = "";
switch (data.Type)
{
case JSONData.DataType.Object:
if (data.Properties.ContainsKey("color"))
{
colorcode = color2tag(JSONData2String(data.Properties["color"], ""));
}
if (data.Properties.ContainsKey("extra"))
{
JSONData[] extras = data.Properties["extra"].DataArray.ToArray();
foreach (JSONData item in extras)
extra_result = extra_result + JSONData2String(item, colorcode) + "§r";
}
if (data.Properties.ContainsKey("text"))
{
return colorcode + JSONData2String(data.Properties["text"], colorcode) + extra_result;
}
else if (data.Properties.ContainsKey("translate"))
{
List<string> using_data = new List<string>();
if (data.Properties.ContainsKey("using") && !data.Properties.ContainsKey("with"))
data.Properties["with"] = data.Properties["using"];
if (data.Properties.ContainsKey("with"))
{
JSONData[] array = data.Properties["with"].DataArray.ToArray();
for (int i = 0; i < array.Length; i++)
{
using_data.Add(JSONData2String(array[i], colorcode));
}
}
return colorcode + TranslateString(JSONData2String(data.Properties["translate"], ""), using_data) + extra_result;
}
else return extra_result;
case JSONData.DataType.Array:
string result = "";
foreach (JSONData item in data.DataArray)
{
result += JSONData2String(item, colorcode);
}
return result;
case JSONData.DataType.String:
return colorcode + data.StringValue;
}
return "";
}
/// <summary>
/// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f)
/// </summary>
/// <param name="c">Char to test</param>
/// <returns>True if hexadecimal</returns>
private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); }
/// <summary>
/// Do a HTTP request to get a webpage or text data from a server file
/// </summary>
/// <param name="url">URL of resource</param>
/// <returns>Returns resource data if success, otherwise a WebException is raised</returns>
private static string downloadString(string url)
{
System.Net.HttpWebRequest myRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
myRequest.Method = "GET";
System.Net.WebResponse myResponse = myRequest.GetResponse();
System.IO.StreamReader sr = new System.IO.StreamReader(myResponse.GetResponseStream(), System.Text.Encoding.UTF8);
string result = sr.ReadToEnd();
sr.Close();
myResponse.Close();
return result;
}
}
}

View file

@ -0,0 +1,684 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.4.X, 1.5.X, 1.6.X Protocols
/// </summary>
class Protocol16Handler : IMinecraftCom
{
IMinecraftComHandler handler;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool encrypted = false;
private int protocolversion;
private Thread netRead;
Crypto.IAesStream s;
TcpClient c;
public Protocol16Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
ConsoleIO.SetAutoCompleteEngine(this);
if (protocolversion >= 72)
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
}
private Protocol16Handler(TcpClient Client)
{
this.c = Client;
}
private void Updater()
{
try
{
do { Thread.Sleep(100); }
while (Update());
}
catch (System.IO.IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
private bool Update()
{
bool connection_ok = true;
while (c.Client.Available > 0 && connection_ok)
{
byte id = readNextByte();
connection_ok = processPacket(id);
}
return connection_ok;
}
private bool processPacket(byte id)
{
int nbr = 0;
switch (id)
{
case 0x00: byte[] keepalive = new byte[5] { 0, 0, 0, 0, 0 };
Receive(keepalive, 1, 4, SocketFlags.None);
Send(keepalive); break;
case 0x01: readData(4); readNextString(); readData(5); break;
case 0x02: readData(1); readNextString(); readNextString(); readData(4); break;
case 0x03:
string message = readNextString();
if (protocolversion >= 72) { message = ChatParser.ParseText(message); }
handler.OnTextReceived(message); break;
case 0x04: readData(16); break;
case 0x05: readData(6); readNextItemSlot(); break;
case 0x06: readData(12); break;
case 0x07: readData(9); break;
case 0x08: if (protocolversion >= 72) { readData(10); } else readData(8); break;
case 0x09: readData(8); readNextString(); break;
case 0x0A: readData(1); break;
case 0x0B: readData(33); break;
case 0x0C: readData(9); break;
case 0x0D: readData(41); break;
case 0x0E: readData(11); break;
case 0x0F: readData(10); readNextItemSlot(); readData(3); break;
case 0x10: readData(2); break;
case 0x11: readData(14); break;
case 0x12: readData(5); break;
case 0x13: if (protocolversion >= 72) { readData(9); } else readData(5); break;
case 0x14: readData(4); readNextString(); readData(16); readNextEntityMetaData(); break;
case 0x16: readData(8); break;
case 0x17: readData(19); readNextObjectData(); break;
case 0x18: readData(26); readNextEntityMetaData(); break;
case 0x19: readData(4); readNextString(); readData(16); break;
case 0x1A: readData(18); break;
case 0x1B: if (protocolversion >= 72) { readData(10); } break;
case 0x1C: readData(10); break;
case 0x1D: nbr = (int)readNextByte(); readData(nbr * 4); break;
case 0x1E: readData(4); break;
case 0x1F: readData(7); break;
case 0x20: readData(6); break;
case 0x21: readData(9); break;
case 0x22: readData(18); break;
case 0x23: readData(5); break;
case 0x26: readData(5); break;
case 0x27: if (protocolversion >= 72) { readData(9); } else readData(8); break;
case 0x28: readData(4); readNextEntityMetaData(); break;
case 0x29: readData(8); break;
case 0x2A: readData(5); break;
case 0x2B: readData(8); break;
case 0x2C: if (protocolversion >= 72) { readNextEntityProperties(protocolversion); } break;
case 0x33: readData(13); nbr = readNextInt(); readData(nbr); break;
case 0x34: readData(10); nbr = readNextInt(); readData(nbr); break;
case 0x35: readData(12); break;
case 0x36: readData(14); break;
case 0x37: readData(17); break;
case 0x38: readNextChunkBulkData(); break;
case 0x3C: readData(28); nbr = readNextInt(); readData(3 * nbr); readData(12); break;
case 0x3D: readData(18); break;
case 0x3E: readNextString(); readData(17); break;
case 0x3F: if (protocolversion > 51) { readNextString(); readData(32); } break;
case 0x46: readData(2); break;
case 0x47: readData(17); break;
case 0x64: readNextWindowData(protocolversion); break;
case 0x65: readData(1); break;
case 0x66: readData(7); readNextItemSlot(); break;
case 0x67: readData(3); readNextItemSlot(); break;
case 0x68: readData(1); for (nbr = readNextShort(); nbr > 0; nbr--) { readNextItemSlot(); } break;
case 0x69: readData(5); break;
case 0x6A: readData(4); break;
case 0x6B: readData(2); readNextItemSlot(); break;
case 0x6C: readData(2); break;
case 0x82: readData(10); readNextString(); readNextString(); readNextString(); readNextString(); break;
case 0x83: readData(4); nbr = readNextShort(); readData(nbr); break;
case 0x84: readData(11); nbr = readNextShort(); if (nbr > 0) { readData(nbr); } break;
case 0x85: if (protocolversion >= 74) { readData(13); } break;
case 0xC8:
if (readNextInt() == 2022) { handler.OnTextReceived("You are dead. Type /reco to respawn & reconnect."); }
if (protocolversion >= 72) { readData(4); } else readData(1);
break;
case 0xC9: readNextString(); readData(3); break;
case 0xCA: if (protocolversion >= 72) { readData(9); } else readData(3); break;
case 0xCB: autocomplete_result = readNextString(); autocomplete_received = true; break;
case 0xCC: readNextString(); readData(4); break;
case 0xCD: readData(1); break;
case 0xCE: if (protocolversion > 51) { readNextString(); readNextString(); readData(1); } break;
case 0xCF: if (protocolversion > 51) { readNextString(); readData(1); readNextString(); } readData(4); break;
case 0xD0: if (protocolversion > 51) { readData(1); readNextString(); } break;
case 0xD1: if (protocolversion > 51) { readNextTeamData(); } break;
case 0xFA: readNextString(); nbr = readNextShort(); readData(nbr); break;
case 0xFF: string reason = readNextString();
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, reason); break;
default: return false; //unknown packet!
}
return true; //packet has been successfully skipped
}
private void StartUpdating()
{
netRead = new Thread(new ThreadStart(Updater));
netRead.Name = "ProtocolPacketHandler";
netRead.Start();
}
public void Dispose()
{
try
{
if (netRead != null)
{
netRead.Abort();
c.Close();
}
}
catch { }
}
private void readData(int offset)
{
if (offset > 0)
{
try
{
byte[] cache = new byte[offset];
Receive(cache, 0, offset, SocketFlags.None);
}
catch (OutOfMemoryException) { }
}
}
private string readNextString()
{
ushort length = (ushort)readNextShort();
if (length > 0)
{
byte[] cache = new byte[length * 2];
Receive(cache, 0, length * 2, SocketFlags.None);
string result = Encoding.BigEndianUnicode.GetString(cache);
return result;
}
else return "";
}
private byte[] readNextByteArray()
{
short len = readNextShort();
byte[] data = new byte[len];
Receive(data, 0, len, SocketFlags.None);
return data;
}
private short readNextShort()
{
byte[] tmp = new byte[2];
Receive(tmp, 0, 2, SocketFlags.None);
Array.Reverse(tmp);
return BitConverter.ToInt16(tmp, 0);
}
private int readNextInt()
{
byte[] tmp = new byte[4];
Receive(tmp, 0, 4, SocketFlags.None);
Array.Reverse(tmp);
return BitConverter.ToInt32(tmp, 0);
}
private byte readNextByte()
{
byte[] result = new byte[1];
Receive(result, 0, 1, SocketFlags.None);
return result[0];
}
private void readNextItemSlot()
{
short itemid = readNextShort();
//If slot not empty (item ID != -1)
if (itemid != -1)
{
readData(1); //Item count
readData(2); //Item damage
short length = readNextShort();
//If length of optional NBT data > 0, read it
if (length > 0) { readData(length); }
}
}
private void readNextEntityMetaData()
{
do
{
byte[] id = new byte[1];
Receive(id, 0, 1, SocketFlags.None);
if (id[0] == 0x7F) { break; }
int index = id[0] & 0x1F;
int type = id[0] >> 5;
switch (type)
{
case 0: readData(1); break; //Byte
case 1: readData(2); break; //Short
case 2: readData(4); break; //Int
case 3: readData(4); break; //Float
case 4: readNextString(); break; //String
case 5: readNextItemSlot(); break; //Slot
case 6: readData(12); break; //Vector (3 Int)
}
} while (true);
}
private void readNextObjectData()
{
int id = readNextInt();
if (id != 0) { readData(6); }
}
private void readNextTeamData()
{
readNextString(); //Internal Name
byte mode = readNextByte();
if (mode == 0 || mode == 2)
{
readNextString(); //Display Name
readNextString(); //Prefix
readNextString(); //Suffix
readData(1); //Friendly Fire
}
if (mode == 0 || mode == 3 || mode == 4)
{
short count = readNextShort();
for (int i = 0; i < count; i++)
{
readNextString(); //Players
}
}
}
private void readNextEntityProperties(int protocolversion)
{
if (protocolversion >= 72)
{
if (protocolversion >= 74)
{
//Minecraft 1.6.2
readNextInt(); //Entity ID
int count = readNextInt();
for (int i = 0; i < count; i++)
{
readNextString(); //Property name
readData(8); //Property value (Double)
short othercount = readNextShort();
readData(25 * othercount);
}
}
else
{
//Minecraft 1.6.0 / 1.6.1
readNextInt(); //Entity ID
int count = readNextInt();
for (int i = 0; i < count; i++)
{
readNextString(); //Property name
readData(8); //Property value (Double)
}
}
}
}
private void readNextWindowData(int protocolversion)
{
readData(1);
byte windowtype = readNextByte();
readNextString();
readData(1);
if (protocolversion > 51)
{
readData(1);
if (protocolversion >= 72 && windowtype == 0xb)
{
readNextInt();
}
}
}
private void readNextChunkBulkData()
{
short chunkcount = readNextShort();
int datalen = readNextInt();
readData(1);
readData(datalen);
readData(12 * (chunkcount));
}
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
int read = 0;
while (read < offset)
{
if (encrypted)
{
read += s.Read(buffer, start + read, offset - read);
}
else read += c.Client.Receive(buffer, start + read, offset - read, f);
}
}
private void Send(byte[] buffer)
{
if (encrypted)
{
s.Write(buffer, 0, buffer.Length);
}
else c.Client.Send(buffer);
}
private bool Handshake(string uuid, string username, string sessionID, string host, int port)
{
//array
byte[] data = new byte[10 + (username.Length + host.Length) * 2];
//packet id
data[0] = (byte)2;
//Protocol Version
data[1] = (byte)protocolversion;
//short len
byte[] sh = BitConverter.GetBytes((short)username.Length);
Array.Reverse(sh);
sh.CopyTo(data, 2);
//username
byte[] bname = Encoding.BigEndianUnicode.GetBytes(username);
bname.CopyTo(data, 4);
//short len
sh = BitConverter.GetBytes((short)host.Length);
Array.Reverse(sh);
sh.CopyTo(data, 4 + (username.Length * 2));
//host
byte[] bhost = Encoding.BigEndianUnicode.GetBytes(host);
bhost.CopyTo(data, 6 + (username.Length * 2));
//port
sh = BitConverter.GetBytes(port);
Array.Reverse(sh);
sh.CopyTo(data, 6 + (username.Length * 2) + (host.Length * 2));
Send(data);
byte[] pid = new byte[1];
Receive(pid, 0, 1, SocketFlags.None);
while (pid[0] == 0xFA) //Skip some early plugin messages
{
processPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
}
if (pid[0] == 0xFD)
{
string serverID = readNextString();
byte[] PublicServerkey = readNextByteArray();
byte[] token = readNextByteArray();
if (serverID == "-")
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.", false);
return true; //No need to check session or start encryption
}
else
{
ConsoleIO.WriteLineFormatted("§8Handshake successful. (Server ID: " + serverID + ')', false);
return StartEncryption(uuid, username, sessionID, token, serverID, PublicServerkey);
}
}
else return false;
}
private bool StartEncryption(string uuid, string username, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.", false);
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey)))
{
return false;
}
}
//Encrypt the data
byte[] key_enc = RSAService.Encrypt(secretKey, false);
byte[] token_enc = RSAService.Encrypt(token, false);
byte[] keylen = BitConverter.GetBytes((short)key_enc.Length);
byte[] tokenlen = BitConverter.GetBytes((short)token_enc.Length);
Array.Reverse(keylen);
Array.Reverse(tokenlen);
//Building the packet
byte[] data = new byte[5 + (short)key_enc.Length + (short)token_enc.Length];
data[0] = 0xFC;
keylen.CopyTo(data, 1);
key_enc.CopyTo(data, 3);
tokenlen.CopyTo(data, 3 + (short)key_enc.Length);
token_enc.CopyTo(data, 5 + (short)key_enc.Length);
//Send it back
Send(data);
//Getting the next packet
byte[] pid = new byte[1];
Receive(pid, 0, 1, SocketFlags.None);
if (pid[0] == 0xFC)
{
readData(4);
s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
encrypted = true;
return true;
}
else return false;
}
public bool Login()
{
if (Handshake(handler.getUserUUID(), handler.getUsername(), handler.getSessionID(), handler.getServerHost(), handler.getServerPort()))
{
Send(new byte[] { 0xCD, 0 });
try
{
byte[] pid = new byte[1];
try
{
if (c.Connected)
{
Receive(pid, 0, 1, SocketFlags.None);
while (pid[0] >= 0xC0 && pid[0] != 0xFF) //Skip some early packets or plugin messages
{
processPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
}
if (pid[0] == (byte)1)
{
readData(4); readNextString(); readData(5);
StartUpdating();
return true; //The Server accepted the request
}
else if (pid[0] == (byte)0xFF)
{
string reason = readNextString();
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, reason);
return false;
}
}
}
catch
{
//Connection failed
return false;
}
}
catch
{
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
return false;
}
return false; //Login was unsuccessful (received a kick...)
}
else return false;
}
public void Disconnect()
{
const string message = "disconnect.quitting";
try
{
byte[] reason = new byte[3 + (message.Length * 2)];
reason[0] = (byte)0xff;
byte[] msglen;
msglen = BitConverter.GetBytes((short)message.Length);
Array.Reverse(msglen);
msglen.CopyTo(reason, 1);
if (message.Length > 0)
{
byte[] msg;
msg = Encoding.BigEndianUnicode.GetBytes(message);
msg.CopyTo(reason, 3);
}
Send(reason);
}
catch (SocketException) { }
catch (System.IO.IOException) { }
}
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] chat = new byte[3 + (message.Length * 2)];
chat[0] = (byte)3;
byte[] msglen;
msglen = BitConverter.GetBytes((short)message.Length);
Array.Reverse(msglen);
msglen.CopyTo(chat, 1);
byte[] msg;
msg = Encoding.BigEndianUnicode.GetBytes(message);
msg.CopyTo(chat, 3);
Send(chat);
return true;
}
catch (SocketException) { return false; }
}
public bool SendRespawnPacket()
{
try
{
Send(new byte[] { 0xCD, 1 });
return true;
}
catch (SocketException) { return false; }
}
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return "";
byte[] autocomplete = new byte[3 + (BehindCursor.Length * 2)];
autocomplete[0] = 0xCB;
byte[] msglen = BitConverter.GetBytes((short)BehindCursor.Length);
Array.Reverse(msglen); msglen.CopyTo(autocomplete, 1);
byte[] msg = Encoding.BigEndianUnicode.GetBytes(BehindCursor);
msg.CopyTo(autocomplete, 3);
autocomplete_received = false;
autocomplete_result = BehindCursor;
Send(autocomplete);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
string[] results = autocomplete_result.Split((char)0x00);
return results[0];
}
private static byte[] concatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
public byte[] getPaddingPacket()
{
//Will generate a 15-bytes long padding packet
byte[] id = new byte[1] { 0xFA }; //Plugin Message
byte[] channel_name = Encoding.BigEndianUnicode.GetBytes("MCC|");
byte[] channel_name_len = BitConverter.GetBytes((short)channel_name.Length); Array.Reverse(channel_name_len);
byte[] data = new byte[] { 0x00, 0x00 };
byte[] data_len = BitConverter.GetBytes((short)data.Length); Array.Reverse(data_len);
byte[] packet_data = concatBytes(id, channel_name_len, channel_name, data_len, data);
return packet_data;
}
public static bool doPing(string host, int port, ref int protocolversion, ref string version)
{
try
{
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
byte[] ping = new byte[2] { 0xfe, 0x01 };
tcp.Client.Send(ping, SocketFlags.None);
tcp.Client.Receive(ping, 0, 1, SocketFlags.None);
if (ping[0] == 0xff)
{
Protocol16Handler ComTmp = new Protocol16Handler(tcp);
string result = ComTmp.readNextString();
if (result.Length > 2 && result[0] == '§' && result[1] == '1')
{
string[] tmp = result.Split((char)0x00);
protocolversion = (byte)Int16.Parse(tmp[1]);
version = tmp[2];
if (protocolversion == 127) //MC 1.7.2+
return false;
}
else
{
protocolversion = (byte)39;
version = "B1.8.1 - 1.3.2";
}
ConsoleIO.WriteLineFormatted("§8Server version : MC " + version + " (protocol v" + protocolversion + ").", false);
return true;
}
else return false;
}
catch { return false; }
}
}
}

View file

@ -0,0 +1,550 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.7.X Protocol
/// </summary>
class Protocol17Handler : IMinecraftCom
{
IMinecraftComHandler handler;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool encrypted = false;
private int protocolversion;
private Thread netRead;
Crypto.IAesStream s;
TcpClient c;
public Protocol17Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
}
private Protocol17Handler(TcpClient Client)
{
this.c = Client;
}
/// <summary>
/// Separate thread. Network reading loop.
/// </summary>
private void Updater()
{
try
{
do { Thread.Sleep(100); }
while (Update());
}
catch (System.IO.IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
/// <summary>
/// Read and data from the network. Should be called on a separate thread.
/// </summary>
/// <returns></returns>
private bool Update()
{
handler.OnUpdate();
if (c.Client == null || !c.Connected) { return false; }
int id = 0, size = 0;
try
{
while (c.Client.Available > 0)
{
size = readNextVarInt(); //Packet size
id = readNextVarInt(); //Packet ID
switch (id)
{
case 0x00:
byte[] keepalive = new byte[4] { 0, 0, 0, 0 };
Receive(keepalive, 0, 4, SocketFlags.None);
byte[] keepalive_packet = concatBytes(getVarInt(0x00), keepalive);
byte[] keepalive_tosend = concatBytes(getVarInt(keepalive_packet.Length), keepalive_packet);
Send(keepalive_tosend);
break;
case 0x02:
handler.OnTextReceived(ChatParser.ParseText(readNextString()));
break;
case 0x3A:
int autocomplete_count = readNextVarInt();
string tab_list = "";
for (int i = 0; i < autocomplete_count; i++)
{
autocomplete_result = readNextString();
if (autocomplete_result != "")
tab_list = tab_list + autocomplete_result + " ";
}
autocomplete_received = true;
tab_list = tab_list.Trim();
if (tab_list.Length > 0)
ConsoleIO.WriteLineFormatted("§8" + tab_list, false);
break;
case 0x40:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString()));
return false;
default:
readData(size - getVarInt(id).Length); //Skip packet
break;
}
}
}
catch (SocketException) { return false; }
return true;
}
/// <summary>
/// Start the updating thread. Should be called after login success.
/// </summary>
private void StartUpdating()
{
netRead = new Thread(new ThreadStart(Updater));
netRead.Name = "ProtocolPacketHandler";
netRead.Start();
}
/// <summary>
/// Disconnect from the server, cancel network reading.
/// </summary>
public void Dispose()
{
try
{
if (netRead != null)
{
netRead.Abort();
c.Close();
}
}
catch { }
}
/// <summary>
/// Read some data and discard the result
/// </summary>
/// <param name="offset">Amount of bytes to read</param>
private void readData(int offset)
{
if (offset > 0)
{
try
{
byte[] cache = new byte[offset];
Receive(cache, 0, offset, SocketFlags.None);
}
catch (OutOfMemoryException) { }
}
}
/// <summary>
/// Read a string from the network
/// </summary>
/// <returns>The string</returns>
private string readNextString()
{
int length = readNextVarInt();
if (length > 0)
{
byte[] cache = new byte[length];
Receive(cache, 0, length, SocketFlags.None);
return Encoding.UTF8.GetString(cache);
}
else return "";
}
/// <summary>
/// Read a byte array from the network
/// </summary>
/// <returns>The byte array</returns>
private byte[] readNextByteArray()
{
byte[] tmp = new byte[2];
Receive(tmp, 0, 2, SocketFlags.None);
Array.Reverse(tmp);
short len = BitConverter.ToInt16(tmp, 0);
byte[] data = new byte[len];
Receive(data, 0, len, SocketFlags.None);
return data;
}
/// <summary>
/// Read an integer from the network
/// </summary>
/// <returns>The integer</returns>
private int readNextVarInt()
{
int i = 0;
int j = 0;
int k = 0;
byte[] tmp = new byte[1];
while (true)
{
Receive(tmp, 0, 1, SocketFlags.None);
k = tmp[0];
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((k & 0x80) != 128) break;
}
return i;
}
/// <summary>
/// Build an integer for sending over the network
/// </summary>
/// <param name="paramInt">Integer to encode</param>
/// <returns>Byte array for this integer</returns>
private static byte[] getVarInt(int paramInt)
{
List<byte> bytes = new List<byte>();
while ((paramInt & -128) != 0)
{
bytes.Add((byte)(paramInt & 127 | 128));
paramInt = (int)(((uint)paramInt) >> 7);
}
bytes.Add((byte)paramInt);
return bytes.ToArray();
}
/// <summary>
/// Easily append several byte arrays
/// </summary>
/// <param name="bytes">Bytes to append</param>
/// <returns>Array containing all the data</returns>
private static byte[] concatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
/// <summary>
/// C-like atoi function for parsing an int from string
/// </summary>
/// <param name="str">String to parse</param>
/// <returns>Int parsed</returns>
private static int atoi(string str)
{
return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray()));
}
/// <summary>
/// Network reading method. Read bytes from the socket or encrypted socket.
/// </summary>
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
int read = 0;
while (read < offset)
{
if (encrypted)
{
read += s.Read(buffer, start + read, offset - read);
}
else read += c.Client.Receive(buffer, start + read, offset - read, f);
}
}
/// <summary>
/// Network sending method. Send bytes using the socket or encrypted socket.
/// </summary>
/// <param name="buffer"></param>
private void Send(byte[] buffer)
{
if (encrypted)
{
s.Write(buffer, 0, buffer.Length);
}
else c.Client.Send(buffer);
}
/// <summary>
/// Do the Minecraft login.
/// </summary>
/// <returns>True if login successful</returns>
public bool Login()
{
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(protocolversion);
byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.getServerHost());
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)handler.getServerPort()); Array.Reverse(server_port);
byte[] next_state = getVarInt(2);
byte[] handshake_packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] handshake_packet_tosend = concatBytes(getVarInt(handshake_packet.Length), handshake_packet);
Send(handshake_packet_tosend);
byte[] username_val = Encoding.UTF8.GetBytes(handler.getUsername());
byte[] username_len = getVarInt(username_val.Length);
byte[] login_packet = concatBytes(packet_id, username_len, username_val);
byte[] login_packet_tosend = concatBytes(getVarInt(login_packet.Length), login_packet);
Send(login_packet_tosend);
readNextVarInt(); //Packet size
int pid = readNextVarInt(); //Packet ID
if (pid == 0x00) //Login rejected
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString()));
return false;
}
else if (pid == 0x01) //Encryption request
{
string serverID = readNextString();
byte[] Serverkey = readNextByteArray();
byte[] token = readNextByteArray();
return StartEncryption(handler.getUserUUID(), handler.getSessionID(), token, serverID, Serverkey);
}
else if (pid == 0x02) //Login successful
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.", false);
StartUpdating();
return true; //No need to check session or start encryption
}
else return false;
}
/// <summary>
/// Start network encryption. Automatically called by Login() if the server requests encryption.
/// </summary>
/// <returns>True if encryption was successful</returns>
private bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.", false);
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey)))
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session.");
return false;
}
}
//Encrypt the data
byte[] key_enc = RSAService.Encrypt(secretKey, false);
byte[] token_enc = RSAService.Encrypt(token, false);
byte[] key_len = BitConverter.GetBytes((short)key_enc.Length); Array.Reverse(key_len);
byte[] token_len = BitConverter.GetBytes((short)token_enc.Length); Array.Reverse(token_len);
//Encryption Response packet
byte[] packet_id = getVarInt(0x01);
byte[] encryption_response = concatBytes(packet_id, key_len, key_enc, token_len, token_enc);
byte[] encryption_response_tosend = concatBytes(getVarInt(encryption_response.Length), encryption_response);
Send(encryption_response_tosend);
//Start client-side encryption
s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
encrypted = true;
//Read and skip the next packet
int received_packet_size = readNextVarInt();
int received_packet_id = readNextVarInt();
bool encryption_success = (received_packet_id == 0x02);
if (received_packet_id == 0) { handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString())); }
else readData(received_packet_size - getVarInt(received_packet_id).Length);
if (encryption_success) { StartUpdating(); }
return encryption_success;
}
/// <summary>
/// Useless padding packet for solving Mono issue.
/// </summary>
/// <returns>The padding packet</returns>
public byte[] getPaddingPacket()
{
//Will generate a 15-bytes long padding packet
byte[] id = getVarInt(0x17); //Plugin Message
byte[] channel_name = Encoding.UTF8.GetBytes("MCC|Pad");
byte[] channel_name_len = getVarInt(channel_name.Length);
byte[] data = new byte[] { 0x00, 0x00, 0x00 };
byte[] data_len = BitConverter.GetBytes((short)data.Length); Array.Reverse(data_len);
byte[] packet_data = concatBytes(id, channel_name_len, channel_name, data_len, data);
byte[] packet_length = getVarInt(packet_data.Length);
return concatBytes(packet_length, packet_data);
}
/// <summary>
/// Send a chat message to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] packet_id = getVarInt(0x01);
byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length);
byte[] message_packet = concatBytes(packet_id, message_len, message_val);
byte[] message_packet_tosend = concatBytes(getVarInt(message_packet.Length), message_packet);
Send(message_packet_tosend);
return true;
}
catch (SocketException) { return false; }
}
/// <summary>
/// Send a respawn packet to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendRespawnPacket()
{
try
{
byte[] packet_id = getVarInt(0x16);
byte[] action_id = new byte[] { 0 };
byte[] respawn_packet = concatBytes(getVarInt(packet_id.Length + 1), packet_id, action_id);
Send(respawn_packet);
return true;
}
catch (SocketException) { return false; }
}
/// <summary>
/// Disconnect from the server
/// </summary>
/// <param name="message">Optional disconnect reason</param>
public void Disconnect()
{
try
{
byte[] packet_id = getVarInt(0x40);
byte[] message_val = Encoding.UTF8.GetBytes("\"disconnect.quitting\"");
byte[] message_len = getVarInt(message_val.Length);
byte[] disconnect_packet = concatBytes(packet_id, message_len, message_val);
byte[] disconnect_packet_tosend = concatBytes(getVarInt(disconnect_packet.Length), disconnect_packet);
Send(disconnect_packet_tosend);
}
catch (SocketException) { }
catch (System.IO.IOException) { }
catch (NullReferenceException) { }
catch (ObjectDisposedException) { }
}
/// <summary>
/// Autocomplete text while typing username or command
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return "";
byte[] packet_id = getVarInt(0x14);
byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor);
byte[] tocomplete_len = getVarInt(tocomplete_val.Length);
byte[] tabcomplete_packet = concatBytes(packet_id, tocomplete_len, tocomplete_val);
byte[] tabcomplete_packet_tosend = concatBytes(getVarInt(tabcomplete_packet.Length), tabcomplete_packet);
autocomplete_received = false;
autocomplete_result = BehindCursor;
Send(tabcomplete_packet_tosend);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
return autocomplete_result;
}
/// <summary>
/// Ping a Minecraft server to get information about the server
/// </summary>
/// <returns>True if ping was successful</returns>
public static bool doPing(string host, int port, ref int protocolversion, ref string version)
{
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
tcp.ReceiveBufferSize = 1024 * 1024;
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(4);
byte[] server_adress_val = Encoding.UTF8.GetBytes(host);
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port);
byte[] next_state = getVarInt(1);
byte[] packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] tosend = concatBytes(getVarInt(packet.Length), packet);
tcp.Client.Send(tosend, SocketFlags.None);
byte[] status_request = getVarInt(0);
byte[] request_packet = concatBytes(getVarInt(status_request.Length), status_request);
tcp.Client.Send(request_packet, SocketFlags.None);
Protocol17Handler ComTmp = new Protocol17Handler(tcp);
if (ComTmp.readNextVarInt() > 0) //Read Response length
{
if (ComTmp.readNextVarInt() == 0x00) //Read Packet ID
{
string result = ComTmp.readNextString(); //Get the Json data
if (result[0] == '{' && result.Contains("protocol\":") && result.Contains("name\":\""))
{
string[] tmp_ver = result.Split(new string[] { "protocol\":" }, StringSplitOptions.None);
string[] tmp_name = result.Split(new string[] { "name\":\"" }, StringSplitOptions.None);
if (tmp_ver.Length >= 2 && tmp_name.Length >= 2)
{
protocolversion = atoi(tmp_ver[1]);
version = tmp_name[1].Split('"')[0];
if (result.Contains("modinfo\":"))
{
//Server is running Forge (which is not supported)
version = "Forge " + version;
protocolversion = 0;
}
ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ").", false);
return true;
}
}
}
}
return false;
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Crypto;
namespace MinecraftClient.Protocol
{
/// <summary>
/// Interface for the Minecraft protocol handler.
/// A protocol handler is used to communicate with the Minecraft server.
/// This interface allows to abstract from the underlying minecraft version in other parts of the program.
/// The protocol handler will take care of parsing and building the appropriate network packets.
/// </summary>
public interface IMinecraftCom : IDisposable, IAutoComplete, IPaddingProvider
{
/// <summary>
/// Start the login procedure once connected to the server
/// </summary>
/// <returns>True if login was successful</returns>
bool Login();
/// <summary>
/// Disconnect from the server
/// </summary>
/// <param name="message">Reason</param>
void Disconnect();
/// <summary>
/// Send a chat message or command to the server
/// </summary>
/// <param name="message">Text to send</param>
/// <returns>True if successfully sent</returns>
bool SendChatMessage(string message);
/// <summary>
/// Allow to respawn after death
/// </summary>
/// <returns>True if packet successfully sent</returns>
bool SendRespawnPacket();
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol
{
/// <summary>
/// Interface for the MinecraftCom Handler.
/// It defines some callbacks that the MinecraftCom handler must have.
/// It allows the protocol handler to abstract from the other parts of the program.
/// </summary>
public interface IMinecraftComHandler
{
/* The MinecraftCom Hanler must
* provide these getters */
int getServerPort();
string getServerHost();
string getUsername();
string getUserUUID();
string getSessionID();
/// <summary>
/// This method is called when the protocol handler receives a chat message
/// </summary>
void OnTextReceived(string text);
/// <summary>
/// This method is called when the connection has been lost
/// </summary>
void OnConnectionLost(ChatBot.DisconnectReason reason, string message);
/// <summary>
/// Called ~10 times per second (10 ticks per second)
/// Useful for updating bots in other parts of the program
/// </summary>
void OnUpdate();
}
}

View file

@ -0,0 +1,208 @@
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
{
/// <summary>
/// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server.
/// </summary>
public static class ProtocolHandler
{
/// <summary>
/// Retrieve information about a Minecraft server
/// </summary>
/// <param name="serverIP">Server IP to ping</param>
/// <param name="protocolversion">Will contain protocol version, if ping successful</param>
/// <param name="version">Will contain minecraft version, if ping successful</param>
/// <returns>TRUE if ping was successful</returns>
public static bool GetServerInfo(string serverIP, ref int protocolversion, ref string version)
{
try
{
string host; int port;
string[] sip = serverIP.Split(':');
host = sip[0];
if (sip.Length == 1)
{
port = 25565;
}
else
{
try
{
port = Convert.ToInt32(sip[1]);
}
catch (FormatException) { port = 25565; }
}
if (Protocol16Handler.doPing(host, port, ref protocolversion, ref version))
{
return true;
}
else if (Protocol17Handler.doPing(host, port, ref protocolversion, ref version))
{
return true;
}
else
{
ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)", false);
return false;
}
}
catch
{
ConsoleIO.WriteLineFormatted("§8An error occured while attempting to connect to this IP.", false);
return false;
}
}
/// <summary>
/// Get a protocol handler for the specified Minecraft version
/// </summary>
/// <param name="Client">Tcp Client connected to the server</param>
/// <param name="ProtocolVersion">Protocol version to handle</param>
/// <param name="Handler">Handler with the appropriate callbacks</param>
/// <returns></returns>
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);
throw new NotSupportedException("The protocol version '" + ProtocolVersion + "' is not supported.");
}
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium };
/// <summary>
/// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
/// </summary>
/// <param name="user">Login</param>
/// <param name="pass">Password</param>
/// <param name="accesstoken">Will contain the access token returned by Minecraft.net, if the login is successful</param>
/// <param name="uuid">Will contain the player's UUID, needed for multiplayer</param>
/// <returns>Returns the status of the login (Success, Failure, etc.)</returns>
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\": \"" + user + "\", \"password\": \"" + 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, false);
return LoginResult.OtherError;
}
}
catch (System.Security.Authentication.AuthenticationException)
{
return LoginResult.SSLError;
}/*
catch
{
return LoginResult.OtherError;
}*/
}
/// <summary>
/// Check session using Mojang's Yggdrasil authentication scheme. Allow to join an online-mode server
/// </summary>
/// <param name="user">Username</param>
/// <param name="accesstoken">Session ID</param>
/// <param name="serverhash">Server ID</param>
/// <returns>TRUE if session was successfully checked</returns>
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; }
}
/// <summary>
/// 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.
/// </summary>
/// <param name="host">Host to connect to</param>
/// <param name="endpoint">Endpoint for making the request</param>
/// <param name="request">Request payload</param>
/// <param name="result">Request result</param>
/// <returns>HTTP Status code</returns>
private static int doHTTPSPost(string host, string endpoint, string request, ref string result)
{
TcpClient client = ProxyHandler.newTcpClient(host, 443);
SslStream stream = new SslStream(client.GetStream());
stream.AuthenticateAsClient(host);
List<String> http_request = new List<string>();
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"))
{
result = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
return Settings.str2int(raw_result.Split(' ')[1]);
}
else return 520; //Web server is returning an unknown error
}
}
}