diff --git a/MinecraftClient.sln b/MinecraftClient.sln
new file mode 100644
index 00000000..666d8566
--- /dev/null
+++ b/MinecraftClient.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinecraftClient", "MinecraftClient\MinecraftClient.csproj", "{1E2FACE4-F5CA-4323-9641-740C6A551770}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1E2FACE4-F5CA-4323-9641-740C6A551770}.Debug|x86.ActiveCfg = Debug|x86
+ {1E2FACE4-F5CA-4323-9641-740C6A551770}.Debug|x86.Build.0 = Debug|x86
+ {1E2FACE4-F5CA-4323-9641-740C6A551770}.Release|x86.ActiveCfg = Release|x86
+ {1E2FACE4-F5CA-4323-9641-740C6A551770}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/MinecraftClient/Bots.cs b/MinecraftClient/Bots.cs
new file mode 100644
index 00000000..e9187c18
--- /dev/null
+++ b/MinecraftClient/Bots.cs
@@ -0,0 +1,845 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient
+{
+ ///
+ /// Welcome to the Bot API file !
+ /// The virtual class "ChatBot" contains anything you need for creating chat bots
+ /// Inherit from this class while adding your bot class to the namespace "Bots", below.
+ /// Once your bot is created, simply edit the switch in Program.cs to add the corresponding command-line argument!
+ ///
+
+ ///
+ /// The virtual class containing anything you need for creating chat bots.
+ ///
+
+ public abstract class ChatBot
+ {
+ public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost };
+
+ #region MinecraftCom Handler for this bot
+
+ //Will be automatically set on bot loading, don't worry about this
+ public void SetHandler(MinecraftCom handler) { this.handler = handler; }
+ private MinecraftCom handler;
+
+ #endregion
+
+ ///
+ /// Anything you want to initialize your bot, will be called on load by MinecraftCom
+ ///
+
+ public virtual void Initialize() { }
+
+ ///
+ /// Will be called every ~100ms (10fps) if loaded in MinecraftCom
+ ///
+
+ public virtual void Update() { }
+
+ ///
+ /// Any text sent by the server will be sent here by MinecraftCom
+ ///
+ /// Text from the server
+
+ public virtual void GetText(string text) { }
+
+ ///
+ /// Is called when the client has been disconnected fom the server
+ ///
+ /// Disconnect Reason
+ /// Kick message, if any
+ /// Return TRUE if the client is about to restart
+
+ public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; }
+
+ #region ToolBox
+
+ ///
+ /// Send text to the server. Can be anything such as chat messages or commands
+ ///
+ /// Text to send to the server
+
+ protected void SendText(string text)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleIO.WriteLine("BOT:" + text);
+ handler.SendChatMessage(text);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
+
+ ///
+ /// Remove color codes ("§c") from a text message received from the server
+ ///
+
+ protected static string getVerbatim(string text)
+ {
+ string verbatim = "";
+ for (int i = 0; i < text.Length; i++)
+ {
+ if (text[i] == '§')
+ {
+ i++; //Will skip also the next char
+ }
+ else verbatim += text[i]; //Add the char
+ }
+ return verbatim;
+ }
+
+ ///
+ /// Verify that a string contains only a-z A-Z 0-9 and _ characters.
+ ///
+
+ protected static bool isValidName(string username)
+ {
+ if (username == "") { return false; }
+ string validchars =
+ "abcdefghijklmnopqrstuvwxyz"
+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "1234567890_";
+
+ bool passe = false;
+ bool ok = true;
+ for (int i = 0; i < username.Length; i++)
+ {
+ passe = false;
+ for (int j = 0; j < validchars.Length; j++)
+ {
+ if (username[i] == validchars[j])
+ {
+ passe = true;
+ break;
+ }
+ }
+ if (!passe)
+ {
+ ok = false;
+ break;
+ }
+ }
+ return ok;
+ }
+
+ ///
+ /// Returns true is the text passed is a private message sent to the bot
+ ///
+ /// text to test
+ /// if it's a private message, this will contain the message
+ /// if it's a private message, this will contain the player name that sends the message
+ /// Returns true if the text is a private message
+
+ protected static bool isPrivateMessage(string text, ref string message, ref string sender)
+ {
+ if (text == "") { return false; }
+ string[] tmp = text.Split(' ');
+
+ try
+ {
+ //Detect vanilla /tell messages
+ //Someone whispers message
+ if (tmp.Length > 2 && tmp[1] == "whispers")
+ {
+ message = text.Substring(tmp[0].Length + 10);
+ sender = tmp[0];
+ return isValidName(sender);
+ }
+
+ //Detect Essentials (Bukkit) /m messages
+ //[Someone -> me] message
+ else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->"
+ && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers
+ {
+ message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1);
+ sender = tmp[0].Substring(1);
+ if (sender[0] == '~') { sender = sender.Substring(1); }
+ return isValidName(sender);
+ }
+ else return false;
+ }
+ catch (IndexOutOfRangeException) { return false; }
+ }
+
+ ///
+ /// Returns true is the text passed is a public message written by a player on the chat
+ ///
+ /// text to test
+ /// if it's message, this will contain the message
+ /// if it's message, this will contain the player name that sends the message
+ /// Returns true if the text is a chat message
+
+ protected static bool isChatMessage(string text, ref string message, ref string sender)
+ {
+ //Detect chat messages
+ // message
+ //<*Faction Someone> message
+ //<*Faction Someone>: message
+ //<*Faction ~Nicknamed>: message
+ if (text == "") { return false; }
+ if (text[0] == '<')
+ {
+ try
+ {
+ text = text.Substring(1);
+ string[] tmp = text.Split('>');
+ sender = tmp[0];
+ message = text.Substring(sender.Length + 2);
+ if (message.Length > 1 && message[0] == ' ')
+ { message = message.Substring(1); }
+ tmp = sender.Split(' ');
+ sender = tmp[tmp.Length - 1];
+ if (sender[0] == '~') { sender = sender.Substring(1); }
+ return isValidName(sender);
+ }
+ catch (IndexOutOfRangeException) { return false; }
+ }
+ else return false;
+ }
+
+ ///
+ /// Writes some text in the console. Nothing will be sent to the server.
+ ///
+ /// Log text to write
+
+ public static void LogToConsole(string text)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleIO.WriteLine("[BOT] " + text);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
+
+ ///
+ /// Disconnect from the server and restart the program
+ /// It will unload & reload all the bots and then reconnect to the server
+ ///
+
+ protected void ReconnectToTheServer() { ReconnectToTheServer(3); }
+
+ ///
+ /// Disconnect from the server and restart the program
+ /// It will unload & reload all the bots and then reconnect to the server
+ ///
+ /// If connection fails, the client will make X extra attempts
+
+ protected void ReconnectToTheServer(int ExtraAttempts)
+ {
+ McTcpClient.AttemptsLeft = ExtraAttempts;
+ Program.Restart();
+ }
+
+ ///
+ /// Unload the chatbot, and release associated memory.
+ ///
+
+ protected void UnloadBot()
+ {
+ handler.BotUnLoad(this);
+ }
+
+ #endregion
+ }
+
+ namespace Bots
+ {
+ ///
+ /// Example of message receiving.
+ ///
+
+ public class TestBot : ChatBot
+ {
+ public override void GetText(string text)
+ {
+ string message = "";
+ string username = "";
+ text = getVerbatim(text);
+
+ if (isPrivateMessage(text, ref message, ref username))
+ {
+ ConsoleIO.WriteLine("Bot: " + username + " told me : " + message);
+ }
+ else if (isChatMessage(text, ref message, ref username))
+ {
+ ConsoleIO.WriteLine("Bot: " + username + " said : " + message);
+ }
+ }
+ }
+
+ ///
+ /// This bot sends a /ping command every 60 seconds in order to stay non-afk.
+ ///
+
+ public class AntiAFK : ChatBot
+ {
+ private int count;
+ private int timeping;
+
+ ///
+ /// This bot sends a /ping command every X seconds in order to stay non-afk.
+ ///
+ /// Time amount between each ping (10 = 1s, 600 = 1 minute, etc.)
+
+ public AntiAFK(int pingparam)
+ {
+ count = 0;
+ timeping = pingparam;
+ if (timeping < 10) { timeping = 10; } //To avoid flooding
+ }
+
+ public override void Update()
+ {
+ count++;
+ if (count == timeping)
+ {
+ SendText("/ping");
+ count = 0;
+ }
+ }
+ }
+
+ ///
+ /// This bot sends a /list command every X seconds and save the result.
+ ///
+
+ public class PlayerListLogger : ChatBot
+ {
+ private int count;
+ private int timeping;
+ private string file;
+
+ ///
+ /// This bot sends a /list command every X seconds and save the result.
+ ///
+ /// Time amount between each list ping (10 = 1s, 600 = 1 minute, etc.)
+
+ public PlayerListLogger(int pingparam, string filetosavein)
+ {
+ count = 0;
+ file = filetosavein;
+ timeping = pingparam;
+ if (timeping < 10) { timeping = 10; } //To avoid flooding
+
+ }
+
+ public override void Update()
+ {
+ count++;
+ if (count == timeping)
+ {
+ SendText("/list");
+ count = 0;
+ }
+ }
+
+ public override void GetText(string text)
+ {
+ if (text.Contains("Joueurs en ligne") || text.Contains("Connected:") || text.Contains("online:"))
+ {
+ LogToConsole("Saving Player List");
+ DateTime now = DateTime.Now;
+ string TimeStamp = "[" + now.Year + '/' + now.Month + '/' + now.Day + ' ' + now.Hour + ':' + now.Minute + ']';
+ System.IO.File.AppendAllText(file, TimeStamp + "\n" + getVerbatim(text) + "\n\n");
+ }
+ }
+ }
+
+ ///
+ /// "Le jeu du Pendu" (Hangman game)
+ ///
+
+ public class Pendu : ChatBot
+ {
+ private int vie = 0;
+ private int vie_param = 10;
+ private int compteur = 0;
+ private int compteur_param = 3000; //5 minutes
+ private bool running = false;
+ private bool[] discovered;
+ private string word = "";
+ private string letters = "";
+ private string[] owners;
+ private bool English;
+
+ ///
+ /// "Le jeu du Pendu" (Hangman Game)
+ ///
+ /// if true, the game will be in english. If false, the game will be in french.
+
+ public Pendu(bool english)
+ {
+ English = english;
+ }
+
+ public override void Initialize()
+ {
+ owners = getowners();
+ }
+
+ public override void Update()
+ {
+ if (running)
+ {
+ if (compteur > 0)
+ {
+ compteur--;
+ }
+ else
+ {
+ SendText(English ? "You took too long to try a letter." : "Temps imparti écoulé !");
+ SendText(English ? "Game canceled." : "Partie annulée.");
+ running = false;
+ }
+ }
+ }
+
+ public override void GetText(string text)
+ {
+ string message = "";
+ string username = "";
+ text = getVerbatim(text);
+
+ if (isPrivateMessage(text, ref message, ref username))
+ {
+ if (owners.Contains(username.ToUpper()))
+ {
+ switch (message)
+ {
+ case "start":
+ start();
+ break;
+ case "stop":
+ running = false;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (running && isChatMessage(text, ref message, ref username))
+ {
+ if (message.Length == 1)
+ {
+ char letter = message.ToUpper()[0];
+ if (letter >= 'A' && letter <= 'Z')
+ {
+ if (letters.Contains(letter))
+ {
+ SendText(English ? ("Letter " + letter + " has already been tried.") : ("Le " + letter + " a déjà été proposé."));
+ }
+ else
+ {
+ letters += letter;
+ compteur = compteur_param;
+
+ if (word.Contains(letter))
+ {
+ for (int i = 0; i < word.Length; i++) { if (word[i] == letter) { discovered[i] = true; } }
+ SendText(English ? ("Yes, the word contains a " + letter + '!') : ("Le " + letter + " figurait bien dans le mot :)"));
+ }
+ else
+ {
+ vie--;
+ if (vie == 0)
+ {
+ SendText(English ? "Game Over! :]" : "Perdu ! Partie terminée :]");
+ SendText(English ? ("The word was: " + word) : ("Le mot était : " + word));
+ running = false;
+ }
+ else SendText(English ? ("The " + letter + "? No.") : ("Le " + letter + " ? Non."));
+ }
+
+ if (running)
+ {
+ SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
+ : ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
+ }
+
+ if (winner)
+ {
+ SendText(English ? ("Congrats, " + username + '!') : ("Félicitations, " + username + " !"));
+ running = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void start()
+ {
+ vie = vie_param;
+ running = true;
+ letters = "";
+ word = chooseword();
+ compteur = compteur_param;
+ discovered = new bool[word.Length];
+
+ SendText(English ? "Hangman v1.0 - By ORelio" : "Pendu v1.0 - Par ORelio");
+ SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
+ : ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
+ SendText(English ? ("Try some letters ... :)") : ("Proposez une lettre ... :)"));
+ }
+
+ private string chooseword()
+ {
+ if (System.IO.File.Exists(English ? "words.txt" : "mots.txt"))
+ {
+ string[] dico = System.IO.File.ReadAllLines(English ? "words.txt" : "mots.txt");
+ return dico[new Random().Next(dico.Length)];
+ }
+ else
+ {
+ LogToConsole(English ? "Cannot find words.txt !" : "Fichier mots.txt introuvable !");
+ return English ? "WORDSAREMISSING" : "DICOMANQUANT";
+ }
+ }
+
+ private string[] getowners()
+ {
+ List owners = new List();
+ owners.Add("CONSOLE");
+ if (System.IO.File.Exists("bot-owners.txt"))
+ {
+ foreach (string s in System.IO.File.ReadAllLines("bot-owners.txt"))
+ {
+ owners.Add(s.ToUpper());
+ }
+ }
+ else LogToConsole(English ? "Cannot find bot-owners.txt !" : "Fichier bot-owners.txt introuvable !");
+ return owners.ToArray();
+ }
+
+ private string word_cached
+ {
+ get
+ {
+ string printed = "";
+ for (int i = 0; i < word.Length; i++)
+ {
+ if (discovered[i])
+ {
+ printed += word[i];
+ }
+ else printed += '_';
+ }
+ return printed;
+ }
+ }
+
+ private bool winner
+ {
+ get
+ {
+ for (int i = 0; i < discovered.Length; i++)
+ {
+ if (!discovered[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ ///
+ /// This bot make the console beep on some specified words. Useful to detect when someone is talking to you, for example.
+ ///
+
+ public class Alerts : ChatBot
+ {
+ private string[] dictionnary = new string[0];
+ private string[] excludelist = new string[0];
+
+ public override void Initialize()
+ {
+ if (System.IO.File.Exists("alerts.txt"))
+ {
+ dictionnary = System.IO.File.ReadAllLines("alerts.txt");
+
+ for (int i = 0; i < dictionnary.Length; i++)
+ {
+ dictionnary[i] = dictionnary[i].ToLower();
+ }
+ }
+ else LogToConsole("Cannot find alerts.txt !");
+
+ if (System.IO.File.Exists("alerts-exclude.txt"))
+ {
+ excludelist = System.IO.File.ReadAllLines("alerts-exclude.txt");
+
+ for (int i = 0; i < excludelist.Length; i++)
+ {
+ excludelist[i] = excludelist[i].ToLower();
+ }
+ }
+ else LogToConsole("Cannot find alerts-exclude.txt !");
+ }
+
+ public override void GetText(string text)
+ {
+ text = getVerbatim(text);
+ string comp = text.ToLower();
+ foreach (string alert in dictionnary)
+ {
+ if (comp.Contains(alert))
+ {
+ bool ok = true;
+
+ foreach (string exclusion in excludelist)
+ {
+ if (comp.Contains(exclusion))
+ {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok)
+ {
+ Console.Beep(); //Text found !
+ #region Displaying the text with the interesting part highlighted
+
+ Console.BackgroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ConsoleColor.White;
+
+ //Will be used for text displaying
+ string[] temp = comp.Split(alert.Split(','), StringSplitOptions.RemoveEmptyEntries);
+ int p = 0;
+
+ //Special case : alert in the beginning of the text
+ string test = "";
+ for (int i = 0; i < alert.Length; i++)
+ {
+ test += comp[i];
+ }
+ if (test == alert)
+ {
+ Console.BackgroundColor = ConsoleColor.Yellow;
+ Console.ForegroundColor = ConsoleColor.Red;
+ for (int i = 0; i < alert.Length; i++)
+ {
+ ConsoleIO.Write(text[p]);
+ p++;
+ }
+ }
+
+ //Displaying the rest of the text
+ for (int i = 0; i < temp.Length; i++)
+ {
+ Console.BackgroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ConsoleColor.White;
+ for (int j = 0; j < temp[i].Length; j++)
+ {
+ ConsoleIO.Write(text[p]);
+ p++;
+ }
+ Console.BackgroundColor = ConsoleColor.Yellow;
+ Console.ForegroundColor = ConsoleColor.Red;
+ try
+ {
+ for (int j = 0; j < alert.Length; j++)
+ {
+ ConsoleIO.Write(text[p]);
+ p++;
+ }
+ }
+ catch (IndexOutOfRangeException) { }
+ }
+ Console.BackgroundColor = ConsoleColor.Black;
+ Console.ForegroundColor = ConsoleColor.Gray;
+ ConsoleIO.Write('\n');
+
+ #endregion
+
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// This bot saves the received messages in a text file.
+ ///
+
+ public class ChatLog : ChatBot
+ {
+ public enum MessageFilter { AllText, AllMessages, OnlyChat, OnlyWhispers };
+ private bool dateandtime;
+ private bool saveOther = true;
+ private bool saveChat = true;
+ private bool savePrivate = true;
+ private string logfile;
+
+ ///
+ /// This bot saves the messages received in the specified file, with some filters and date/time tagging.
+ ///
+ /// The file to save the log in
+ /// The kind of messages to save
+ /// Add a date and time before each message
+
+ public ChatLog(string file, MessageFilter filter, bool AddDateAndTime)
+ {
+ dateandtime = AddDateAndTime;
+ logfile = file;
+ switch (filter)
+ {
+ case MessageFilter.AllText:
+ saveOther = true;
+ savePrivate = true;
+ saveChat = true;
+ break;
+ case MessageFilter.AllMessages:
+ saveOther = false;
+ savePrivate = true;
+ saveChat = true;
+ break;
+ case MessageFilter.OnlyChat:
+ saveOther = false;
+ savePrivate = false;
+ saveChat = true;
+ break;
+ case MessageFilter.OnlyWhispers:
+ saveOther = false;
+ savePrivate = true;
+ saveChat = false;
+ break;
+ }
+ }
+
+ public override void GetText(string text)
+ {
+ text = getVerbatim(text);
+ string sender = "";
+ string message = "";
+
+ if (saveChat && isChatMessage(text, ref message, ref sender))
+ {
+ save("Chat " + sender + ": " + message);
+ }
+ else if (savePrivate && isPrivateMessage(text, ref message, ref sender))
+ {
+ save("Private " + sender + ": " + message);
+ }
+ else if (saveOther)
+ {
+ save("Other: " + text);
+ }
+ }
+
+ private void save(string tosave)
+ {
+ if (dateandtime)
+ {
+ int day = DateTime.Now.Day, month = DateTime.Now.Month;
+ int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
+
+ string D = day < 10 ? "0" + day : "" + day;
+ string M = month < 10 ? "0" + month : "" + day;
+ string Y = "" + DateTime.Now.Year;
+
+ string h = hour < 10 ? "0" + hour : "" + hour;
+ string m = minute < 10 ? "0" + minute : "" + minute;
+ string s = second < 10 ? "0" + second : "" + second;
+
+ tosave = "" + D + '-' + M + '-' + Y + ' ' + h + ':' + m + ':' + s + ' ' + tosave;
+ }
+
+ System.IO.FileStream stream = new System.IO.FileStream(logfile, System.IO.FileMode.OpenOrCreate);
+ System.IO.StreamWriter writer = new System.IO.StreamWriter(stream);
+ stream.Seek(0, System.IO.SeekOrigin.End);
+ writer.WriteLine(tosave);
+ writer.Dispose();
+ stream.Close();
+ }
+ }
+
+ ///
+ /// This bot automatically re-join the server if kick message contains predefined string (Server is restarting ...)
+ ///
+
+ public class AutoRelog : ChatBot
+ {
+ private string[] dictionnary = new string[0];
+ private int attempts;
+ private int delay;
+
+ ///
+ /// This bot automatically re-join the server if kick message contains predefined string
+ ///
+ /// Delay before re-joining the server (in seconds)
+ /// Number of retries if connection fails (-1 = infinite)
+
+ public AutoRelog(int DelayBeforeRelog, int retries)
+ {
+ attempts = retries;
+ if (attempts == -1) { attempts = int.MaxValue; }
+ McTcpClient.AttemptsLeft = attempts;
+ delay = DelayBeforeRelog;
+ if (delay < 1) { delay = 1; }
+ }
+
+ public override void Initialize()
+ {
+ McTcpClient.AttemptsLeft = attempts;
+ if (System.IO.File.Exists("kickmessages.txt"))
+ {
+ dictionnary = System.IO.File.ReadAllLines("kickmessages.txt");
+
+ for (int i = 0; i < dictionnary.Length; i++)
+ {
+ dictionnary[i] = dictionnary[i].ToLower();
+ }
+ }
+ else LogToConsole("Cannot find kickmessages.txt !");
+ }
+
+ public override bool OnDisconnect(DisconnectReason reason, string message)
+ {
+ message = getVerbatim(message);
+ string comp = message.ToLower();
+ foreach (string msg in dictionnary)
+ {
+ if (comp.Contains(msg))
+ {
+ LogToConsole("Waiting " + delay + " seconds before reconnecting...");
+ System.Threading.Thread.Sleep(delay * 1000);
+ McTcpClient.AttemptsLeft = attempts;
+ ReconnectToTheServer();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ ///
+ /// Automatically send login command on servers usign the xAuth plugin
+ ///
+
+ public class xAuth : ChatBot
+ {
+ private string password;
+ private int countdown = 50;
+
+ public xAuth(string pass)
+ {
+ password = pass;
+ }
+
+ public override void Update()
+ {
+ countdown--;
+ if (countdown == 0)
+ {
+ SendText("/login " + password);
+ UnloadBot(); //This bot is no more needed.
+ }
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/BouncyCastle.Crypto.dll b/MinecraftClient/BouncyCastle.Crypto.dll
new file mode 100644
index 00000000..8531f7fc
Binary files /dev/null and b/MinecraftClient/BouncyCastle.Crypto.dll differ
diff --git a/MinecraftClient/ChatParser.cs b/MinecraftClient/ChatParser.cs
new file mode 100644
index 00000000..b0d07e3f
--- /dev/null
+++ b/MinecraftClient/ChatParser.cs
@@ -0,0 +1,297 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient
+{
+ ///
+ /// This class parses JSON chat data from MC 1.6+ and returns the appropriate string to be printed.
+ ///
+
+ static class ChatParser
+ {
+ ///
+ /// The main function to convert text from MC 1.6+ JSON to MC 1.5.2 formatted text
+ ///
+ /// JSON serialized text
+ /// Returns the translated text
+
+ public static string ParseText(string json)
+ {
+ int cursorpos = 0;
+ JSONData jsonData = String2Data(json, ref cursorpos);
+ return JSONData2String(jsonData).Replace("u0027", "'");
+ }
+
+ ///
+ /// An internal class to store unserialized JSON data
+ /// The data can be an object, an array or a string
+ ///
+
+ private class JSONData
+ {
+ public enum DataType { Object, Array, String };
+ private DataType type;
+ public DataType Type { get { return type; } }
+ public Dictionary Properties;
+ public List DataArray;
+ public string StringValue;
+ public JSONData(DataType datatype)
+ {
+ type = datatype;
+ Properties = new Dictionary();
+ DataArray = new List();
+ StringValue = String.Empty;
+ }
+ }
+
+ ///
+ /// Get the classic color tag corresponding to a color name
+ ///
+ /// Color Name
+ /// Color code
+
+ 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_cyan": return "§3";
+ case "dark_cyanred": return "§4";
+ case "dark_magenta": return "§5";
+ case "dark_yellow": return "§6";
+ case "gray": return "§7";
+ case "dark_gray": return "§8";
+ case "blue": return "§9";
+ case "green": return "§a";
+ case "cyan": return "§b";
+ case "red": return "§c";
+ case "magenta": return "§d";
+ case "yellow": return "§e";
+ case "white": return "§f";
+ default: return "";
+ }
+ }
+
+ ///
+ /// Rules for text translation
+ ///
+
+ private static bool init = false;
+ private static Dictionary TranslationRules = new Dictionary();
+ 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";
+
+ //Load an external dictionnary of translation rules
+ if (System.IO.File.Exists("translations.lang"))
+ {
+ string[] translations = System.IO.File.ReadAllLines("translations.lang");
+ foreach (string line in translations)
+ {
+ if (line.Length > 0)
+ {
+ string[] splitted = line.Split('=');
+ if (splitted.Length == 2)
+ {
+ TranslationRules[splitted[0]] = splitted[1];
+ }
+ }
+ }
+
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleIO.WriteLine("Translations file loaded.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
+ else //No external dictionnary found.
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleIO.WriteLine("MC 1.6+ warning: Translations file \"translations.lang\" not found."
+ + "\nYou can pick a translation file from .minecraft\\assets\\lang\\"
+ + "\nCopy to the same folder as MinecraftClient & rename to \"translations.lang\""
+ + "\nSome messages won't be properly printed without this file.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
+ }
+
+ ///
+ /// Format text using a specific formatting rule.
+ /// Example : * %s %s + ["ORelio", "is doing something"] = * ORelio is doing something
+ ///
+ /// Name of the rule, chosen by the server
+ /// Data to be used in the rule
+ /// Returns the formatted text according to the given data
+
+ private static string TranslateString(string rulename, List using_data)
+ {
+ if (!init) { InitRules(); init = true; }
+ if (TranslationRules.ContainsKey(rulename))
+ {
+ string[] syntax = TranslationRules[rulename].Split(new string[2] { "%s", "%d" }, 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);
+ }
+
+ ///
+ /// Parse a JSON string to build a JSON object
+ ///
+ /// String to parse
+ /// Cursor start (set to 0 for function init)
+ ///
+
+ 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] == '\\') { cursorpos++; }
+ 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);
+ }
+ }
+
+ ///
+ /// Use a JSON Object to build the corresponding string
+ ///
+ /// JSON object to convert
+ /// returns the Minecraft-formatted string
+
+ private static string JSONData2String(JSONData data)
+ {
+ string colorcode = "";
+ switch (data.Type)
+ {
+ case JSONData.DataType.Object:
+ if (data.Properties.ContainsKey("color"))
+ {
+ colorcode = color2tag(JSONData2String(data.Properties["color"]));
+ }
+ if (data.Properties.ContainsKey("text"))
+ {
+ return colorcode + JSONData2String(data.Properties["text"]) + colorcode;
+ }
+ else if (data.Properties.ContainsKey("translate"))
+ {
+ List using_data = new List();
+ if (data.Properties.ContainsKey("using"))
+ {
+ JSONData[] array = data.Properties["using"].DataArray.ToArray();
+ for (int i = 0; i < array.Length; i++)
+ {
+ using_data.Add(JSONData2String(array[i]));
+ }
+ }
+ return colorcode + TranslateString(JSONData2String(data.Properties["translate"]), using_data) + colorcode;
+ }
+ else return "";
+
+ case JSONData.DataType.Array:
+ string result = "";
+ foreach (JSONData item in data.DataArray)
+ {
+ result += JSONData2String(item);
+ }
+ return result;
+
+ case JSONData.DataType.String:
+ return data.StringValue;
+ }
+
+ return "";
+ }
+ }
+}
diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs
new file mode 100644
index 00000000..d2a181f5
--- /dev/null
+++ b/MinecraftClient/ConsoleIO.cs
@@ -0,0 +1,219 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient
+{
+ ///
+ /// Allows simultaneous console input and output without breaking user input
+ /// (Without having this annoying behaviour : User inp[Some Console output]ut)
+ ///
+
+ public static class ConsoleIO
+ {
+ public static void Reset() { if (reading) { reading = false; Console.Write("\b \b"); } }
+ private static LinkedList previous = new LinkedList();
+ private static string buffer = "";
+ private static string buffer2 = "";
+ private static bool consolelock = false;
+ private static bool reading = false;
+ private static bool writing = false;
+
+ #region Read User Input
+ public static string ReadLine()
+ {
+ ConsoleKeyInfo k = new ConsoleKeyInfo();
+ Console.Write('>');
+ reading = true;
+ buffer = "";
+ buffer2 = "";
+
+ while (k.Key != ConsoleKey.Enter)
+ {
+ k = Console.ReadKey(true);
+ while (writing) { }
+ consolelock = true;
+ switch (k.Key)
+ {
+ case ConsoleKey.Escape:
+ ClearLineAndBuffer();
+ break;
+ case ConsoleKey.Backspace:
+ RemoveOneChar();
+ break;
+ case ConsoleKey.Enter:
+ Console.Write('\n');
+ break;
+ case ConsoleKey.LeftArrow:
+ GoLeft();
+ break;
+ case ConsoleKey.RightArrow:
+ GoRight();
+ break;
+ case ConsoleKey.Home:
+ while (buffer.Length > 0) { GoLeft(); }
+ break;
+ case ConsoleKey.End:
+ while (buffer2.Length > 0) { GoRight(); }
+ break;
+ case ConsoleKey.Delete:
+ if (buffer2.Length > 0)
+ {
+ GoRight();
+ RemoveOneChar();
+ }
+ break;
+ case ConsoleKey.Oem6:
+ break;
+ case ConsoleKey.DownArrow:
+ if (previous.Count > 0)
+ {
+ ClearLineAndBuffer();
+ buffer = previous.First.Value;
+ previous.AddLast(buffer);
+ previous.RemoveFirst();
+ Console.Write(buffer);
+ }
+ break;
+ case ConsoleKey.UpArrow:
+ if (previous.Count > 0)
+ {
+ ClearLineAndBuffer();
+ buffer = previous.Last.Value;
+ previous.AddFirst(buffer);
+ previous.RemoveLast();
+ Console.Write(buffer);
+ }
+ break;
+ default:
+ AddChar(k.KeyChar);
+ break;
+ }
+ consolelock = false;
+ }
+ while (writing) { }
+ reading = false;
+ previous.AddLast(buffer + buffer2);
+ return buffer + buffer2;
+ }
+ #endregion
+
+ #region Console Output
+ public static void Write(string text)
+ {
+ while (consolelock) { }
+ writing = true;
+ if (reading)
+ {
+ ConsoleColor fore = Console.ForegroundColor;
+ ConsoleColor back = Console.BackgroundColor;
+ string buf = buffer;
+ string buf2 = buffer2;
+ ClearLineAndBuffer();
+ if (Console.CursorLeft == 0)
+ {
+ Console.CursorLeft = Console.BufferWidth - 1;
+ Console.CursorTop--;
+ Console.Write(' ');
+ Console.CursorLeft = Console.BufferWidth - 1;
+ Console.CursorTop--;
+ }
+ else Console.Write("\b \b");
+ Console.Write(text);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ Console.BackgroundColor = ConsoleColor.Black;
+ buffer = buf;
+ buffer2 = buf2;
+ Console.Write(">" + buffer);
+ if (buffer2.Length > 0)
+ {
+ Console.Write(buffer2 + " \b");
+ for (int i = 0; i < buffer2.Length; i++) { GoBack(); }
+ }
+ Console.ForegroundColor = fore;
+ Console.BackgroundColor = back;
+ }
+ else Console.Write(text);
+ writing = false;
+ }
+
+ public static void WriteLine(string line)
+ {
+ Write(line + '\n');
+ }
+
+ public static void Write(char c)
+ {
+ Write("" + c);
+ }
+ #endregion
+
+ #region subfunctions
+ private static void ClearLineAndBuffer()
+ {
+ while (buffer2.Length > 0) { GoRight(); }
+ while (buffer.Length > 0) { RemoveOneChar(); }
+ }
+ private static void RemoveOneChar()
+ {
+ if (buffer.Length > 0)
+ {
+ if (Console.CursorLeft == 0)
+ {
+ Console.CursorLeft = Console.BufferWidth - 1;
+ Console.CursorTop--;
+ Console.Write(' ');
+ Console.CursorLeft = Console.BufferWidth - 1;
+ Console.CursorTop--;
+ }
+ else Console.Write("\b \b");
+ buffer = buffer.Substring(0, buffer.Length - 1);
+
+ if (buffer2.Length > 0)
+ {
+ Console.Write(buffer2 + " \b");
+ for (int i = 0; i < buffer2.Length; i++) { GoBack(); }
+ }
+ }
+ }
+ private static void GoBack()
+ {
+ if (buffer.Length > 0)
+ {
+ if (Console.CursorLeft == 0)
+ {
+ Console.CursorLeft = Console.BufferWidth - 1;
+ Console.CursorTop--;
+ }
+ else Console.Write('\b');
+ }
+ }
+ private static void GoLeft()
+ {
+ if (buffer.Length > 0)
+ {
+ buffer2 = "" + buffer[buffer.Length - 1] + buffer2;
+ buffer = buffer.Substring(0, buffer.Length - 1);
+ Console.Write('\b');
+ }
+ }
+ private static void GoRight()
+ {
+ if (buffer2.Length > 0)
+ {
+ buffer = buffer + buffer2[0];
+ Console.Write(buffer2[0]);
+ buffer2 = buffer2.Substring(1);
+ }
+ }
+ private static void AddChar(char c)
+ {
+ Console.Write(c);
+ buffer += c;
+ Console.Write(buffer2);
+ for (int i = 0; i < buffer2.Length; i++) { GoBack(); }
+ }
+ #endregion
+ }
+}
diff --git a/MinecraftClient/Crypto.cs b/MinecraftClient/Crypto.cs
new file mode 100644
index 00000000..91b78953
--- /dev/null
+++ b/MinecraftClient/Crypto.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+using java.security;
+using java.security.spec;
+using javax.crypto;
+using javax.crypto.spec;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace MinecraftClient
+{
+ ///
+ /// Cryptographic functions ported from Minecraft Source Code (Java). Decompiled with MCP. Copy, paste, little adjustements.
+ ///
+
+ public class Crypto
+ {
+ public static PublicKey GenerateRSAPublicKey(byte[] key)
+ {
+ X509EncodedKeySpec localX509EncodedKeySpec = new X509EncodedKeySpec(key);
+ KeyFactory localKeyFactory = KeyFactory.getInstance("RSA");
+ return localKeyFactory.generatePublic(localX509EncodedKeySpec);
+ }
+
+ public static SecretKey GenerateAESPrivateKey()
+ {
+ CipherKeyGenerator var0 = new CipherKeyGenerator();
+ var0.Init(new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(), 128));
+ return new SecretKeySpec(var0.GenerateKey(), "AES");
+ }
+
+ public static byte[] getServerHash(String toencode, PublicKey par1PublicKey, SecretKey par2SecretKey)
+ {
+ return digest("SHA-1", new byte[][] { Encoding.GetEncoding("iso-8859-1").GetBytes(toencode), par2SecretKey.getEncoded(), par1PublicKey.getEncoded() });
+ }
+
+ public static byte[] Encrypt(Key par0Key, byte[] par1ArrayOfByte)
+ {
+ return func_75885_a(1, par0Key, par1ArrayOfByte);
+ }
+
+ private static byte[] digest(String par0Str, byte[][] par1ArrayOfByte)
+ {
+ MessageDigest var2 = MessageDigest.getInstance(par0Str);
+ byte[][] var3 = par1ArrayOfByte;
+ int var4 = par1ArrayOfByte.Length;
+
+ for (int var5 = 0; var5 < var4; ++var5)
+ {
+ byte[] var6 = var3[var5];
+ var2.update(var6);
+ }
+
+ return var2.digest();
+ }
+ private static byte[] func_75885_a(int par0, Key par1Key, byte[] par2ArrayOfByte)
+ {
+ try
+ {
+ return cypherencrypt(par0, par1Key.getAlgorithm(), par1Key).doFinal(par2ArrayOfByte);
+ }
+ catch (IllegalBlockSizeException var4)
+ {
+ var4.printStackTrace();
+ }
+ catch (BadPaddingException var5)
+ {
+ var5.printStackTrace();
+ }
+
+ Console.Error.WriteLine("Cipher data failed!");
+ return null;
+ }
+ private static Cipher cypherencrypt(int par0, String par1Str, Key par2Key)
+ {
+ try
+ {
+ Cipher var3 = Cipher.getInstance(par1Str);
+ var3.init(par0, par2Key);
+ return var3;
+ }
+ catch (InvalidKeyException var4)
+ {
+ var4.printStackTrace();
+ }
+ catch (NoSuchAlgorithmException var5)
+ {
+ var5.printStackTrace();
+ }
+ catch (NoSuchPaddingException var6)
+ {
+ var6.printStackTrace();
+ }
+
+ Console.Error.WriteLine("Cipher creation failed!");
+ return null;
+ }
+
+ public static AesStream SwitchToAesMode(System.IO.Stream stream, Key key)
+ {
+ return new AesStream(stream, key.getEncoded());
+ }
+
+ ///
+ /// An encrypted stream using AES
+ ///
+
+ public class AesStream : System.IO.Stream
+ {
+ CryptoStream enc;
+ CryptoStream dec;
+ public AesStream(System.IO.Stream stream, byte[] key)
+ {
+ BaseStream = stream;
+ enc = new CryptoStream(stream, GenerateAES(key).CreateEncryptor(), CryptoStreamMode.Write);
+ dec = new CryptoStream(stream, GenerateAES(key).CreateDecryptor(), CryptoStreamMode.Read);
+ }
+ public System.IO.Stream BaseStream { get; set; }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return true; }
+ }
+
+ public override void Flush()
+ {
+ BaseStream.Flush();
+ }
+
+ public override long Length
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override int ReadByte()
+ {
+ return dec.ReadByte();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return dec.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void WriteByte(byte b)
+ {
+ enc.WriteByte(b);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ enc.Write(buffer, offset, count);
+ }
+
+ private RijndaelManaged GenerateAES(byte[] key)
+ {
+ RijndaelManaged cipher = new RijndaelManaged();
+ cipher.Mode = CipherMode.CFB;
+ cipher.Padding = PaddingMode.None;
+ cipher.KeySize = 128;
+ cipher.FeedbackSize = 8;
+ cipher.Key = key;
+ cipher.IV = key;
+ return cipher;
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/IKVM.OpenJDK.Core.dll b/MinecraftClient/IKVM.OpenJDK.Core.dll
new file mode 100644
index 00000000..328ab6d4
Binary files /dev/null and b/MinecraftClient/IKVM.OpenJDK.Core.dll differ
diff --git a/MinecraftClient/IKVM.OpenJDK.Security.dll b/MinecraftClient/IKVM.OpenJDK.Security.dll
new file mode 100644
index 00000000..0e87f2de
Binary files /dev/null and b/MinecraftClient/IKVM.OpenJDK.Security.dll differ
diff --git a/MinecraftClient/IKVM.OpenJDK.Util.dll b/MinecraftClient/IKVM.OpenJDK.Util.dll
new file mode 100644
index 00000000..8113cc39
Binary files /dev/null and b/MinecraftClient/IKVM.OpenJDK.Util.dll differ
diff --git a/MinecraftClient/IKVM.Runtime.dll b/MinecraftClient/IKVM.Runtime.dll
new file mode 100644
index 00000000..d69e3f53
Binary files /dev/null and b/MinecraftClient/IKVM.Runtime.dll differ
diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs
new file mode 100644
index 00000000..e31f280e
--- /dev/null
+++ b/MinecraftClient/McTcpClient.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Net.Sockets;
+using System.Threading;
+using System.IO;
+using System.Net;
+
+namespace MinecraftClient
+{
+ ///
+ /// The main client class, used to connect to a Minecraft server.
+ /// It allows message sending and text receiving.
+ ///
+
+ class McTcpClient
+ {
+ public static int AttemptsLeft = 0;
+
+ string host;
+ int port;
+ string username;
+ string text;
+ Thread t_updater;
+ Thread t_sender;
+ TcpClient client;
+ MinecraftCom handler;
+
+ ///
+ /// Starts the main chat client, wich will login to the server using the MinecraftCom class.
+ ///
+ /// The chosen username of a premium Minecraft Account
+ /// A valid sessionID obtained with MinecraftCom.GetLogin()
+ /// The server IP (serveradress or serveradress:port)
+
+ public McTcpClient(string username, string sessionID, string server_port, MinecraftCom handler)
+ {
+ StartClient(username, sessionID, server_port, false, handler, "");
+ }
+
+ ///
+ /// Starts the main chat client in single command sending mode, wich will login to the server using the MinecraftCom class, send the command and close.
+ ///
+ /// The chosen username of a premium Minecraft Account
+ /// A valid sessionID obtained with MinecraftCom.GetLogin()
+ /// The server IP (serveradress or serveradress:port)
+ /// The text or command to send.
+
+ public McTcpClient(string username, string sessionID, string server_port, MinecraftCom handler, string command)
+ {
+ StartClient(username, sessionID, server_port, true, handler, command);
+ }
+
+ ///
+ /// Starts the main chat client, wich will login to the server using the MinecraftCom class.
+ ///
+ /// The chosen username of a premium Minecraft Account
+ /// A valid sessionID obtained with MinecraftCom.GetLogin()
+ /// The server IP (serveradress or serveradress:port)/param>
+ /// If set to true, the client will send a single command and then disconnect from the server
+ /// The text or command to send. Will only be sent if singlecommand is set to true.
+
+ private void StartClient(string user, string sessionID, string server_port, bool singlecommand, MinecraftCom handler, string command)
+ {
+ this.handler = handler;
+ username = user;
+ string[] sip = server_port.Split(':');
+ host = sip[0];
+ if (sip.Length == 1)
+ {
+ port = 25565;
+ }
+ else
+ {
+ try
+ {
+ port = Convert.ToInt32(sip[1]);
+ }
+ catch (FormatException) { port = 25565; }
+ }
+
+ try
+ {
+ Console.WriteLine("Connecting...");
+ client = new TcpClient(host, port);
+ client.ReceiveBufferSize = 1024 * 1024;
+ handler.setClient(client);
+ byte[] token = new byte[1]; string serverID = "";
+ if (handler.Handshake(user, sessionID, ref serverID, ref token, host, port))
+ {
+ Console.WriteLine("Logging in...");
+
+ if (handler.FinalizeLogin())
+ {
+ //Single command sending
+ if (singlecommand)
+ {
+ handler.SendChatMessage(command);
+ Console.Write("Command ");
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.Write(command);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ Console.WriteLine(" sent.");
+ Thread.Sleep(5000);
+ handler.Disconnect("disconnect.quitting");
+ Thread.Sleep(1000);
+ }
+ else
+ {
+ Console.WriteLine("Server was successfuly joined.\nType '/quit' to leave the server.");
+
+ //Command sending thread, allowing user input
+ t_sender = new Thread(new ThreadStart(StartTalk));
+ t_sender.Name = "CommandSender";
+ t_sender.Start();
+
+ //Data receiving thread, allowing text receiving
+ t_updater = new Thread(new ThreadStart(Updater));
+ t_updater.Name = "PacketHandler";
+ t_updater.Start();
+ }
+ }
+ else
+ {
+ Console.WriteLine("Login failed.");
+ if (!singlecommand) { Program.ReadLineReconnect(); }
+ }
+ }
+ else
+ {
+ Console.WriteLine("Invalid session ID.");
+ if (!singlecommand) { Program.ReadLineReconnect(); }
+ }
+ }
+ catch (SocketException)
+ {
+ Console.WriteLine("Failed to connect to this IP.");
+ if (AttemptsLeft > 0)
+ {
+ ChatBot.LogToConsole("Waiting 5 seconds (" + AttemptsLeft + " attempts left)...");
+ Thread.Sleep(5000); AttemptsLeft--; Program.Restart();
+ }
+ else if (!singlecommand){ Console.ReadLine(); }
+ }
+ }
+
+ ///
+ /// Allows the user to send chat messages, commands, and to leave the server.
+ /// Will be automatically called on a separate Thread by StartClient()
+ ///
+
+ private void StartTalk()
+ {
+ try
+ {
+ while (client.Client.Connected)
+ {
+ text = ConsoleIO.ReadLine();
+ if (text == "/quit" || text == "/reco" || text == "/reconnect") { break; }
+ while (text.Length > 0 && text[0] == ' ') { text = text.Substring(1); }
+ if (text != "")
+ {
+ //Message is too long
+ if (text.Length > 100)
+ {
+ if (text[0] == '/')
+ {
+ //Send the first 100 chars of the command
+ text = text.Substring(0, 100);
+ handler.SendChatMessage(text);
+ }
+ else
+ {
+ //Send the message splitted in sereval messages
+ while (text.Length > 100)
+ {
+ handler.SendChatMessage(text.Substring(0, 100));
+ text = text.Substring(100, text.Length - 100);
+ }
+ handler.SendChatMessage(text);
+ }
+ }
+ else handler.SendChatMessage(text);
+ }
+ }
+
+ if (text == "/quit")
+ {
+ ConsoleIO.WriteLine("You have left the server.");
+ Disconnect();
+ }
+
+ else if (text == "/reco" || text == "/reconnect")
+ {
+ ConsoleIO.WriteLine("You have left the server.");
+ Program.Restart();
+ }
+ }
+ catch (IOException) { }
+ }
+
+ ///
+ /// Receive the data (including chat messages) from the server, and keep the connection alive.
+ /// Will be automatically called on a separate Thread by StartClient()
+ ///
+
+ private void Updater()
+ {
+ try
+ {
+ //handler.DebugDump();
+ do
+ {
+ Thread.Sleep(100);
+ } while (handler.Update());
+ }
+ catch (IOException) { }
+ catch (SocketException) { }
+ catch (ObjectDisposedException) { }
+
+ if (!handler.HasBeenKicked)
+ {
+ ConsoleIO.WriteLine("Connection has been lost.");
+ if (!handler.OnConnectionLost() && !Program.ReadLineReconnect()) { t_sender.Abort(); }
+ }
+ else if (Program.ReadLineReconnect()) { t_sender.Abort(); }
+ }
+
+ ///
+ /// Disconnect the client from the server
+ ///
+
+ public void Disconnect()
+ {
+ handler.Disconnect("disconnect.quitting");
+ Thread.Sleep(1000);
+ if (t_updater != null) { t_updater.Abort(); }
+ if (t_sender != null) { t_sender.Abort(); }
+ if (client != null) { client.Close(); }
+ }
+ }
+}
diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
new file mode 100644
index 00000000..b1b033e6
--- /dev/null
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -0,0 +1,127 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {1E2FACE4-F5CA-4323-9641-740C6A551770}
+ Exe
+ Properties
+ MinecraftClient
+ MinecraftClient
+ v4.0
+ Client
+ 512
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ false
+
+
+
+ False
+ .\BouncyCastle.Crypto.dll
+
+
+ False
+ .\IKVM.OpenJDK.Core.dll
+
+
+ False
+ .\IKVM.OpenJDK.Security.dll
+
+
+ False
+ .\IKVM.OpenJDK.Util.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ Microsoft .NET Framework 4 Client Profile %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinecraftClient/MinecraftClient.csproj.user b/MinecraftClient/MinecraftClient.csproj.user
new file mode 100644
index 00000000..1aaa11ad
--- /dev/null
+++ b/MinecraftClient/MinecraftClient.csproj.user
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ publish\
+
+
+
+
+
+ en-US
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinecraftClient/MinecraftCom.cs b/MinecraftClient/MinecraftCom.cs
new file mode 100644
index 00000000..9508f280
--- /dev/null
+++ b/MinecraftClient/MinecraftCom.cs
@@ -0,0 +1,825 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Net;
+using System.Net.Sockets;
+
+namespace MinecraftClient
+{
+ ///
+ /// The class containing all the core functions needed to communicate with a Minecraft server.
+ ///
+
+ public class MinecraftCom
+ {
+ #region Login to Minecraft.net, Obtaining a session ID
+
+ public enum LoginResult { Error, Success, WrongPassword, Blocked, AccountMigrated, NotPremium };
+
+ ///
+ /// Allows to login to a premium Minecraft account, and retrieve the session ID.
+ ///
+ /// Login
+ /// Password
+ /// Will contain the data returned by Minecraft.net, if the login is successful : Version:UpdateTicket:Username:SessionID
+ /// Returns the status of the login (Success, Failure, etc.)
+
+ public static LoginResult GetLogin(string user, string pass, ref string outdata)
+ {
+ try
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ WebClient wClient = new WebClient();
+ Console.WriteLine("https://login.minecraft.net/?user=" + user + "&password=<******>&version=13");
+ string result = wClient.DownloadString("https://login.minecraft.net/?user=" + user + "&password=" + pass + "&version=13");
+ outdata = result;
+ Console.WriteLine(result);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ if (result == "Bad login") { return LoginResult.WrongPassword; }
+ if (result == "User not premium") { return LoginResult.NotPremium; }
+ if (result == "Too many failed logins") { return LoginResult.Blocked; }
+ if (result == "Account migrated, use e-mail as username.") { return LoginResult.AccountMigrated; }
+ else return LoginResult.Success;
+ }
+ catch (WebException) { return LoginResult.Error; }
+ }
+
+ #endregion
+
+ #region Keep-Alive for a Minecraft.net session, should be called every 5 minutes (currently unused)
+
+ ///
+ /// The session ID will expire within 5 minutes unless this function is called every 5 minutes
+ ///
+ /// Username
+ /// Session ID to keep alive
+
+ public static void SessionKeepAlive(string user, string sessionID)
+ {
+ new WebClient().DownloadString("https://login.minecraft.net/session?name=" + user + "&session=" + sessionID);
+ }
+
+ #endregion
+
+ #region Session checking when joining a server in online mode
+
+ ///
+ /// This method allows to join an online-mode server.
+ /// It Should be called between the handshake and the login attempt.
+ ///
+ /// Username
+ /// A valid session ID for this username
+ /// Hash returned by the server during the handshake
+ /// Returns true if the check was successful
+
+ public static bool SessionCheck(string user, string sessionID, string hash)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ WebClient client = new WebClient();
+ Console.Write("http://session.minecraft.net/game/joinserver.jsp?user=" + user + "&sessionId=" + sessionID + "&serverId=" + hash + " ... ");
+ string result = client.DownloadString("http://session.minecraft.net/game/joinserver.jsp?user=" + user + "&sessionId=" + sessionID + "&serverId=" + hash);
+ Console.WriteLine(result);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return (result == "OK");
+ }
+
+ #endregion
+
+ #region Server-side session checking (programmed for testing purposes)
+
+ ///
+ /// Reproduces the username checking done by an online-mode server during the login process.
+ ///
+ /// Username
+ /// Hash sent by the server during the handshake
+ /// Returns true if the user is allowed to join the server
+
+ public static bool ServerSessionCheck(string user, string hash)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ WebClient client = new WebClient();
+ ConsoleIO.WriteLine("http://session.minecraft.net/game/checkserver.jsp?user=" + user + "&serverId=" + hash);
+ string result = client.DownloadString("http://session.minecraft.net/game/checkserver.jsp?user=" + user + "&serverId=" + hash);
+ ConsoleIO.WriteLine(result);
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return (result == "YES");
+ }
+
+ #endregion
+
+ TcpClient c = new TcpClient();
+ Crypto.AesStream s;
+
+ public bool HasBeenKicked { get { return connectionlost; } }
+ bool connectionlost = false;
+ bool encrypted = false;
+ byte protocolversion;
+
+ public bool Update()
+ {
+ for (int i = 0; i < bots.Count; i++) { bots[i].Update(); }
+ if (c.Client == null || !c.Connected) { return false; }
+ byte id = 0;
+
+ try
+ {
+ while (c.Client.Available > 0)
+ {
+ id = readNextByte();
+ ProcessResult result = processPacket(id);
+
+ //Debug : Print packet IDs that are beign processed. Green = OK, Red = Unknown packet
+ //If the client gets out of sync, check the last green packet processing code.
+ //if (result == ProcessResult.OK) { printstring("§a0x" + id.ToString("X"), false); }
+ //else { printstring("§c0x" + id.ToString("X"), false); }
+
+ if (result == ProcessResult.ConnectionLost)
+ {
+ return false;
+ }
+ }
+ }
+ catch (SocketException) { return false; }
+ return true;
+ }
+ public void DebugDump()
+ {
+ byte[] cache = new byte[128000];
+ Receive(cache, 0, 128000, SocketFlags.None);
+ string dump = BitConverter.ToString(cache);
+ System.IO.File.WriteAllText("debug.txt", dump);
+ System.Diagnostics.Process.Start("debug.txt");
+ }
+ public bool OnConnectionLost()
+ {
+ if (!connectionlost)
+ {
+ connectionlost = true;
+ for (int i = 0; i < bots.Count; i++)
+ {
+ if (bots[i].OnDisconnect(ChatBot.DisconnectReason.ConnectionLost, "Connection has been lost."))
+ {
+ return true; //The client is about to restart
+ }
+ }
+ }
+ return false;
+ }
+
+ private enum ProcessResult { OK, ConnectionLost, UnknownPacket }
+ private ProcessResult processPacket(int 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)
+ {
+ //printstring("§8" + message, false); //Debug
+ message = ChatParser.ParseText(message);
+ printstring(message, false);
+ }
+ else printstring(message, false);
+ for (int i = 0; i < bots.Count; i++) { bots[i].GetText(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 (protocolversion >= 72) { readData(8); } else readData(5); break;
+ case 0xC9: readNextString(); readData(3); break;
+ case 0xCA: if (protocolversion >= 72) { readData(9); } else readData(3); break;
+ case 0xCB: readNextString(); 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();
+ ConsoleIO.WriteLine("Disconnected by Server :"); printstring(reason, true); connectionlost = true;
+ for (int i = 0; i < bots.Count; i++) { bots[i].OnDisconnect(ChatBot.DisconnectReason.InGameKick, reason); } return ProcessResult.ConnectionLost;
+ default: return ProcessResult.UnknownPacket; //unknown packet!
+ }
+ return ProcessResult.OK; //packet has been successfully skipped
+ }
+ 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()
+ {
+ short lenght = readNextShort();
+ if (lenght > 0)
+ {
+ byte[] cache = new byte[lenght * 2];
+ Receive(cache, 0, lenght * 2, SocketFlags.None);
+ string result = ByteArrayToString(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 lenght 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 setcolor(char c)
+ {
+ switch (c)
+ {
+ case '0': Console.ForegroundColor = ConsoleColor.Gray; break; //Should be Black but Black is non-readable on a black background
+ case '1': Console.ForegroundColor = ConsoleColor.DarkBlue; break;
+ case '2': Console.ForegroundColor = ConsoleColor.DarkGreen; break;
+ case '3': Console.ForegroundColor = ConsoleColor.DarkCyan; break;
+ case '4': Console.ForegroundColor = ConsoleColor.DarkRed; break;
+ case '5': Console.ForegroundColor = ConsoleColor.DarkMagenta; break;
+ case '6': Console.ForegroundColor = ConsoleColor.DarkYellow; break;
+ case '7': Console.ForegroundColor = ConsoleColor.Gray; break;
+ case '8': Console.ForegroundColor = ConsoleColor.DarkGray; break;
+ case '9': Console.ForegroundColor = ConsoleColor.Blue; break;
+ case 'a': Console.ForegroundColor = ConsoleColor.Green; break;
+ case 'b': Console.ForegroundColor = ConsoleColor.Cyan; break;
+ case 'c': Console.ForegroundColor = ConsoleColor.Red; break;
+ case 'd': Console.ForegroundColor = ConsoleColor.Magenta; break;
+ case 'e': Console.ForegroundColor = ConsoleColor.Yellow; break;
+ case 'f': Console.ForegroundColor = ConsoleColor.White; break;
+ case 'r': Console.ForegroundColor = ConsoleColor.White; break;
+ }
+ }
+ private void printstring(string str, bool acceptnewlines)
+ {
+ if (str != "")
+ {
+ char prev = ' ';
+ foreach (char c in str)
+ {
+ if (c == '§')
+ {
+ prev = c;
+ continue;
+ }
+ else if (prev == '§')
+ {
+ setcolor(c);
+ prev = c;
+ }
+ else
+ {
+ if (c == '\n' && !acceptnewlines) { continue; }
+ else ConsoleIO.Write(c);
+ }
+ }
+ ConsoleIO.Write('\n');
+ }
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
+ private string ReverseString(string a)
+ {
+ char[] tmp = a.ToCharArray();
+ Array.Reverse(tmp);
+ return new string(tmp);
+ }
+ private string ByteArrayToString(byte[] ba)
+ {
+ string conv = "";
+ for (int i = 1; i < ba.Length; i += 2)
+ {
+ conv += (char)ba[i];
+ }
+ return conv;
+ }
+
+ public void setVersion(byte ver) { protocolversion = ver; }
+ public void setClient(TcpClient n) { c = n; }
+ private void setEncryptedClient(Crypto.AesStream n) { s = n; encrypted = true; }
+ private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
+ {
+ while (c.Client.Available < start + offset) { }
+ if (encrypted)
+ {
+ s.Read(buffer, start, offset);
+ }
+ else c.Client.Receive(buffer, start, offset, f);
+ }
+ private void Send(byte[] buffer)
+ {
+ if (encrypted)
+ {
+ s.Write(buffer, 0, buffer.Length);
+ }
+ else c.Client.Send(buffer);
+ }
+
+ public static bool GetServerInfo(string serverIP, ref byte 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; }
+ }
+
+ TcpClient tcp = new TcpClient(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)
+ {
+ MinecraftCom ComTmp = new MinecraftCom();
+ ComTmp.setClient(tcp);
+ string result = ComTmp.readNextString();
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ //Console.WriteLine(result.Replace((char)0x00, ' '));
+ if (result.Length > 2 && result[0] == '§' && result[1] == '1')
+ {
+ string[] tmp = result.Split((char)0x00);
+ protocolversion = (byte)Int16.Parse(tmp[1]);
+ version = tmp[2];
+ }
+ else
+ {
+ protocolversion = (byte)39;
+ version = "B1.8.1 - 1.3.2";
+ }
+ Console.WriteLine("Server version : MC " + version + " (protocol v" + protocolversion + ").");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return true;
+ }
+ else
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine("Unexpected answer from the server (is that a Minecraft server ?)");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return false;
+ }
+ }
+ catch
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine("An error occured while attempting to connect to this IP.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return false;
+ }
+ }
+ public bool Handshake(string username, string sessionID, ref string serverID, ref byte[] token, string host, int port)
+ {
+ username = ReverseString(username);
+ //array
+ byte[] data = new byte[10 + (username.Length * 2) + (host.Length * 2)];
+
+ //packet id
+ data[0] = (byte)2;
+
+ //Protocol Version - Minecraft 1.3.1 & 1.3.2
+ //data[1] = (byte)39;
+
+ //Protocol Version - Minecraft 1.4.2
+ //data[1] = (byte)47;
+
+ //Protocol Version - Minecraft 1.4.4
+ //data[1] = (byte)49;
+
+ //Protocol Version - Minecraft 1.4.6 & 1.4.7
+ //data[1] = (byte)51;
+
+ //Protocol Version - Minecraft 1.5.0
+ //data[1] = (byte)60;
+
+ //Protocol Version - Custom
+ data[1] = protocolversion;
+
+ //short len
+ byte[] sh = new byte[2];
+ sh = BitConverter.GetBytes((short)username.Length);
+ Array.Reverse(sh);
+ sh.CopyTo(data, 2);
+
+ //username
+ byte[] name = Encoding.Unicode.GetBytes(username);
+ Array.Reverse(name);
+ name.CopyTo(data, 4);
+
+ //short len
+ sh = new byte[2];
+ sh = BitConverter.GetBytes((short)host.Length);
+ Array.Reverse(sh);
+ sh.CopyTo(data, 4 + (username.Length * 2));
+
+ //host
+ byte[] bhost = Encoding.Unicode.GetBytes(username);
+ Array.Reverse(bhost);
+ bhost.CopyTo(data, 6 + (username.Length * 2));
+
+ //port
+ sh = new byte[4];
+ 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)
+ {
+ serverID = readNextString();
+ byte[] Serverkey_RAW = readNextByteArray();
+ token = readNextByteArray();
+
+ if (serverID == "-")
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine("Server is in offline mode.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return true; //No need to check session or start encryption
+ }
+ else
+ {
+ var PublicServerkey = Crypto.GenerateRSAPublicKey(Serverkey_RAW);
+ var SecretKey = Crypto.GenerateAESPrivateKey();
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine("Handshake sussessful. (Server ID: " + serverID + ')');
+ Console.ForegroundColor = ConsoleColor.Gray;
+ return StartEncryption(ReverseString(username), sessionID, token, serverID, PublicServerkey, SecretKey);
+ }
+ }
+ else return false;
+ }
+ public bool StartEncryption(string username, string sessionID, byte[] token, string serverIDhash, java.security.PublicKey serverKey, javax.crypto.SecretKey secretKey)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleIO.WriteLine("Crypto keys & hash generated.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+
+ if (serverIDhash != "-")
+ {
+ Console.WriteLine("Checking Session...");
+ if (!SessionCheck(username, sessionID, new java.math.BigInteger(Crypto.getServerHash(serverIDhash, serverKey, secretKey)).toString(16)))
+ {
+ return false;
+ }
+ }
+
+ //Encrypt the data
+ byte[] key_enc = Crypto.Encrypt(serverKey, secretKey.getEncoded());
+ byte[] token_enc = Crypto.Encrypt(serverKey, token);
+ 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);
+ setEncryptedClient(Crypto.SwitchToAesMode(c.GetStream(), secretKey));
+ return true;
+ }
+ else return false;
+ }
+ public bool FinalizeLogin()
+ {
+ //Creating byte array
+ byte[] data = new byte[2];
+ data[0] = 0xCD;
+ data[1] = 0;
+
+ Send(data);
+ 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);
+ return true; //The Server accepted the request
+ }
+ else if (pid[0] == (byte)0xFF)
+ {
+ string reason = readNextString();
+ Console.WriteLine("Login rejected by Server :"); printstring(reason, true);
+ for (int i = 0; i < bots.Count; i++) { bots[i].OnDisconnect(ChatBot.DisconnectReason.LoginRejected, reason); }
+ return false;
+ }
+ }
+ }
+ catch
+ {
+ //Connection failed
+ return false;
+ }
+ }
+ catch
+ {
+ //Network error
+ Console.WriteLine("Connection Lost.");
+ return false;
+ }
+ return false; //Login was unsuccessful (received a kick...)
+ }
+ public bool SendChatMessage(string message)
+ {
+ try
+ {
+ message = ReverseString(message);
+ byte[] chat = new byte[3 + (message.Length * 2)];
+ chat[0] = (byte)3;
+
+ byte[] msglen = new byte[2];
+ msglen = BitConverter.GetBytes((short)message.Length);
+ Array.Reverse(msglen);
+ msglen.CopyTo(chat, 1);
+
+ byte[] msg = new byte[message.Length * 2];
+ msg = Encoding.Unicode.GetBytes(message);
+ Array.Reverse(msg);
+ msg.CopyTo(chat, 3);
+
+ Send(chat);
+ return true;
+ }
+ catch (SocketException) { return false; }
+ }
+ public void Disconnect(string message)
+ {
+ try
+ {
+ message = ReverseString(message);
+ byte[] reason = new byte[3 + (message.Length * 2)];
+ reason[0] = (byte)0xff;
+
+ byte[] msglen = new byte[2];
+ msglen = BitConverter.GetBytes((short)message.Length);
+ Array.Reverse(msglen);
+ msglen.CopyTo(reason, 1);
+
+ byte[] msg = new byte[message.Length * 2];
+ msg = Encoding.Unicode.GetBytes(message);
+ Array.Reverse(msg);
+ msg.CopyTo(reason, 3);
+
+ Send(reason);
+ }
+ catch (SocketException) { }
+ catch (System.IO.IOException) { }
+ }
+
+ private List bots = new List();
+ public void BotLoad(ChatBot b) { b.SetHandler(this); bots.Add(b); b.Initialize(); }
+ public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); }
+ public void BotClear() { bots.Clear(); }
+ }
+}
diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs
new file mode 100644
index 00000000..73431430
--- /dev/null
+++ b/MinecraftClient/Program.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient
+{
+ ///
+ /// Minecraft Console Client by ORelio (c) 2012-2013.
+ /// Allows to connect to any Minecraft server, send and receive text, automated scripts.
+ /// This source code is released under the CDDL 1.0 License.
+ ///
+
+ class Program
+ {
+ private static McTcpClient Client;
+ private static string loginusername = "";
+ private static string user = "";
+ private static string pass = "";
+ private static string ip = "";
+ private static string command = "";
+ private static string[] startupargs;
+
+ ///
+ /// The main entry point of Minecraft Console Client
+ ///
+
+ static void Main(string[] args)
+ {
+ Console.WriteLine("Console Client for MC 1.4.6 to 1.6.2 - v1.5.2 - By ORelio (or3L1o@live.fr)");
+
+ //Processing Command-line arguments
+
+ if (args.Length >= 1)
+ {
+ user = args[0];
+ if (args.Length >= 2)
+ {
+ pass = args[1];
+ if (args.Length >= 3)
+ {
+ ip = args[2];
+ if (args.Length >= 4)
+ {
+ command = args[3];
+ }
+ }
+ }
+ }
+
+ //Asking the user to type in missing data such as Username and Password
+
+ if (user == "") {
+ Console.Write("Username : ");
+ user = Console.ReadLine();
+ }
+ if (pass == "") {
+ Console.Write("Password : ");
+ pass = Console.ReadLine();
+
+ //Hide the password
+ Console.CursorTop--;
+ Console.Write("Password : <******>");
+ for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); }
+ }
+
+ //Save the arguments
+ startupargs = args;
+ loginusername = user;
+
+ //Start the Client
+ InitializeClient();
+ }
+
+ ///
+ /// Start a new Client
+ ///
+
+ private static void InitializeClient()
+ {
+ MinecraftCom.LoginResult result;
+ string logindata = "";
+
+ if (pass == "-")
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine("You chose to run in offline mode.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ result = MinecraftCom.LoginResult.Success;
+ logindata = "0:deprecated:" + user + ":0";
+ }
+ else
+ {
+ Console.WriteLine("Connecting to Minecraft.net...");
+ result = MinecraftCom.GetLogin(loginusername, pass, ref logindata);
+ }
+ if (result == MinecraftCom.LoginResult.Success)
+ {
+ user = logindata.Split(':')[2];
+ string sessionID = logindata.Split(':')[3];
+ Console.WriteLine("Success. (session ID: " + sessionID + ')');
+ if (ip == "")
+ {
+ Console.Write("Server IP : ");
+ ip = Console.ReadLine();
+ }
+
+ //Get server version
+ Console.WriteLine("Retrieving Server Info...");
+ byte protocolversion = 0; string version = "";
+ if (MinecraftCom.GetServerInfo(ip, ref protocolversion, ref version))
+ {
+ //Supported protocol version ?
+ int[] supportedVersions = { 51, 60, 61, 72, 73, 74 };
+ if (Array.IndexOf(supportedVersions, protocolversion) > -1)
+ {
+ //Minecraft 1.6+ ? Load translations
+ if (protocolversion >= 72) { ChatParser.InitTranslations(); }
+
+ //Will handle the connection for this client
+ Console.WriteLine("Version is supported.");
+ MinecraftCom handler = new MinecraftCom();
+ handler.setVersion(protocolversion);
+
+ //Load & initialize bots if needed
+ foreach (string arg in startupargs)
+ {
+ if (arg.Length > 4 && arg.Substring(0, 4).ToLower() == "bot:")
+ {
+ int param;
+ string[] botargs = arg.ToLower().Split(':');
+ switch (botargs[1])
+ {
+ case "antiafk":
+ #region Arguments for the AntiAFK bot
+ param = 600;
+ if (botargs.Length > 2)
+ {
+ try { param = Convert.ToInt32(botargs[2]); }
+ catch (FormatException) { }
+ }
+ #endregion
+ handler.BotLoad(new Bots.AntiAFK(param)); break;
+
+ case "pendu": handler.BotLoad(new Bots.Pendu(false)); break;
+ case "hangman": handler.BotLoad(new Bots.Pendu(true)); break;
+ case "alerts": handler.BotLoad(new Bots.Alerts()); break;
+
+ case "log":
+ #region Arguments for the ChatLog bot
+ bool datetime = true;
+ string file = "chat-" + ip + ".log";
+ Bots.ChatLog.MessageFilter filter = Bots.ChatLog.MessageFilter.AllMessages;
+ if (botargs.Length > 2)
+ {
+ datetime = (botargs[2] != "0");
+ if (botargs.Length > 3)
+ {
+ switch (botargs[3])
+ {
+ case "all": filter = Bots.ChatLog.MessageFilter.AllText; break;
+ case "messages": filter = Bots.ChatLog.MessageFilter.AllMessages; break;
+ case "chat": filter = Bots.ChatLog.MessageFilter.OnlyChat; break;
+ case "private": filter = Bots.ChatLog.MessageFilter.OnlyWhispers; break;
+ }
+ if (botargs.Length > 4 && botargs[4] != "") { file = botargs[4]; }
+ }
+ }
+ #endregion
+ handler.BotLoad(new Bots.ChatLog(file, filter, datetime)); break;
+
+ case "logplayerlist":
+ #region Arguments for the PlayerListLogger bot
+ param = 600;
+ if (botargs.Length > 2)
+ {
+ try { param = Convert.ToInt32(botargs[2]); }
+ catch (FormatException) { }
+ }
+ #endregion
+ handler.BotLoad(new Bots.PlayerListLogger(param, "connected-" + ip + ".log")); break;
+
+ case "autorelog":
+ #region Arguments for the AutoRelog bot
+ int delay = 10;
+ if (botargs.Length > 2)
+ {
+ try { delay = Convert.ToInt32(botargs[2]); }
+ catch (FormatException) { }
+ }
+ int retries = 3;
+ if (botargs.Length > 3)
+ {
+ try { retries = Convert.ToInt32(botargs[3]); }
+ catch (FormatException) { }
+ }
+ #endregion
+ handler.BotLoad(new Bots.AutoRelog(delay, retries)); break;
+
+ case "xauth":
+ if (botargs.Length > 2) { handler.BotLoad(new Bots.xAuth(botargs[2])); } break;
+ }
+
+ command = "";
+ }
+ }
+
+ //Start the main TCP client
+ if (command != "")
+ {
+ Client = new McTcpClient(user, sessionID, ip, handler, command);
+ }
+ else Client = new McTcpClient(user, sessionID, ip, handler);
+ }
+ else
+ {
+ Console.WriteLine("Cannot connect to the server : This version is not supported !");
+ ReadLineReconnect();
+ }
+ }
+ else
+ {
+ Console.WriteLine("Failed to ping this IP.");
+ ReadLineReconnect();
+ }
+ }
+ else
+ {
+ Console.ForegroundColor = ConsoleColor.Gray;
+ Console.Write("Connection failed : ");
+ switch (result)
+ {
+ case MinecraftCom.LoginResult.AccountMigrated: Console.WriteLine("Account migrated, use e-mail as username."); break;
+ case MinecraftCom.LoginResult.Blocked: Console.WriteLine("Too many failed logins. Please try again later."); break;
+ case MinecraftCom.LoginResult.WrongPassword: Console.WriteLine("Incorrect password."); break;
+ case MinecraftCom.LoginResult.NotPremium: Console.WriteLine("User not premium."); break;
+ case MinecraftCom.LoginResult.Error: Console.WriteLine("Network error."); break;
+ }
+ while (Console.KeyAvailable) { Console.ReadKey(false); }
+ if (command == "") { ReadLineReconnect(); }
+ }
+ }
+
+ ///
+ /// Disconnect the current client from the server and restart it
+ ///
+
+ public static void Restart()
+ {
+ new System.Threading.Thread(new System.Threading.ThreadStart(t_restart)).Start();
+ }
+
+ ///
+ /// Pause the program, usually when an error or a kick occured, letting the user press Enter to quit OR type /reconnect
+ ///
+ /// Return True if the user typed "/reconnect"
+
+ public static bool ReadLineReconnect()
+ {
+ string text = Console.ReadLine();
+ if (text == "reco" || text == "reconnect" || text == "/reco" || text == "/reconnect")
+ {
+ Program.Restart();
+ return true;
+ }
+ else return false;
+ }
+
+ ///
+ /// Private thread for restarting the program. Called through Restart()
+ ///
+
+ private static void t_restart()
+ {
+ if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
+ Console.WriteLine("Restarting Minecraft Console Client...");
+ InitializeClient();
+ }
+ }
+}
diff --git a/MinecraftClient/Properties/AssemblyInfo.cs b/MinecraftClient/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..bc2d0307
--- /dev/null
+++ b/MinecraftClient/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Minecraft Console Client")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MinecraftClient")]
+[assembly: AssemblyCopyright("Copyright © ORelio 2012-2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("78af6200-1f48-4daa-b473-109a9728b61f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.3")]
+[assembly: AssemblyFileVersion("1.3")]