Merge pull request #132 from ORelio/Indev

Merge changes for v1.9.0 BETA
This commit is contained in:
ORelio 2016-03-12 19:30:44 +01:00
commit 7ad0ae58e4
72 changed files with 8346 additions and 2148 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/MinecraftClientGUI.v11.suo
/MinecraftClientGUI.suo
/MinecraftClient.userprefs
/.vs/

View file

@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Threading;
namespace MinecraftClient
{
/// <summary>
/// C# Script runner - Compile on-the-fly and run C# scripts
/// </summary>
class CSharpRunner
{
private static readonly Dictionary<ulong, Assembly> CompileCache = new Dictionary<ulong, Assembly>();
/// <summary>
/// Run the specified C# script file
/// </summary>
/// <param name="apiHandler">ChatBot handler for accessing ChatBot API</param>
/// <param name="tickHandler">Tick handler for waiting after some API calls</param>
/// <param name="lines">Lines of the script file to run</param>
/// <param name="args">Arguments to pass to the script</param>
/// <param name="run">Set to false to compile and cache the script without launching it</param>
/// <exception cref="CSharpException">Thrown if an error occured</exception>
/// <returns>Result of the execution, returned by the script</returns>
public static object Run(ChatBot apiHandler, ManualResetEvent tickHandler, string[] lines, string[] args, bool run = true)
{
//Script compatibility check for handling future versions differently
if (lines.Length < 1 || lines[0] != "//MCCScript 1.0")
throw new CSharpException(CSErrorType.InvalidScript,
new InvalidDataException("The provided script does not have a valid MCCScript header"));
//Script hash for determining if it was previously compiled
ulong scriptHash = QuickHash(lines);
Assembly assembly = null;
//No need to compile two scripts at the same time
lock (CompileCache)
{
///Process and compile script only if not already compiled
if (!Settings.CacheScripts || !CompileCache.ContainsKey(scriptHash))
{
//Process different sections of the script file
bool scriptMain = true;
List<string> script = new List<string>();
List<string> extensions = new List<string>();
foreach (string line in lines)
{
if (line.StartsWith("//MCCScript"))
{
if (line.EndsWith("Extensions"))
scriptMain = false;
}
else if (scriptMain)
script.Add(line);
else extensions.Add(line);
}
//Add return statement if missing
if (script.All(line => !line.StartsWith("return ") && !line.Contains(" return ")))
script.Add("return null;");
//Generate a class from the given script
string code = String.Join("\n", new string[]
{
"using System;",
"using System.Collections.Generic;",
"using System.Text.RegularExpressions;",
"using System.Linq;",
"using System.Text;",
"using System.IO;",
"using System.Threading;",
"using MinecraftClient;",
"using MinecraftClient.Mapping;",
"namespace ScriptLoader {",
"public class Script {",
"public CSharpAPI MCC;",
"public object __run(CSharpAPI __apiHandler, string[] args) {",
"this.MCC = __apiHandler;",
String.Join("\n", script),
"}",
String.Join("\n", extensions),
"}}",
});
//Compile the C# class in memory using all the currently loaded assemblies
CSharpCodeProvider compiler = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies
.AddRange(AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => !a.IsDynamic)
.Select(a => a.Location).ToArray());
parameters.CompilerOptions = "/t:library";
parameters.GenerateInMemory = true;
CompilerResults result = compiler.CompileAssemblyFromSource(parameters, code);
//Process compile warnings and errors
if (result.Errors.Count > 0)
throw new CSharpException(CSErrorType.LoadError,
new InvalidOperationException(result.Errors[0].ErrorText));
//Retrieve compiled assembly
assembly = result.CompiledAssembly;
if (Settings.CacheScripts)
CompileCache[scriptHash] = result.CompiledAssembly;
}
else if (Settings.CacheScripts)
assembly = CompileCache[scriptHash];
}
//Run the compiled assembly with exception handling
if (run)
{
try
{
object compiledScript
= CompileCache[scriptHash].CreateInstance("ScriptLoader.Script");
return
compiledScript
.GetType()
.GetMethod("__run")
.Invoke(compiledScript,
new object[] { new CSharpAPI(apiHandler, tickHandler), args });
}
catch (Exception e) { throw new CSharpException(CSErrorType.RuntimeError, e); }
}
else return null;
}
/// <summary>
/// Quickly calculate a hash for the given script
/// </summary>
/// <param name="lines">script lines</param>
/// <returns>Quick hash as unsigned long</returns>
private static ulong QuickHash(string[] lines)
{
ulong hashedValue = 3074457345618258791ul;
for (int i = 0; i < lines.Length; i++)
{
for (int j = 0; j < lines[i].Length; j++)
{
hashedValue += lines[i][j];
hashedValue *= 3074457345618258799ul;
}
hashedValue += '\n';
hashedValue *= 3074457345618258799ul;
}
return hashedValue;
}
}
/// <summary>
/// Describe a C# script error type
/// </summary>
public enum CSErrorType { FileReadError, InvalidScript, LoadError, RuntimeError };
/// <summary>
/// Describe a C# script error with associated error type
/// </summary>
public class CSharpException : Exception
{
private CSErrorType _type;
public CSErrorType ExceptionType { get { return _type; } }
public override string Message { get { return InnerException.Message; } }
public override string ToString() { return InnerException.ToString(); }
public CSharpException(CSErrorType type, Exception inner)
: base(inner != null ? inner.Message : "", inner)
{
_type = type;
}
}
/// <summary>
/// Represents the C# API object accessible from C# Scripts
/// </summary>
public class CSharpAPI : ChatBot
{
/// <summary>
/// Thread blocking utility for stopping execution when making a ChatBot API call
/// </summary>
private ManualResetEvent tickHandler;
/// <summary>
/// Create a new C# API Wrapper
/// </summary>
/// <param name="apiHandler">ChatBot API Handler</param>
/// <param name="tickHandler">ChatBot tick handler</param>
public CSharpAPI(ChatBot apiHandler, ManualResetEvent tickHandler)
{
SetMaster(apiHandler);
this.tickHandler = tickHandler;
}
/* == Wrappers for ChatBot API with public visibility and call limit to one per tick for safety == */
/// <summary>
/// Write some text in the console. Nothing will be sent to the server.
/// </summary>
/// <param name="text">Log text to write</param>
new public void LogToConsole(object text)
{
base.LogToConsole(text);
}
/// <summary>
/// Send text to the server. Can be anything such as chat messages or commands
/// </summary>
/// <param name="text">Text to send to the server</param>
/// <returns>True if the text was sent with no error</returns>
public bool SendText(object text)
{
bool result = base.SendText(text is string ? (string)text : text.ToString());
tickHandler.WaitOne();
Thread.Sleep(1000);
return result;
}
/// <summary>
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
/// </summary>
/// <param name="command">The command to process</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
new public bool PerformInternalCommand(string command)
{
bool result = base.PerformInternalCommand(command);
tickHandler.WaitOne();
return result;
}
/// <summary>
/// Disconnect from the server and restart the program
/// It will unload and reload all the bots and then reconnect to the server
/// </summary>
/// <param name="extraAttempts">If connection fails, the client will make X extra attempts</param>
new public void ReconnectToTheServer(int extraAttempts = -999999)
{
if (extraAttempts == -999999)
base.ReconnectToTheServer();
else base.ReconnectToTheServer(extraAttempts);
tickHandler.WaitOne();
}
/// <summary>
/// Disconnect from the server and exit the program
/// </summary>
new public void DisconnectAndExit()
{
base.DisconnectAndExit();
tickHandler.WaitOne();
}
/// <summary>
/// Load the provided ChatBot object
/// </summary>
/// <param name="bot">Bot to load</param>
new public void LoadBot(ChatBot bot)
{
base.LoadBot(bot);
tickHandler.WaitOne();
}
/* == Additional Methods useful for Script API == */
/// <summary>
/// Get a global variable by name
/// </summary>
/// <param name="varName">Name of the variable</param>
/// <returns>Value of the variable or null if no variable</returns>
public object GetVar(string varName)
{
return Settings.GetVar(varName);
}
/// <summary>
/// Get a global variable by name, as a string
/// </summary>
/// <param name="varName">Name of the variable</param>
/// <returns>Value of the variable as string, or null if no variable</returns>
public string GetVarAsString(string varName)
{
object val = GetVar(varName);
if (val != null)
return val.ToString();
return null;
}
/// <summary>
/// Get a global variable by name, as an integer
/// </summary>
/// <param name="varName">Name of the variable</param>
/// <returns>Value of the variable as int, or 0 if no variable or not a number</returns>
public int GetVarAsInt(string varName)
{
if (GetVar(varName) is int)
return (int)GetVar(varName);
int result;
if (int.TryParse(GetVarAsString(varName), out result))
return result;
return 0;
}
/// <summary>
/// Get a global variable by name, as a boolean
/// </summary>
/// <param name="varName">Name of the variable</param>
/// <returns>Value of the variable as bool, or false if no variable or not a boolean</returns>
public bool GetVarAsBool(string varName)
{
if (GetVar(varName) is bool)
return (bool)GetVar(varName);
bool result;
if (bool.TryParse(GetVarAsString(varName), out result))
return result;
return false;
}
/// <summary>
/// Set a global variable for further use in any other script
/// </summary>
/// <param name="varName">Name of the variable</param>
/// <param name="varValue">Value of the variable</param>
public bool SetVar(string varName, object varValue)
{
return Settings.SetVar(varName, varValue);
}
/// <summary>
/// Load login/password using an account alias and optionally reconnect to the server
/// </summary>
/// <param name="accountAlias">Account alias</param>
/// <param name="andReconnect">Set to true to reconnecto to the server afterwards</param>
/// <returns>True if the account was found and loaded</returns>
public bool SetAccount(string accountAlias, bool andReconnect = false)
{
bool result = Settings.SetAccount(accountAlias);
if (result && andReconnect)
ReconnectToTheServer();
return result;
}
/// <summary>
/// Load new server information and optionally reconnect to the server
/// </summary>
/// <param name="server">"serverip:port" couple or server alias</param>
/// <returns>True if the server IP was valid and loaded, false otherwise</returns>
public bool SetServer(string server, bool andReconnect = false)
{
bool result = Settings.SetServerIP(server);
if (result && andReconnect)
ReconnectToTheServer();
return result;
}
/// <summary>
/// Synchronously call another script and retrieve the result
/// </summary>
/// <param name="script">Script to call</param>
/// <param name="args">Arguments to pass to the script</param>
/// <returns>An object returned by the script, or null</returns>
public object CallScript(string script, string[] args)
{
string[] lines = null;
ChatBots.Script.LookForScript(ref script);
try { lines = File.ReadAllLines(script); }
catch (Exception e) { throw new CSharpException(CSErrorType.FileReadError, e); }
return CSharpRunner.Run(this, tickHandler, lines, args);
}
}
}

View file

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Text.RegularExpressions;
namespace MinecraftClient
{
@ -14,12 +16,12 @@ namespace MinecraftClient
/// Once your bot is created, read the explanations below to start using it in the MinecraftClient app.
///
/// Pieces of code to add in other parts of the program for your bot. Line numbers are approximative.
/// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); }
/// Settings.cs:73 | public static bool YourBot_Enabled = false;
/// Settings.cs:74 | private enum ParseMode { /* [...] */, YourBot };
/// Settings.cs:106 | case "yourbot": pMode = ParseMode.YourBot; break;
/// Settings.cs:197 | case ParseMode.YourBot: switch (argName.ToLower()) { case "enabled": YourBot_Enabled = str2bool(argValue); break; } break;
/// Settings.cs:267 | + "[YourBot]\r\n" + "enabled=false\r\n"
/// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); }
/// Here your are. Now you will have a setting in MinecraftClient.ini for enabling your brand new bot.
/// Delete MinecraftClient.ini to re-generate it or add the lines [YourBot] and enabled=true to the existing one.
///
@ -32,9 +34,40 @@ namespace MinecraftClient
{
public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost };
//Will be automatically set on bot loading, don't worry about this
public void SetHandler(McTcpClient handler) { this.handler = handler; }
private McTcpClient handler;
//Handler will be automatically set on bot loading, don't worry about this
public void SetHandler(McTcpClient handler) { this._handler = handler; }
protected void SetMaster(ChatBot master) { this.master = master; }
protected void LoadBot(ChatBot bot) { Handler.BotUnLoad(bot); Handler.BotLoad(bot); }
private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } }
private McTcpClient _handler = null;
private ChatBot master = null;
private List<string> registeredPluginChannels = new List<String>();
private Queue<string> chatQueue = new Queue<string>();
private DateTime lastMessageSentTime = DateTime.MinValue;
private bool CanSendTextNow
{
get
{
return DateTime.Now > lastMessageSentTime + Settings.botMessageDelay;
}
}
/// <summary>
/// Processes the current chat message queue, displaying a message after enough time passes.
/// </summary>
internal void ProcessQueuedText()
{
if (chatQueue.Count > 0)
{
if (CanSendTextNow)
{
string text = chatQueue.Dequeue();
LogToConsole("Sending '" + text + "'");
lastMessageSentTime = DateTime.Now;
Handler.SendText(text);
}
}
}
/* ================================================== */
/* Main methods to override for creating your bot */
@ -42,10 +75,22 @@ namespace MinecraftClient
/// <summary>
/// Anything you want to initialize your bot, will be called on load by MinecraftCom
///
/// NOTE: Chat messages cannot be sent at this point in the login process. If you want to send
/// a message when the bot is loaded, use AfterGameJoined.
/// </summary>
public virtual void Initialize() { }
/// <summary>
/// Called after the server has been joined successfully and chat messages are able to be sent.
///
/// NOTE: This is not always right after joining the server - if the bot was loaded after logging
/// in this is still called.
/// </summary>
public virtual void AfterGameJoined() { }
/// <summary>
/// Will be called every ~100ms (10fps) if loaded in MinecraftCom
/// </summary>
@ -68,6 +113,17 @@ namespace MinecraftClient
public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; }
/// <summary>
/// Called when a plugin channel message is received.
/// The given channel must have previously been registered with RegisterPluginChannel.
/// This can be used to communicate with server mods or plugins. See wiki.vg for more
/// information about plugin channels: http://wiki.vg/Plugin_channel
/// </summary>
/// <param name="channel">The name of the channel</param>
/// <param name="data">The payload for the message</param>
public virtual void OnPluginMessage(string channel, byte[] data) { }
/* =================================================================== */
/* ToolBox - Methods below might be useful while creating your bot. */
/* You should not need to interact with other classes of the program. */
@ -78,12 +134,25 @@ namespace MinecraftClient
/// Send text to the server. Can be anything such as chat messages or commands
/// </summary>
/// <param name="text">Text to send to the server</param>
/// <param name="sendImmediately">Whether the message should be sent immediately rather than being queued to avoid chat spam</param>
/// <returns>True if the text was sent with no error</returns>
protected bool SendText(string text)
protected bool SendText(string text, bool sendImmediately = false)
{
if (Settings.botMessageDelay.TotalSeconds > 0 && !sendImmediately)
{
if (!CanSendTextNow)
{
chatQueue.Enqueue(text);
// TODO: We don't know whether there was an error at this point, so we assume there isn't.
// Might not be the best idea.
return true;
}
}
LogToConsole("Sending '" + text + "'");
return handler.SendText(text);
lastMessageSentTime = DateTime.Now;
return Handler.SendText(text);
}
/// <summary>
@ -92,10 +161,10 @@ namespace MinecraftClient
/// <param name="command">The command to process</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
protected bool performInternalCommand(string command)
protected bool PerformInternalCommand(string command)
{
string temp = "";
return handler.performInternalCommand(command, ref temp);
return Handler.PerformInternalCommand(command, ref temp);
}
/// <summary>
@ -105,16 +174,16 @@ namespace MinecraftClient
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
protected bool performInternalCommand(string command, ref string response_msg)
protected bool PerformInternalCommand(string command, ref string response_msg)
{
return handler.performInternalCommand(command, ref response_msg);
return Handler.PerformInternalCommand(command, ref response_msg);
}
/// <summary>
/// Remove color codes ("§c") from a text message received from the server
/// </summary>
protected static string getVerbatim(string text)
protected static string GetVerbatim(string text)
{
if ( String.IsNullOrEmpty(text) )
return String.Empty;
@ -135,13 +204,13 @@ namespace MinecraftClient
/// Verify that a string contains only a-z A-Z 0-9 and _ characters.
/// </summary>
protected static bool isValidName(string username)
protected static bool IsValidName(string username)
{
if ( String.IsNullOrEmpty(username) )
if (String.IsNullOrEmpty(username))
return false;
foreach ( char c in username )
if ( !((c >= 'a' && c <= 'z')
foreach (char c in username)
if (!((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_') )
@ -158,75 +227,108 @@ namespace MinecraftClient
/// <param name="sender">if it's a private message, this will contain the player name that sends the message</param>
/// <returns>Returns true if the text is a private message</returns>
protected static bool isPrivateMessage(string text, ref string message, ref string sender)
protected static bool IsPrivateMessage(string text, ref string message, ref string sender)
{
text = getVerbatim(text);
if (text == "") { return false; }
string[] tmp = text.Split(' ');
if (String.IsNullOrEmpty(text))
return false;
try
text = GetVerbatim(text);
//Built-in detection routine for private messages
if (Settings.ChatFormat_Builtins)
{
//Detect vanilla /tell messages
//Someone whispers message (MC 1.5)
//Someone whispers to you: message (MC 1.7)
if (tmp.Length > 2 && tmp[1] == "whispers")
string[] tmp = text.Split(' ');
try
{
if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:")
//Detect vanilla /tell messages
//Someone whispers message (MC 1.5)
//Someone whispers to you: message (MC 1.7)
if (tmp.Length > 2 && tmp[1] == "whispers")
{
message = text.Substring(tmp[0].Length + 18); //MC 1.7
if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:")
{
message = text.Substring(tmp[0].Length + 18); //MC 1.7
}
else message = text.Substring(tmp[0].Length + 10); //MC 1.5
sender = tmp[0];
return IsValidName(sender);
}
else message = text.Substring(tmp[0].Length + 10); //MC 1.5
sender = tmp[0];
return isValidName(sender);
}
//Detect Essentials (Bukkit) /m messages
//[Someone -> me] message
//[~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);
}
//Detect Essentials (Bukkit) /m messages
//[Someone -> me] message
//[~Someone -> me] message
else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->"
&& (tmp[2].ToLower() == "me]" || tmp[2].ToLower() == "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);
}
//Detect Essentials (Bukkit) /me messages with some custom rank
//[Someone [rank] -> me] message
//[~Someone [rank] -> me] message
else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->"
&& (tmp[3] == "me]" || tmp[3] == "moi]")) //'me' is replaced by 'moi' in french servers
{
message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1);
sender = tmp[0].Substring(1);
if (sender[0] == '~') { sender = sender.Substring(1); }
return isValidName(sender);
}
//Detect Modified server messages. /m
//[Someone @ me] message
else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@"
&& (tmp[2].ToLower() == "me]" || tmp[2].ToLower() == "moi]")) //'me' is replaced by 'moi' in french servers
{
message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0);
sender = tmp[0].Substring(1);
if (sender[0] == '~') { sender = sender.Substring(1); }
return IsValidName(sender);
}
//Detect HeroChat PMsend
//From Someone: message
else if (text.StartsWith("From "))
{
sender = text.Substring(5).Split(':')[0];
message = text.Substring(text.IndexOf(':') + 2);
return isValidName(sender);
}
//Detect Essentials (Bukkit) /me messages with some custom prefix
//[Prefix] [Someone -> me] message
//[Prefix] [~Someone -> me] message
else if (text[0] == '[' && tmp[0][tmp[0].Length - 1] == ']'
&& tmp[1][0] == '[' && tmp.Length > 4 && tmp[2] == "->"
&& (tmp[3].ToLower() == "me]" || tmp[3].ToLower() == "moi]"))
{
message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1);
sender = tmp[1].Substring(1);
if (sender[0] == '~') { sender = sender.Substring(1); }
return IsValidName(sender);
}
//Detect HeroChat Messages
//[Channel] [Rank] User: Message
else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2)
{
int name_end = text.IndexOf(':');
int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2;
sender = text.Substring(name_start, name_end - name_start);
message = text.Substring(name_end + 2);
return isValidName(sender);
}
//Detect Essentials (Bukkit) /me messages with some custom rank
//[Someone [rank] -> me] message
//[~Someone [rank] -> me] message
else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->"
&& (tmp[3].ToLower() == "me]" || tmp[3].ToLower() == "moi]"))
{
message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1);
sender = tmp[0].Substring(1);
if (sender[0] == '~') { sender = sender.Substring(1); }
return IsValidName(sender);
}
else return false;
//Detect HeroChat PMsend
//From Someone: message
else if (text.StartsWith("From "))
{
sender = text.Substring(5).Split(':')[0];
message = text.Substring(text.IndexOf(':') + 2);
return IsValidName(sender);
}
else return false;
}
catch (IndexOutOfRangeException) { /* Not an expected chat format */ }
catch (ArgumentOutOfRangeException) { /* Same here */ }
}
catch (IndexOutOfRangeException) { return false; }
//User-defined regex for private chat messages
if (Settings.ChatFormat_Private != null)
{
Match regexMatch = Settings.ChatFormat_Private.Match(text);
if (regexMatch.Success && regexMatch.Groups.Count >= 3)
{
sender = regexMatch.Groups[1].Value;
message = regexMatch.Groups[2].Value;
return IsValidName(sender);
}
}
return false;
}
/// <summary>
@ -237,33 +339,101 @@ namespace MinecraftClient
/// <param name="sender">if it's message, this will contain the player name that sends the message</param>
/// <returns>Returns true if the text is a chat message</returns>
protected static bool isChatMessage(string text, ref string message, ref string sender)
protected static bool IsChatMessage(string text, ref string message, ref string sender)
{
//Detect chat messages
//<Someone> message
//<*Faction Someone> message
//<*Faction Someone>: message
//<*Faction ~Nicknamed>: message
text = getVerbatim(text);
if (text == "") { return false; }
if (text[0] == '<')
if (String.IsNullOrEmpty(text))
return false;
text = GetVerbatim(text);
//Built-in detection routine for public messages
if (Settings.ChatFormat_Builtins)
{
try
string[] tmp = text.Split(' ');
//Detect vanilla/factions Messages
//<Someone> message
//<*Faction Someone> message
//<*Faction Someone>: message
//<*Faction ~Nicknamed>: message
if (text[0] == '<')
{
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);
try
{
text = text.Substring(1);
string[] tmp2 = text.Split('>');
sender = tmp2[0];
message = text.Substring(sender.Length + 2);
if (message.Length > 1 && message[0] == ' ')
{ message = message.Substring(1); }
tmp2 = sender.Split(' ');
sender = tmp2[tmp2.Length - 1];
if (sender[0] == '~') { sender = sender.Substring(1); }
return IsValidName(sender);
}
catch (IndexOutOfRangeException) { /* Not a vanilla/faction message */ }
catch (ArgumentOutOfRangeException) { /* Same here */ }
}
//Detect HeroChat Messages
//Public chat messages
//[Channel] [Rank] User: Message
else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2)
{
try
{
int name_end = text.IndexOf(':');
int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2;
sender = text.Substring(name_start, name_end - name_start);
message = text.Substring(name_end + 2);
return IsValidName(sender);
}
catch (IndexOutOfRangeException) { /* Not a herochat message */ }
catch (ArgumentOutOfRangeException) { /* Same here */ }
}
//Detect (Unknown Plugin) Messages
//**Faction<Rank> User : Message
else if (text[0] == '*'
&& text.Length > 1
&& text[1] != ' '
&& text.Contains('<') && text.Contains('>')
&& text.Contains(' ') && text.Contains(':')
&& text.IndexOf('*') < text.IndexOf('<')
&& text.IndexOf('<') < text.IndexOf('>')
&& text.IndexOf('>') < text.IndexOf(' ')
&& text.IndexOf(' ') < text.IndexOf(':'))
{
try
{
string prefix = tmp[0];
string user = tmp[1];
string semicolon = tmp[2];
if (prefix.All(c => char.IsLetterOrDigit(c) || new char[] { '*', '<', '>', '_' }.Contains(c))
&& semicolon == ":")
{
message = text.Substring(prefix.Length + user.Length + 4);
return IsValidName(user);
}
}
catch (IndexOutOfRangeException) { /* Not a <unknown plugin> message */ }
catch (ArgumentOutOfRangeException) { /* Same here */ }
}
catch (IndexOutOfRangeException) { return false; }
}
else return false;
//User-defined regex for public chat messages
if (Settings.ChatFormat_Public != null)
{
Match regexMatch = Settings.ChatFormat_Public.Match(text);
if (regexMatch.Success && regexMatch.Groups.Count >= 3)
{
sender = regexMatch.Groups[1].Value;
message = regexMatch.Groups[2].Value;
return IsValidName(sender);
}
}
return false;
}
/// <summary>
@ -273,27 +443,65 @@ namespace MinecraftClient
/// <param name="sender">Will contain the sender's username, if it's a teleport request</param>
/// <returns>Returns true if the text is a teleport request</returns>
protected static bool isTeleportRequest(string text, ref string sender)
protected static bool IsTeleportRequest(string text, ref string sender)
{
text = getVerbatim(text);
sender = text.Split(' ')[0];
if (text.EndsWith("has requested to teleport to you.")
|| text.EndsWith("has requested that you teleport to them."))
if (String.IsNullOrEmpty(text))
return false;
text = GetVerbatim(text);
//Built-in detection routine for teleport requests
if (Settings.ChatFormat_Builtins)
{
return isValidName(sender);
string[] tmp = text.Split(' ');
//Detect Essentials teleport requests, prossibly with
//nicknamed names or other modifications such as HeroChat
if (text.EndsWith("has requested to teleport to you.")
|| text.EndsWith("has requested that you teleport to them."))
{
//<Rank> Username has requested...
//[Rank] Username has requested...
if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">"))
|| (tmp[0].StartsWith("[") && tmp[0].EndsWith("]")))
&& tmp.Length > 1)
sender = tmp[1];
//Username has requested...
else sender = tmp[0];
//~Username has requested...
if (sender.Length > 1 && sender[0] == '~')
sender = sender.Substring(1);
//Final check on username validity
return IsValidName(sender);
}
}
else return false;
//User-defined regex for teleport requests
if (Settings.ChatFormat_TeleportRequest != null)
{
Match regexMatch = Settings.ChatFormat_TeleportRequest.Match(text);
if (regexMatch.Success && regexMatch.Groups.Count >= 2)
{
sender = regexMatch.Groups[1].Value;
return IsValidName(sender);
}
}
return false;
}
/// <summary>
/// Writes some text in the console. Nothing will be sent to the server.
/// Write some text in the console. Nothing will be sent to the server.
/// </summary>
/// <param name="text">Log text to write</param>
public static void LogToConsole(string text)
protected void LogToConsole(object text)
{
ConsoleIO.WriteLineFormatted("§8[BOT] " + text);
string logfile = Settings.expandVars(Settings.chatbotLogFile);
ConsoleIO.WriteLogLine(String.Format("[{0}] {1}", this.GetType().Name, text));
string logfile = Settings.ExpandVars(Settings.chatbotLogFile);
if (!String.IsNullOrEmpty(logfile))
{
@ -305,26 +513,19 @@ namespace MinecraftClient
catch { return; /* Invalid file name or access denied */ }
}
File.AppendAllLines(logfile, new string[] { getTimestamp() + ' ' + text });
File.AppendAllLines(logfile, new string[] { GetTimestamp() + ' ' + text });
}
}
/// <summary>
/// Disconnect from the server and restart the program
/// It will unload & reload all the bots and then reconnect to the server
/// </summary>
protected void ReconnectToTheServer() { ReconnectToTheServer(3); }
/// <summary>
/// Disconnect from the server and restart the program
/// It will unload & reload all the bots and then reconnect to the server
/// It will unload and reload all the bots and then reconnect to the server
/// </summary>
/// <param name="attempts">If connection fails, the client will make X extra attempts</param>
protected void ReconnectToTheServer(int ExtraAttempts)
protected void ReconnectToTheServer(int ExtraAttempts = 3)
{
McTcpClient.AttemptsLeft = ExtraAttempts;
McTcpClient.ReconnectionAttemptsLeft = ExtraAttempts;
Program.Restart();
}
@ -343,7 +544,7 @@ namespace MinecraftClient
protected void UnloadBot()
{
handler.BotUnLoad(this);
Handler.BotUnLoad(this);
}
/// <summary>
@ -354,7 +555,7 @@ namespace MinecraftClient
protected void SendPrivateMessage(string player, string message)
{
SendText("/tell " + player + ' ' + message);
SendText(String.Format("/{0} {1} {2}", Settings.PrivateMsgsCmdName, player, message));
}
/// <summary>
@ -365,26 +566,102 @@ namespace MinecraftClient
protected void RunScript(string filename, string playername = "")
{
handler.BotLoad(new ChatBots.Script(filename, playername));
Handler.BotLoad(new ChatBots.Script(filename, playername));
}
/// <summary>
/// Get a D-M-Y h:m:s timestamp representing the current system date and time
/// Get the current Minecraft World
/// </summary>
/// <returns>Minecraft world or null if associated setting is disabled</returns>
protected Mapping.World GetWorld()
{
if (Settings.TerrainAndMovements)
return Handler.GetWorld();
return null;
}
/// <summary>
/// Get a Y-M-D h:m:s timestamp representing the current system date and time
/// </summary>
protected static string getTimestamp()
protected static string GetTimestamp()
{
DateTime time = DateTime.Now;
return String.Format("{0}-{1}-{2} {3}:{4}:{5}",
time.Year.ToString("0000"),
time.Month.ToString("00"),
time.Day.ToString("00"),
time.Hour.ToString("00"),
time.Minute.ToString("00"),
time.Second.ToString("00"));
}
string D = time.Day.ToString("00");
string M = time.Month.ToString("00");
string Y = time.Year.ToString("0000");
/// <summary>
/// Load entries from a file as a string array, removing duplicates and empty lines
/// </summary>
/// <param name="file">File to load</param>
/// <returns>The string array or an empty array if failed to load the file</returns>
protected string[] LoadDistinctEntriesFromFile(string file)
{
if (File.Exists(file))
{
//Read all lines from file, remove lines with no text, convert to lowercase,
//remove duplicate entries, convert to a string array, and return the result.
return File.ReadAllLines(file)
.Where(line => !String.IsNullOrWhiteSpace(line))
.Select(line => line.ToLower())
.Distinct().ToArray();
}
else
{
LogToConsole("File not found: " + Settings.Alerts_MatchesFile);
return new string[0];
}
}
string h = time.Hour.ToString("00");
string m = time.Minute.ToString("00");
string s = time.Second.ToString("00");
/// <summary>
/// Registers the given plugin channel for use by this chatbot.
/// </summary>
/// <param name="channel">The name of the channel to register</param>
return "" + D + '-' + M + '-' + Y + ' ' + h + ':' + m + ':' + s;
protected void RegisterPluginChannel(string channel)
{
this.registeredPluginChannels.Add(channel);
Handler.RegisterPluginChannel(channel, this);
}
/// <summary>
/// Unregisters the given plugin channel, meaning this chatbot can no longer use it.
/// </summary>
/// <param name="channel">The name of the channel to unregister</param>
protected void UnregisterPluginChannel(string channel)
{
this.registeredPluginChannels.RemoveAll(chan => chan == channel);
Handler.UnregisterPluginChannel(channel, this);
}
/// <summary>
/// Sends the given plugin channel message to the server, if the channel has been registered.
/// See http://wiki.vg/Plugin_channel for more information about plugin channels.
/// </summary>
/// <param name="channel">The channel to send the message on.</param>
/// <param name="data">The data to send.</param>
/// <param name="sendEvenIfNotRegistered">Should the message be sent even if it hasn't been registered by the server or this bot? (Some Minecraft channels aren't registered)</param>
/// <returns>Whether the message was successfully sent. False if there was a network error or if the channel wasn't registered.</returns>
protected bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false)
{
if (!sendEvenIfNotRegistered)
{
if (!this.registeredPluginChannels.Contains(channel))
{
return false;
}
}
return Handler.SendPluginChannelMessage(channel, data, sendEvenIfNotRegistered);
}
}
}

View file

@ -14,36 +14,13 @@ namespace MinecraftClient.ChatBots
private string[] dictionary = new string[0];
private string[] excludelist = new string[0];
/// <summary>
/// Import alerts from the specified file
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
private static string[] FromFile(string file)
{
if (File.Exists(file))
{
//Read all lines from file, remove lines with no text, convert to lowercase,
//remove duplicate entries, convert to a string array, and return the result.
return File.ReadAllLines(file)
.Where(line => !String.IsNullOrWhiteSpace(line))
.Select(line => line.ToLower())
.Distinct().ToArray();
}
else
{
LogToConsole("File not found: " + Settings.Alerts_MatchesFile);
return new string[0];
}
}
/// <summary>
/// Intitialize the Alerts bot
/// </summary>
public override void Initialize()
{
dictionary = FromFile(Settings.Alerts_MatchesFile);
excludelist = FromFile(Settings.Alerts_ExcludesFile);
dictionary = LoadDistinctEntriesFromFile(Settings.Alerts_MatchesFile);
excludelist = LoadDistinctEntriesFromFile(Settings.Alerts_ExcludesFile);
}
/// <summary>
@ -53,7 +30,7 @@ namespace MinecraftClient.ChatBots
public override void GetText(string text)
{
//Remove color codes and convert to lowercase
text = getVerbatim(text).ToLower();
text = GetVerbatim(text).ToLower();
//Proceed only if no exclusions are found in text
if (!excludelist.Any(exclusion => text.Contains(exclusion)))
@ -67,10 +44,13 @@ namespace MinecraftClient.ChatBots
if (ConsoleIO.basicIO) //Using a GUI? Pass text as is.
ConsoleIO.WriteLine(text.Replace(alert, "§c" + alert + "§r"));
else //Using Consome Prompt : Print text with alert highlighted
else //Using Console Prompt : Print text with alert highlighted
{
string[] splitted = text.Split(new string[] { alert }, StringSplitOptions.None);
ConsoleColor fore = Console.ForegroundColor;
ConsoleColor back = Console.BackgroundColor;
if (splitted.Length > 0)
{
Console.BackgroundColor = ConsoleColor.DarkGray;
@ -89,8 +69,8 @@ namespace MinecraftClient.ChatBots
}
}
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Gray;
Console.BackgroundColor = back;
Console.ForegroundColor = fore;
ConsoleIO.Write('\n');
}
}

View file

@ -25,14 +25,14 @@ namespace MinecraftClient.ChatBots
{
attempts = retries;
if (attempts == -1) { attempts = int.MaxValue; }
McTcpClient.AttemptsLeft = attempts;
McTcpClient.ReconnectionAttemptsLeft = attempts;
delay = DelayBeforeRelog;
if (delay < 1) { delay = 1; }
}
public override void Initialize()
{
McTcpClient.AttemptsLeft = attempts;
McTcpClient.ReconnectionAttemptsLeft = attempts;
if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile))
{
dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile);
@ -47,7 +47,7 @@ namespace MinecraftClient.ChatBots
public override bool OnDisconnect(DisconnectReason reason, string message)
{
message = getVerbatim(message);
message = GetVerbatim(message);
string comp = message.ToLower();
foreach (string msg in dictionary)
{
@ -55,12 +55,22 @@ namespace MinecraftClient.ChatBots
{
LogToConsole("Waiting " + delay + " seconds before reconnecting...");
System.Threading.Thread.Sleep(delay * 1000);
McTcpClient.AttemptsLeft = attempts;
McTcpClient.ReconnectionAttemptsLeft = attempts;
ReconnectToTheServer();
return true;
}
}
return false;
}
public static bool OnDisconnectStatic(DisconnectReason reason, string message)
{
if (Settings.AutoRelog_Enabled)
{
AutoRelog bot = new AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries);
return bot.OnDisconnect(reason, message);
}
return false;
}
}
}

View file

@ -0,0 +1,226 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// This bot automatically runs actions when a user sends a message matching a specified rule
/// </summary>
class AutoRespond : ChatBot
{
private string matchesFile;
private List<RespondRule> respondRules;
private enum MessageType { Public, Private, Other };
/// <summary>
/// Create a new AutoRespond bot
/// </summary>
/// <param name="matchesFile">INI File to load matches from</param>
public AutoRespond(string matchesFile)
{
this.matchesFile = matchesFile;
}
/// <summary>
/// Describe a respond rule based on a simple match or a regex
/// </summary>
private class RespondRule
{
private Regex regex;
private string match;
private string actionPublic;
private string actionPrivate;
private string actionOther;
/// <summary>
/// Create a respond rule from a regex and a reponse message or command
/// </summary>
/// <param name="regex">Regex</param>
/// <param name="actionPublic">Internal command to run for public messages</param>
/// <param name="actionPrivate">Internal command to run for private messages</param>
/// <param name="actionOther">Internal command to run for any other messages</param>
public RespondRule(Regex regex, string actionPublic, string actionPrivate, string actionOther)
{
this.regex = regex;
this.match = null;
this.actionPublic = actionPublic;
this.actionPrivate = actionPrivate;
this.actionOther = actionOther;
}
/// <summary>
/// Create a respond rule from a match string and a reponse message or command
/// </summary>
/// <param name="match">Match string</param>
/// <param name="actionPublic">Internal command to run for public messages</param>
/// <param name="actionPrivate">Internal command to run for private messages</param>
public RespondRule(string match, string actionPublic, string actionPrivate, string actionOther)
{
this.regex = null;
this.match = match;
this.actionPublic = actionPublic;
this.actionPrivate = actionPrivate;
this.actionOther = actionOther;
}
/// <summary>
/// Match the respond rule to the specified string and return a message or command to send if a match is detected
/// </summary>
/// <param name="username">Player who have sent the message</param>
/// <param name="message">Message to match against the regex or match string</param>
/// <param name="msgType">Type of the message public/private message, or other message</param>
/// <returns>Internal command to run as a response to this user, or null if no match has been detected</returns>
public string Match(string username, string message, MessageType msgType)
{
string toSend = null;
switch (msgType)
{
case MessageType.Public: toSend = actionPublic; break;
case MessageType.Private: toSend = actionPrivate; break;
case MessageType.Other: toSend = actionOther; break;
}
if (String.IsNullOrEmpty(toSend))
return null;
if (regex != null)
{
if (regex.IsMatch(message))
{
Match regexMatch = regex.Match(message);
for (int i = regexMatch.Groups.Count - 1; i >= 1; i--)
toSend = toSend.Replace("$" + i, regexMatch.Groups[i].Value);
toSend = toSend.Replace("$u", username);
return toSend;
}
}
else if (!String.IsNullOrEmpty(match))
{
if (message.ToLower().Contains(match.ToLower()))
{
return toSend.Replace("$u", username);
}
}
return null;
}
}
/// <summary>
/// Initialize the AutoRespond bot from the matches file
/// </summary>
public override void Initialize()
{
if (File.Exists(matchesFile))
{
Regex matchRegex = null;
string matchString = null;
string matchAction = null;
string matchActionPrivate = null;
string matchActionOther = null;
respondRules = new List<RespondRule>();
foreach (string lineRAW in File.ReadAllLines(matchesFile))
{
string line = lineRAW.Split('#')[0].Trim();
if (line.Length > 0)
{
if (line[0] == '[' && line[line.Length - 1] == ']')
{
switch (line.Substring(1, line.Length - 2).ToLower())
{
case "match":
CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther);
matchRegex = null;
matchString = null;
matchAction = null;
matchActionPrivate = null;
matchActionOther = null;
break;
}
}
else
{
string argName = line.Split('=')[0];
if (line.Length > (argName.Length + 1))
{
string argValue = line.Substring(argName.Length + 1);
switch (argName.ToLower())
{
case "regex": matchRegex = new Regex(argValue); break;
case "match": matchString = argValue; break;
case "action": matchAction = argValue; break;
case "actionprivate": matchActionPrivate = argValue; break;
case "actionother": matchActionOther = argValue; break;
}
}
}
}
}
CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther);
}
else
{
LogToConsole("File not found: '" + matchesFile + "'");
UnloadBot(); //No need to keep the bot active
}
}
/// <summary>
/// Create a new respond rule from the provided arguments, only if they are valid: at least one match and one action
/// </summary>
/// <param name="matchRegex">Matching regex</param>
/// <param name="matchString">Matching string</param>
/// <param name="matchAction">Action if the matching message is public</param>
/// <param name="matchActionPrivate">Action if the matching message is private</param>
private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate, string matchActionOther)
{
if (matchAction != null || matchActionPrivate != null || matchActionOther != null)
{
if (matchRegex != null)
{
respondRules.Add(new RespondRule(matchRegex, matchAction, matchActionPrivate, matchActionOther));
}
else if (matchString != null)
{
respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate, matchActionOther));
}
}
}
public override void GetText(string text)
{
//Remove colour codes
text = GetVerbatim(text);
//Get Message type
string sender = "", message = "";
MessageType msgType = MessageType.Other;
if (IsChatMessage(text, ref message, ref sender))
msgType = MessageType.Public;
else if (IsPrivateMessage(text, ref message, ref sender))
msgType = MessageType.Private;
else message = text;
//Do not process messages sent by the bot itself
if (msgType == MessageType.Other || sender != Settings.Username)
{
foreach (RespondRule rule in respondRules)
{
string toPerform = rule.Match(sender, message, msgType);
if (!String.IsNullOrEmpty(toPerform))
{
string response = null;
LogToConsole(toPerform);
PerformInternalCommand(toPerform, ref response);
if (!String.IsNullOrEmpty(response))
LogToConsole(response);
}
}
}
}
}
}

View file

@ -69,15 +69,15 @@ namespace MinecraftClient.ChatBots
public override void GetText(string text)
{
text = getVerbatim(text);
text = GetVerbatim(text);
string sender = "";
string message = "";
if (saveChat && isChatMessage(text, ref message, ref sender))
if (saveChat && IsChatMessage(text, ref message, ref sender))
{
save("Chat " + sender + ": " + message);
}
else if (savePrivate && isPrivateMessage(text, ref message, ref sender))
else if (savePrivate && IsPrivateMessage(text, ref message, ref sender))
{
save("Private " + sender + ": " + message);
}
@ -90,7 +90,7 @@ namespace MinecraftClient.ChatBots
private void save(string tosave)
{
if (dateandtime)
tosave = getTimestamp() + ' ' + tosave;
tosave = GetTimestamp() + ' ' + tosave;
string directory = Path.GetDirectoryName(logfile);
if (!String.IsNullOrEmpty(directory) && !Directory.Exists(directory))

View file

@ -52,9 +52,9 @@ namespace MinecraftClient.ChatBots
{
string message = "";
string username = "";
text = getVerbatim(text);
text = GetVerbatim(text);
if (isPrivateMessage(text, ref message, ref username))
if (IsPrivateMessage(text, ref message, ref username))
{
if (Settings.Bots_Owners.Contains(username.ToLower()))
{
@ -73,7 +73,7 @@ namespace MinecraftClient.ChatBots
}
else
{
if (running && isChatMessage(text, ref message, ref username))
if (running && IsChatMessage(text, ref message, ref username))
{
if (message.Length == 1)
{

View file

@ -46,7 +46,7 @@ namespace MinecraftClient.ChatBots
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");
System.IO.File.AppendAllText(file, TimeStamp + "\n" + GetVerbatim(text) + "\n\n");
}
}
}

View file

@ -13,19 +13,19 @@ namespace MinecraftClient.ChatBots
{
public override void GetText(string text)
{
text = getVerbatim(text);
text = GetVerbatim(text);
string command = "", sender = "";
if (isPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower().Trim()))
if (IsPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower().Trim()))
{
string response = "";
performInternalCommand(command, ref response);
PerformInternalCommand(command, ref response);
if (response.Length > 0)
{
SendPrivateMessage(sender, response);
}
}
else if (Settings.RemoteCtrl_AutoTpaccept
&& isTeleportRequest(text, ref sender)
&& IsTeleportRequest(text, ref sender)
&& (Settings.RemoteCtrl_AutoTpaccept_Everyone || Settings.Bots_Owners.Contains(sender.ToLower().Trim())))
{
SendText("/tpaccept");

View file

@ -2,6 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
namespace MinecraftClient.ChatBots
{
@ -13,14 +17,17 @@ namespace MinecraftClient.ChatBots
{
private string file;
private string[] lines = new string[0];
private string[] args = new string[0];
private int sleepticks = 10;
private int sleepticks_interval = 10;
private int nextline = 0;
private string owner;
private bool csharp;
private Thread thread;
private ManualResetEvent tpause;
public Script(string filename)
{
file = filename;
ParseArguments(filename);
}
public Script(string filename, string ownername)
@ -30,7 +37,52 @@ namespace MinecraftClient.ChatBots
owner = ownername;
}
public static bool lookForScript(ref string filename)
private void ParseArguments(string argstr)
{
List<string> args = new List<string>();
StringBuilder str = new StringBuilder();
bool escape = false;
bool quotes = false;
foreach (char c in argstr)
{
if (escape)
{
if (c != '"')
str.Append('\\');
str.Append(c);
escape = false;
}
else
{
if (c == '\\')
escape = true;
else if (c == '"')
quotes = !quotes;
else if (c == ' ' && !quotes)
{
if (str.Length > 0)
args.Add(str.ToString());
str.Clear();
}
else str.Append(c);
}
}
if (str.Length > 0)
args.Add(str.ToString());
if (args.Count > 0)
{
file = args[0];
args.RemoveAt(0);
this.args = args.ToArray();
}
else file = "";
}
public static bool LookForScript(ref string filename)
{
//Automatically look in subfolders and try to add ".txt" file extension
char dir_slash = Program.isUsingMono ? '/' : '\\';
@ -38,10 +90,13 @@ namespace MinecraftClient.ChatBots
{
filename,
filename + ".txt",
filename + ".cs",
"scripts" + dir_slash + filename,
"scripts" + dir_slash + filename + ".txt",
"scripts" + dir_slash + filename + ".cs",
"config" + dir_slash + filename,
"config" + dir_slash + filename + ".txt",
"config" + dir_slash + filename + ".cs",
};
foreach (string possible_file in files)
@ -59,64 +114,105 @@ namespace MinecraftClient.ChatBots
public override void Initialize()
{
//Load the given file from the startup parameters
if (lookForScript(ref file))
if (LookForScript(ref file))
{
lines = System.IO.File.ReadAllLines(file);
if (owner != null) { SendPrivateMessage(owner, "Script '" + file + "' loaded."); }
csharp = file.EndsWith(".cs");
thread = null;
if (owner != null)
SendPrivateMessage(owner, "Script '" + file + "' loaded.");
}
else
{
LogToConsole("File not found: '" + file + "'");
if (owner != null)
SendPrivateMessage(owner, "File not found: '" + file + "'");
UnloadBot(); //No need to keep the bot active
}
}
public override void Update()
{
if (sleepticks > 0) { sleepticks--; }
else
if (csharp) //C# compiled script
{
if (nextline < lines.Length) //Is there an instruction left to interpret?
//Initialize thread on first update
if (thread == null)
{
string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line
nextline++; //Move the cursor so that the next time the following line will be interpreted
sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming
if (instruction_line.Length > 1)
tpause = new ManualResetEvent(false);
thread = new Thread(() =>
{
if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/')
try
{
instruction_line = Settings.expandVars(instruction_line);
string instruction_name = instruction_line.Split(' ')[0];
switch (instruction_name.ToLower())
{
case "wait":
int ticks = 10;
try
{
ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5));
}
catch { }
sleepticks = ticks;
break;
default:
if (!performInternalCommand(instruction_line))
{
sleepticks = 0; Update(); //Unknown command : process next line immediately
}
else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); }
break;
}
CSharpRunner.Run(this, tpause, lines, args);
}
else { sleepticks = 0; Update(); } //Comment: process next line immediately
}
catch (CSharpException e)
{
string errorMessage = "Script '" + file + "' failed to run (" + e.ExceptionType + ").";
LogToConsole(errorMessage);
if (owner != null)
SendPrivateMessage(owner, errorMessage);
LogToConsole(e.InnerException);
}
});
thread.Start();
}
//Let the thread run for a short span of time
if (thread != null)
{
tpause.Set();
tpause.Reset();
if (!thread.IsAlive)
UnloadBot();
}
}
else //Classic MCC script interpreter
{
if (sleepticks > 0) { sleepticks--; }
else
{
//No more instructions to interpret
UnloadBot();
if (nextline < lines.Length) //Is there an instruction left to interpret?
{
string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line
nextline++; //Move the cursor so that the next time the following line will be interpreted
if (instruction_line.Length > 1)
{
if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/')
{
instruction_line = Settings.ExpandVars(instruction_line);
string instruction_name = instruction_line.Split(' ')[0];
switch (instruction_name.ToLower())
{
case "wait":
int ticks = 10;
try
{
ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5));
}
catch { }
sleepticks = ticks;
break;
default:
if (!PerformInternalCommand(instruction_line))
{
Update(); //Unknown command : process next line immediately
}
else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); }
break;
}
}
else { Update(); } //Comment: process next line immediately
}
}
else
{
//No more instructions to interpret
UnloadBot();
}
}
}
}

View file

@ -95,7 +95,7 @@ namespace MinecraftClient.ChatBots
if (current_task != null)
{
//Check if we built a valid task before adding it
if (current_task.script_file != null && Script.lookForScript(ref current_task.script_file) //Check if file exists
if (current_task.script_file != null && Script.LookForScript(ref current_task.script_file) //Check if file exists
&& (current_task.triggerOnLogin
|| (current_task.triggerOnTime && current_task.triggerOnTime_Times.Count > 0))
|| (current_task.triggerOnInterval && current_task.triggerOnInterval_Interval > 0)) //Look for a valid trigger

View file

@ -15,13 +15,13 @@ namespace MinecraftClient.ChatBots
{
string message = "";
string username = "";
text = getVerbatim(text);
text = GetVerbatim(text);
if (isPrivateMessage(text, ref message, ref username))
if (IsPrivateMessage(text, ref message, ref username))
{
ConsoleIO.WriteLine("Bot: " + username + " told me : " + message);
}
else if (isChatMessage(text, ref message, ref username))
else if (IsChatMessage(text, ref message, ref username))
{
ConsoleIO.WriteLine("Bot: " + username + " said : " + message);
}

View file

@ -17,13 +17,13 @@ namespace MinecraftClient.Commands
string[] args = getArgs(command);
if (args.Length > 1)
{
if (!Settings.setAccount(args[1]))
if (!Settings.SetAccount(args[1]))
{
return "Unknown account '" + args[1] + "'.";
}
}
if (Settings.setServerIP(args[0]))
if (Settings.SetServerIP(args[0]))
{
Program.Restart();
return "";

View file

@ -12,7 +12,7 @@ namespace MinecraftClient.Commands
public override string Run(McTcpClient handler, string command)
{
return "PlayerList: " + String.Join(", ", handler.getOnlinePlayers());
return "PlayerList: " + String.Join(", ", handler.GetOnlinePlayers());
}
}
}

View file

@ -14,7 +14,7 @@ namespace MinecraftClient.Commands
{
if (hasArg(command))
{
ChatBot.LogToConsole(getArg(command));
ConsoleIO.WriteLogLine(getArg(command));
return "";
}
else return CMDDesc;

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Mapping;
namespace MinecraftClient.Commands
{
public class Move : Command
{
public override string CMDName { get { return "move"; } }
public override string CMDDesc { get { return "move <get|up|down|east|west|north|south|x y z>: walk or start walking."; } }
public override string Run(McTcpClient handler, string command)
{
if (Settings.TerrainAndMovements)
{
string[] args = getArgs(command);
if (args.Length == 1)
{
string dirStr = getArg(command).Trim().ToLower();
Direction direction;
switch (dirStr)
{
case "up": direction = Direction.Up; break;
case "down": direction = Direction.Down; break;
case "east": direction = Direction.East; break;
case "west": direction = Direction.West; break;
case "north": direction = Direction.North; break;
case "south": direction = Direction.South; break;
case "get": return handler.GetCurrentLocation().ToString();
default: return "Unknown direction '" + dirStr + "'.";
}
if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction))
{
handler.MoveTo(Movement.Move(handler.GetCurrentLocation(), direction));
return "Moving " + dirStr + '.';
}
else return "Cannot move in that direction.";
}
else if (args.Length == 3)
{
try
{
int x = int.Parse(args[0]);
int y = int.Parse(args[1]);
int z = int.Parse(args[2]);
Location goal = new Location(x, y, z);
if (handler.MoveTo(goal))
return "Walking to " + goal;
return "Failed to compute path to " + goal;
}
catch (FormatException) { return CMDDesc; }
}
else return CMDDesc;
}
else return "Please enable terrainandmovements in config to use this command.";
}
}
}

View file

@ -15,7 +15,7 @@ namespace MinecraftClient.Commands
string[] args = getArgs(command);
if (args.Length > 0)
{
if (!Settings.setAccount(args[0]))
if (!Settings.SetAccount(args[0]))
{
return "Unknown account '" + args[0] + "'.";
}

View file

@ -17,7 +17,7 @@ namespace MinecraftClient.Commands
string[] temp = getArg(command).Split('=');
if (temp.Length > 1)
{
if (Settings.setVar(temp[0], getArg(command).Substring(temp[0].Length + 1)))
if (Settings.SetVar(temp[0], getArg(command).Substring(temp[0].Length + 1)))
{
return ""; //Success
}

View file

@ -14,16 +14,30 @@ namespace MinecraftClient
public static class ConsoleIO
{
public static void Reset() { if (reading) { reading = false; Console.Write("\b \b"); } }
public static void SetAutoCompleteEngine(IAutoComplete engine) { autocomplete_engine = engine; }
public static bool basicIO = false;
private static IAutoComplete autocomplete_engine;
private static LinkedList<string> previous = new LinkedList<string>();
private static readonly object io_lock = new object();
private static bool reading = false;
private static string buffer = "";
private static string buffer2 = "";
private static bool reading = false;
private static bool reading_lock = false;
private static bool writing_lock = false;
/// <summary>
/// Reset the IO mechanism and clear all buffers
/// </summary>
public static void Reset()
{
lock (io_lock)
{
if (reading)
{
ClearLineAndBuffer();
reading = false;
Console.Write("\b \b");
}
}
}
/// <summary>
/// Read a password from the standard input
@ -31,22 +45,18 @@ namespace MinecraftClient
public static string ReadPassword()
{
string password = "";
ConsoleKeyInfo k = new ConsoleKeyInfo();
while (k.Key != ConsoleKey.Enter)
StringBuilder password = new StringBuilder();
ConsoleKeyInfo k;
while ((k = Console.ReadKey(true)).Key != ConsoleKey.Enter)
{
k = Console.ReadKey(true);
switch (k.Key)
{
case ConsoleKey.Enter:
Console.Write('\n');
return password;
case ConsoleKey.Backspace:
if (password.Length > 0)
{
Console.Write("\b \b");
password = password.Substring(0, password.Length - 1);
password.Remove(password.Length - 1, 1);
}
break;
@ -56,7 +66,6 @@ namespace MinecraftClient
case ConsoleKey.Home:
case ConsoleKey.End:
case ConsoleKey.Delete:
case ConsoleKey.Oem6:
case ConsoleKey.DownArrow:
case ConsoleKey.UpArrow:
case ConsoleKey.Tab:
@ -66,12 +75,14 @@ namespace MinecraftClient
if (k.KeyChar != 0)
{
Console.Write('*');
password += k.KeyChar;
password.Append(k.KeyChar);
}
break;
}
}
return password;
Console.WriteLine();
return password.ToString();
}
/// <summary>
@ -82,104 +93,106 @@ namespace MinecraftClient
{
if (basicIO) { return Console.ReadLine(); }
ConsoleKeyInfo k = new ConsoleKeyInfo();
Console.Write('>');
reading = true;
buffer = "";
buffer2 = "";
lock (io_lock)
{
Console.Write('>');
reading = true;
buffer = "";
buffer2 = "";
}
while (k.Key != ConsoleKey.Enter)
{
k = Console.ReadKey(true);
while (writing_lock) { }
reading_lock = true;
if (k.Key == ConsoleKey.V && k.Modifiers == ConsoleModifiers.Control)
lock (io_lock)
{
string clip = ReadClipboard();
foreach (char c in clip)
AddChar(c);
}
else
{
switch (k.Key)
if (k.Key == ConsoleKey.V && k.Modifiers == ConsoleModifiers.Control)
{
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();
string clip = ReadClipboard();
foreach (char c in clip)
AddChar(c);
}
else
{
switch (k.Key)
{
case ConsoleKey.Escape:
ClearLineAndBuffer();
break;
case ConsoleKey.Backspace:
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;
case ConsoleKey.Tab:
if (autocomplete_engine != null && buffer.Length > 0)
{
string[] tmp = buffer.Split(' ');
if (tmp.Length > 0)
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;
case ConsoleKey.Tab:
if (autocomplete_engine != null && buffer.Length > 0)
{
string word_tocomplete = tmp[tmp.Length - 1];
string word_autocomplete = autocomplete_engine.AutoComplete(buffer);
if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete)
if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != buffer)
{
while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); }
foreach (char c in word_autocomplete) { AddChar(c); }
}
}
}
break;
default:
if (k.KeyChar != 0)
AddChar(k.KeyChar);
break;
break;
default:
if (k.KeyChar != 0)
AddChar(k.KeyChar);
break;
}
}
}
reading_lock = false;
}
while (writing_lock) { }
reading = false;
previous.AddLast(buffer + buffer2);
return buffer + buffer2;
lock (io_lock)
{
reading = false;
previous.AddLast(buffer + buffer2);
return buffer + buffer2;
}
}
/// <summary>
@ -188,41 +201,47 @@ namespace MinecraftClient
public static void Write(string text)
{
if (basicIO) { Console.Write(text); return; }
while (reading_lock) { }
writing_lock = true;
if (reading)
if (!basicIO)
{
ConsoleColor fore = Console.ForegroundColor;
ConsoleColor back = Console.BackgroundColor;
string buf = buffer;
string buf2 = buffer2;
ClearLineAndBuffer();
if (Console.CursorLeft == 0)
lock (io_lock)
{
Console.CursorLeft = Console.BufferWidth - 1;
Console.CursorTop--;
Console.Write(' ');
Console.CursorLeft = Console.BufferWidth - 1;
Console.CursorTop--;
if (reading)
{
try
{
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);
buffer = buf;
buffer2 = buf2;
Console.Write(">" + buffer);
if (buffer2.Length > 0)
{
Console.Write(buffer2 + " \b");
for (int i = 0; i < buffer2.Length; i++) { GoBack(); }
}
}
catch (ArgumentOutOfRangeException)
{
//Console resized: Try again
Console.Write('\n');
Write(text);
}
}
else Console.Write(text);
}
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_lock = false;
}
/// <summary>
@ -257,7 +276,7 @@ namespace MinecraftClient
if (Settings.chatTimeStamps)
{
int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
ConsoleIO.Write(hour.ToString("00") + ':' + minute.ToString("00") + ':' + second.ToString("00") + ' ');
ConsoleIO.Write(String.Format("{0}:{1}:{2} ", hour.ToString("00"), minute.ToString("00"), second.ToString("00")));
}
if (!acceptnewlines) { str = str.Replace('\n', ' '); }
if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(str); return; }
@ -285,7 +304,7 @@ namespace MinecraftClient
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;
case 'r': Console.ForegroundColor = ConsoleColor.Gray; break;
}
if (subs[i].Length > 1)
@ -294,9 +313,19 @@ namespace MinecraftClient
}
}
}
Console.ForegroundColor = ConsoleColor.Gray;
ConsoleIO.Write('\n');
}
Console.ForegroundColor = ConsoleColor.Gray;
}
/// <summary>
/// Write a Minecraft Console Client Log line
/// </summary>
/// <param name="text">Text of the log line</param>
public static void WriteLogLine(string text)
{
WriteLineFormatted("§8[MCC] " + text);
}
#region Subfunctions
@ -309,15 +338,21 @@ namespace MinecraftClient
{
if (buffer.Length > 0)
{
if (Console.CursorLeft == 0)
try
{
Console.CursorLeft = Console.BufferWidth - 1;
Console.CursorTop--;
Console.Write(' ');
Console.CursorLeft = Console.BufferWidth - 1;
Console.CursorTop--;
if (Console.CursorLeft == 0)
{
Console.CursorLeft = Console.BufferWidth - 1;
if (Console.CursorTop > 0)
Console.CursorTop--;
Console.Write(' ');
Console.CursorLeft = Console.BufferWidth - 1;
if (Console.CursorTop > 0)
Console.CursorTop--;
}
else Console.Write("\b \b");
}
else Console.Write("\b \b");
catch (ArgumentOutOfRangeException) { /* Console was resized!? */ }
buffer = buffer.Substring(0, buffer.Length - 1);
if (buffer2.Length > 0)
@ -329,12 +364,17 @@ namespace MinecraftClient
}
private static void GoBack()
{
if (Console.CursorLeft == 0)
try
{
Console.CursorLeft = Console.BufferWidth - 1;
Console.CursorTop--;
if (Console.CursorLeft == 0)
{
Console.CursorLeft = Console.BufferWidth - 1;
if (Console.CursorTop > 0)
Console.CursorTop--;
}
else Console.Write('\b');
}
else Console.Write('\b');
catch (ArgumentOutOfRangeException) { /* Console was resized!? */ }
}
private static void GoLeft()
{
@ -383,6 +423,18 @@ namespace MinecraftClient
return clipdata;
}
#endregion
#region AutoComplete API
/// <summary>
/// Set an auto-completion engine for TAB autocompletion
/// </summary>
/// <param name="engine">Engine implementing the IAutoComplete interface</param>
public static void SetAutoCompleteEngine(IAutoComplete engine)
{
autocomplete_engine = engine;
}
#endregion
}
/// <summary>

View file

@ -36,9 +36,13 @@ namespace MinecraftClient
{
using (HttpWebResponse httpWebReponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
Bitmap skin = new Bitmap(Image.FromStream(httpWebReponse.GetResponseStream())); //Read skin from network
skin = skin.Clone(new Rectangle(8, 8, 8, 8), skin.PixelFormat); //Crop skin
SetConsoleIcon(skin.GetHicon()); //Set skin as icon
try
{
Bitmap skin = new Bitmap(Image.FromStream(httpWebReponse.GetResponseStream())); //Read skin from network
skin = skin.Clone(new Rectangle(8, 8, 8, 8), skin.PixelFormat); //Crop skin
SetConsoleIcon(skin.GetHicon()); //Set skin as icon
}
catch (ArgumentException) { /* Invalid image in HTTP response */ }
}
}
catch (WebException) //Skin not found? Reset to default icon

View file

@ -197,14 +197,13 @@ namespace MinecraftClient.Crypto
/// </summary>
/// <param name="underlyingStream">Stream to encrypt</param>
/// <param name="AesKey">Key to use</param>
/// <param name="paddingProvider">Padding provider for Mono implementation</param>
/// <returns>Return an appropriate stream depending on the framework being used</returns>
public static IAesStream getAesStream(Stream underlyingStream, byte[] AesKey, IPaddingProvider paddingProvider)
public static IAesStream getAesStream(Stream underlyingStream, byte[] AesKey)
{
if (Program.isUsingMono)
{
return new Streams.MonoAesStream(underlyingStream, AesKey, paddingProvider);
return new Streams.MonoAesStream(underlyingStream, AesKey);
}
else return new Streams.RegularAesStream(underlyingStream, AesKey);
}

View file

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Crypto
{
/// <summary>
/// Interface for padding provider
/// Allow to get a padding plugin message from the current network protocol implementation.
/// </summary>
public interface IPaddingProvider
{
byte[] getPaddingPacket();
}
}

View file

@ -0,0 +1,855 @@
using System;
using System.Diagnostics;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Utilities;
namespace Org.BouncyCastle.Crypto.Engines
{
/**
* an implementation of the AES (Rijndael)), from FIPS-197.
* <p>
* For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>.
*
* This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
* <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
*
* There are three levels of tradeoff of speed vs memory
* Because java has no preprocessor), they are written as three separate classes from which to choose
*
* The fastest uses 8Kbytes of static tables to precompute round calculations), 4 256 word tables for encryption
* and 4 for decryption.
*
* The middle performance version uses only one 256 word table for each), for a total of 2Kbytes),
* adding 12 rotate operations per round to compute the values contained in the other tables from
* the contents of the first
*
* The slowest version uses no static tables at all and computes the values in each round
* </p>
* <p>
* This file contains the fast version with 8Kbytes of static tables for round precomputation
* </p>
*/
public class AesFastEngine
: IBlockCipher
{
// The S box
private static readonly byte[] S =
{
99, 124, 119, 123, 242, 107, 111, 197,
48, 1, 103, 43, 254, 215, 171, 118,
202, 130, 201, 125, 250, 89, 71, 240,
173, 212, 162, 175, 156, 164, 114, 192,
183, 253, 147, 38, 54, 63, 247, 204,
52, 165, 229, 241, 113, 216, 49, 21,
4, 199, 35, 195, 24, 150, 5, 154,
7, 18, 128, 226, 235, 39, 178, 117,
9, 131, 44, 26, 27, 110, 90, 160,
82, 59, 214, 179, 41, 227, 47, 132,
83, 209, 0, 237, 32, 252, 177, 91,
106, 203, 190, 57, 74, 76, 88, 207,
208, 239, 170, 251, 67, 77, 51, 133,
69, 249, 2, 127, 80, 60, 159, 168,
81, 163, 64, 143, 146, 157, 56, 245,
188, 182, 218, 33, 16, 255, 243, 210,
205, 12, 19, 236, 95, 151, 68, 23,
196, 167, 126, 61, 100, 93, 25, 115,
96, 129, 79, 220, 34, 42, 144, 136,
70, 238, 184, 20, 222, 94, 11, 219,
224, 50, 58, 10, 73, 6, 36, 92,
194, 211, 172, 98, 145, 149, 228, 121,
231, 200, 55, 109, 141, 213, 78, 169,
108, 86, 244, 234, 101, 122, 174, 8,
186, 120, 37, 46, 28, 166, 180, 198,
232, 221, 116, 31, 75, 189, 139, 138,
112, 62, 181, 102, 72, 3, 246, 14,
97, 53, 87, 185, 134, 193, 29, 158,
225, 248, 152, 17, 105, 217, 142, 148,
155, 30, 135, 233, 206, 85, 40, 223,
140, 161, 137, 13, 191, 230, 66, 104,
65, 153, 45, 15, 176, 84, 187, 22,
};
// The inverse S-box
private static readonly byte[] Si =
{
82, 9, 106, 213, 48, 54, 165, 56,
191, 64, 163, 158, 129, 243, 215, 251,
124, 227, 57, 130, 155, 47, 255, 135,
52, 142, 67, 68, 196, 222, 233, 203,
84, 123, 148, 50, 166, 194, 35, 61,
238, 76, 149, 11, 66, 250, 195, 78,
8, 46, 161, 102, 40, 217, 36, 178,
118, 91, 162, 73, 109, 139, 209, 37,
114, 248, 246, 100, 134, 104, 152, 22,
212, 164, 92, 204, 93, 101, 182, 146,
108, 112, 72, 80, 253, 237, 185, 218,
94, 21, 70, 87, 167, 141, 157, 132,
144, 216, 171, 0, 140, 188, 211, 10,
247, 228, 88, 5, 184, 179, 69, 6,
208, 44, 30, 143, 202, 63, 15, 2,
193, 175, 189, 3, 1, 19, 138, 107,
58, 145, 17, 65, 79, 103, 220, 234,
151, 242, 207, 206, 240, 180, 230, 115,
150, 172, 116, 34, 231, 173, 53, 133,
226, 249, 55, 232, 28, 117, 223, 110,
71, 241, 26, 113, 29, 41, 197, 137,
111, 183, 98, 14, 170, 24, 190, 27,
252, 86, 62, 75, 198, 210, 121, 32,
154, 219, 192, 254, 120, 205, 90, 244,
31, 221, 168, 51, 136, 7, 199, 49,
177, 18, 16, 89, 39, 128, 236, 95,
96, 81, 127, 169, 25, 181, 74, 13,
45, 229, 122, 159, 147, 201, 156, 239,
160, 224, 59, 77, 174, 42, 245, 176,
200, 235, 187, 60, 131, 83, 153, 97,
23, 43, 4, 126, 186, 119, 214, 38,
225, 105, 20, 99, 85, 33, 12, 125,
};
// vector used in calculating key schedule (powers of x in GF(256))
private static readonly byte[] rcon =
{
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91
};
// precomputation tables of calculations for rounds
private static readonly uint[] T0 =
{
0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff,
0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102,
0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d,
0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa,
0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41,
0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453,
0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d,
0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83,
0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2,
0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795,
0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a,
0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df,
0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912,
0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc,
0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7,
0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413,
0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040,
0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d,
0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0,
0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed,
0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a,
0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78,
0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080,
0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1,
0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020,
0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18,
0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488,
0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a,
0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0,
0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54,
0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b,
0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad,
0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992,
0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd,
0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3,
0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda,
0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8,
0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4,
0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a,
0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697,
0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96,
0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c,
0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7,
0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969,
0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9,
0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9,
0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715,
0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5,
0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65,
0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929,
0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d,
0x3a16162c
};
private static readonly uint[] T1 =
{
0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d,
0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203,
0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6,
0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87,
0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, 0xadad41ec,
0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7,
0x7272e496, 0xc0c09b5b, 0xb7b775c2, 0xfdfde11c, 0x93933dae,
0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f,
0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, 0x7171e293,
0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552,
0x23234665, 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f,
0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d,
0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b,
0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, 0x6e6edcb2,
0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761,
0xb3b37dce, 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397,
0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060,
0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46,
0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8,
0xcfcf854a, 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16,
0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf,
0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844,
0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, 0x404080c0,
0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104,
0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, 0x10102030,
0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814,
0x13132635, 0xececc32f, 0x5f5fbee1, 0x979735a2, 0x444488cc,
0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47,
0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0,
0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e,
0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3,
0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, 0xdbdbad76,
0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db,
0x06060c0a, 0x2424486c, 0x5c5cb8e4, 0xc2c29f5d, 0xd3d3bd6e,
0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337,
0x7979f28b, 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7,
0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4,
0x5656acfa, 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e,
0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f,
0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751,
0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, 0x4b4b96dd,
0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42,
0xb5b571c4, 0x6666ccaa, 0x484890d8, 0x03030605, 0xf6f6f701,
0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0,
0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938,
0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970,
0x8e8e0789, 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592,
0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a,
0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da,
0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, 0x999929b0,
0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6,
0x16162c3a
};
private static readonly uint[] T2 =
{
0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2,
0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301,
0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab,
0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d,
0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, 0xad41ecad,
0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4,
0x72e49672, 0xc09b5bc0, 0xb775c2b7, 0xfde11cfd, 0x933dae93,
0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc,
0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, 0x71e29371,
0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7,
0x23466523, 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05,
0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2,
0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09,
0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, 0x6edcb26e,
0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6,
0xb37dceb3, 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784,
0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020,
0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb,
0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858,
0xcf854acf, 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb,
0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45,
0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c,
0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, 0x4080c040,
0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5,
0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, 0x10203010,
0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c,
0x13263513, 0xecc32fec, 0x5fbee15f, 0x9735a297, 0x4488cc44,
0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d,
0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060,
0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a,
0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8,
0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, 0xdbad76db,
0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49,
0x060c0a06, 0x24486c24, 0x5cb8e45c, 0xc29f5dc2, 0xd3bd6ed3,
0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4,
0x79f28b79, 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d,
0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c,
0x56acfa56, 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a,
0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25,
0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6,
0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, 0x4b96dd4b,
0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e,
0xb571c4b5, 0x66ccaa66, 0x4890d848, 0x03060503, 0xf6f701f6,
0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9,
0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, 0xe1d938e1,
0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9,
0x8e07898e, 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287,
0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf,
0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf,
0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, 0x9929b099,
0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb,
0x162c3a16
};
private static readonly uint[] T3 =
{
0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2,
0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101,
0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab,
0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d,
0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, 0x41ecadad,
0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4,
0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393,
0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc,
0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, 0xe2937171,
0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7,
0x46652323, 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505,
0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2,
0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909,
0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, 0xdcb26e6e,
0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6,
0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484,
0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020,
0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb,
0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858,
0x854acfcf, 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb,
0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545,
0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c,
0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, 0x80c04040,
0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5,
0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, 0x20301010,
0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c,
0x26351313, 0xc32fecec, 0xbee15f5f, 0x35a29797, 0x88cc4444,
0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d,
0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060,
0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a,
0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8,
0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb,
0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949,
0x0c0a0606, 0x486c2424, 0xb8e45c5c, 0x9f5dc2c2, 0xbd6ed3d3,
0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4,
0xf28b7979, 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d,
0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c,
0xacfa5656, 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a,
0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525,
0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6,
0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, 0x96dd4b4b,
0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e,
0x71c4b5b5, 0xccaa6666, 0x90d84848, 0x06050303, 0xf701f6f6,
0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9,
0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, 0xd938e1e1,
0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9,
0x07898e8e, 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787,
0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf,
0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf,
0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, 0x29b09999,
0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb,
0x2c3a1616
};
private static readonly uint[] Tinv0 =
{
0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b,
0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad,
0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526,
0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d,
0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03,
0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458,
0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899,
0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d,
0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1,
0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f,
0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3,
0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3,
0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a,
0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506,
0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05,
0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd,
0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491,
0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6,
0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7,
0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000,
0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd,
0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68,
0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4,
0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c,
0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e,
0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af,
0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644,
0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8,
0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85,
0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc,
0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411,
0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322,
0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6,
0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850,
0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e,
0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf,
0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd,
0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa,
0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea,
0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235,
0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1,
0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43,
0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1,
0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb,
0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a,
0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7,
0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418,
0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478,
0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16,
0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08,
0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48,
0x4257b8d0
};
private static readonly uint[] Tinv1 =
{
0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb,
0x459d1ff1, 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6,
0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680,
0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1,
0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, 0x5f8f03e7,
0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3,
0x69e04929, 0xc8c98e44, 0x89c2756a, 0x798ef478, 0x3e58996b,
0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4,
0x4adf6318, 0x311ae582, 0x33519760, 0x7f536245, 0x7764b1e0,
0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19,
0x6cde9487, 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357,
0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5,
0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b,
0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, 0xbe0506d5,
0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532,
0xebf6a475, 0xec830b39, 0xef6040aa, 0x9f715e06, 0x106ebd51,
0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5,
0x5dc47105, 0xd406046f, 0x155060ff, 0xfb981924, 0xe9bdd697,
0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738,
0xeec879db, 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000,
0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb,
0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821,
0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, 0x96eeb4d2,
0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16,
0xba93e20a, 0x2aa0c0e5, 0xe0223c43, 0x171b121d, 0x0d090e0b,
0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c,
0xdd99eebb, 0x607fa3fd, 0x2601f79f, 0xf5725cbc, 0x3b6644c5,
0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863,
0xdc31d7ca, 0x85634210, 0x22971340, 0x11c68420, 0x244a857d,
0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3,
0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa,
0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, 0x903322ef,
0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf,
0xde7aa528, 0x8eb7da26, 0xbfad3fa4, 0x9d3a2ce4, 0x9278500d,
0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e,
0xafc382f5, 0x805d9fbe, 0x93d0697c, 0x2dd56fa9, 0x1225cfb3,
0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09,
0x18596ef4, 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e,
0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4,
0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0,
0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, 0x9804f14a,
0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d,
0x4daacc54, 0x0496e4df, 0xb5d19ee3, 0x886a4c1b, 0x1f2cc1b8,
0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e,
0x1d67b35a, 0xd2db9252, 0x5610e933, 0x47d66d13, 0x61d79a8c,
0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735,
0xe51ce1ed, 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879,
0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886,
0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672,
0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, 0xb30c08de,
0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874,
0x57b8d042
};
private static readonly uint[] Tinv2 =
{
0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b,
0x9d1ff145, 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d,
0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044,
0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0,
0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, 0x8f03e75f,
0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321,
0xe0492969, 0xc98e44c8, 0xc2756a89, 0x8ef47879, 0x58996b3e,
0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a,
0xdf63184a, 0x1ae58231, 0x51976033, 0x5362457f, 0x64b1e077,
0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd,
0xde94876c, 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f,
0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508,
0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c,
0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, 0x0506d5be,
0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1,
0xf6a475eb, 0x830b39ec, 0x6040aaef, 0x715e069f, 0x6ebd5110,
0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d,
0xc471055d, 0x06046fd4, 0x5060ff15, 0x981924fb, 0xbdd697e9,
0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b,
0xc879dbee, 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000,
0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff,
0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6,
0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, 0xeeb4d296,
0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a,
0x93e20aba, 0xa0c0e52a, 0x223c43e0, 0x1b121d17, 0x090e0b0d,
0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07,
0x99eebbdd, 0x7fa3fd60, 0x01f79f26, 0x725cbcf5, 0x6644c53b,
0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1,
0x31d7cadc, 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24,
0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330,
0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48,
0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, 0x3322ef90,
0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81,
0x7aa528de, 0xb7da268e, 0xad3fa4bf, 0x3a2ce49d, 0x78500d92,
0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7,
0xc382f5af, 0x5d9fbe80, 0xd0697c93, 0xd56fa92d, 0x25cfb312,
0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978,
0x596ef418, 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6,
0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409,
0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066,
0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, 0x04f14a98,
0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0,
0xaacc544d, 0x96e4df04, 0xd19ee3b5, 0x6a4c1b88, 0x2cc1b81f,
0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41,
0x67b35a1d, 0xdb9252d2, 0x10e93356, 0xd66d1347, 0xd79a8c61,
0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9,
0x1ce1ede5, 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce,
0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db,
0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3,
0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, 0x0c08deb3,
0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c,
0xb8d04257
};
private static readonly uint[] Tinv3 =
{
0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab,
0x1ff1459d, 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76,
0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435,
0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe,
0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, 0x03e75f8f,
0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174,
0x492969e0, 0x8e44c8c9, 0x756a89c2, 0xf478798e, 0x996b3e58,
0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace,
0x63184adf, 0xe582311a, 0x97603351, 0x62457f53, 0xb1e07764,
0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45,
0x94876cde, 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f,
0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837,
0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf,
0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, 0x06d5be05,
0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a,
0xa475ebf6, 0x0b39ec83, 0x40aaef60, 0x5e069f71, 0xbd51106e,
0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54,
0x71055dc4, 0x046fd406, 0x60ff1550, 0x1924fb98, 0xd697e9bd,
0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19,
0x79dbeec8, 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000,
0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e,
0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c,
0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, 0xb4d296ee,
0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12,
0xe20aba93, 0xc0e52aa0, 0x3c43e022, 0x121d171b, 0x0e0b0d09,
0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775,
0xeebbdd99, 0xa3fd607f, 0xf79f2601, 0x5cbcf572, 0x44c53b66,
0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4,
0xd7cadc31, 0x42108563, 0x13402297, 0x842011c6, 0x857d244a,
0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2,
0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894,
0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, 0x22ef9033,
0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5,
0xa528de7a, 0xda268eb7, 0x3fa4bfad, 0x2ce49d3a, 0x500d9278,
0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739,
0x82f5afc3, 0x9fbe805d, 0x697c93d0, 0x6fa92dd5, 0xcfb31225,
0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826,
0x6ef41859, 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff,
0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f,
0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2,
0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, 0xf14a9804,
0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef,
0xcc544daa, 0xe4df0496, 0x9ee3b5d1, 0x4c1b886a, 0xc1b81f2c,
0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b,
0xb35a1d67, 0x9252d2db, 0xe9335610, 0x6d1347d6, 0x9a8c61d7,
0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961,
0xe1ede51c, 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14,
0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44,
0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d,
0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, 0x08deb30c,
0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c,
0xd04257b8
};
private static uint Shift(uint r, int shift)
{
return (r >> shift) | (r << (32 - shift));
}
/* multiply four bytes in GF(2^8) by 'x' {02} in parallel */
private const uint m1 = 0x80808080;
private const uint m2 = 0x7f7f7f7f;
private const uint m3 = 0x0000001b;
private static uint FFmulX(uint x)
{
return ((x & m2) << 1) ^ (((x & m1) >> 7) * m3);
}
/*
The following defines provide alternative definitions of FFmulX that might
give improved performance if a fast 32-bit multiply is not available.
private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); }
private static final int m4 = 0x1b1b1b1b;
private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); }
*/
private static uint Inv_Mcol(uint x)
{
uint f2 = FFmulX(x);
uint f4 = FFmulX(f2);
uint f8 = FFmulX(f4);
uint f9 = x ^ f8;
return f2 ^ f4 ^ f8 ^ Shift(f2 ^ f9, 8) ^ Shift(f4 ^ f9, 16) ^ Shift(f9, 24);
}
private static uint SubWord(uint x)
{
return (uint)S[x&255]
| (((uint)S[(x>>8)&255]) << 8)
| (((uint)S[(x>>16)&255]) << 16)
| (((uint)S[(x>>24)&255]) << 24);
}
/**
* Calculate the necessary round keys
* The number of calculations depends on key size and block size
* AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits
* This code is written assuming those are the only possible values
*/
private uint[][] GenerateWorkingKey(
byte[] key,
bool forEncryption)
{
int KC = key.Length / 4; // key length in words
if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.Length))
throw new ArgumentException("Key length not 128/192/256 bits.");
ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes
uint[][] W = new uint[ROUNDS + 1][]; // 4 words in a block
for (int i = 0; i <= ROUNDS; ++i)
{
W[i] = new uint[4];
}
//
// copy the key into the round key array
//
int t = 0;
for (int i = 0; i < key.Length; t++)
{
W[t >> 2][t & 3] = Pack.LE_To_UInt32(key, i);
i+=4;
}
//
// while not enough round key material calculated
// calculate new values
//
int k = (ROUNDS + 1) << 2;
for (int i = KC; (i < k); i++)
{
uint temp = W[(i-1)>>2][(i-1)&3];
if ((i % KC) == 0) {
temp = SubWord(Shift(temp, 8)) ^ rcon[(i / KC)-1];
} else if ((KC > 6) && ((i % KC) == 4)) {
temp = SubWord(temp);
}
W[i>>2][i&3] = W[(i - KC)>>2][(i-KC)&3] ^ temp;
}
if (!forEncryption)
{
for (int j = 1; j < ROUNDS; j++)
{
uint[] w = W[j];
for (int i = 0; i < 4; i++)
{
w[i] = Inv_Mcol(w[i]);
}
}
}
return W;
}
private int ROUNDS;
private uint[][] WorkingKey;
private uint C0, C1, C2, C3;
private bool forEncryption;
private const int BLOCK_SIZE = 16;
/**
* default constructor - 128 bit block size.
*/
public AesFastEngine()
{
}
/**
* initialise an AES cipher.
*
* @param forEncryption whether or not we are for encryption.
* @param parameters the parameters required to set up the cipher.
* @exception ArgumentException if the parameters argument is
* inappropriate.
*/
public virtual void Init(
bool forEncryption,
ICipherParameters parameters)
{
KeyParameter keyParameter = parameters as KeyParameter;
if (keyParameter == null)
throw new ArgumentException("invalid parameter passed to AES init - " + parameters.GetType().Name);
WorkingKey = GenerateWorkingKey(keyParameter.GetKey(), forEncryption);
this.forEncryption = forEncryption;
}
public virtual string AlgorithmName
{
get { return "AES"; }
}
public virtual bool IsPartialBlockOkay
{
get { return false; }
}
public virtual int GetBlockSize()
{
return BLOCK_SIZE;
}
public virtual int ProcessBlock(
byte[] input,
int inOff,
byte[] output,
int outOff)
{
if (WorkingKey == null)
throw new InvalidOperationException("AES engine not initialised");
Check.DataLength(input, inOff, 16, "input buffer too short");
Check.OutputLength(output, outOff, 16, "output buffer too short");
UnPackBlock(input, inOff);
if (forEncryption)
{
EncryptBlock(WorkingKey);
}
else
{
DecryptBlock(WorkingKey);
}
PackBlock(output, outOff);
return BLOCK_SIZE;
}
public virtual void Reset()
{
}
private void UnPackBlock(
byte[] bytes,
int off)
{
C0 = Pack.LE_To_UInt32(bytes, off);
C1 = Pack.LE_To_UInt32(bytes, off + 4);
C2 = Pack.LE_To_UInt32(bytes, off + 8);
C3 = Pack.LE_To_UInt32(bytes, off + 12);
}
private void PackBlock(
byte[] bytes,
int off)
{
Pack.UInt32_To_LE(C0, bytes, off);
Pack.UInt32_To_LE(C1, bytes, off + 4);
Pack.UInt32_To_LE(C2, bytes, off + 8);
Pack.UInt32_To_LE(C3, bytes, off + 12);
}
private void EncryptBlock(uint[][] KW)
{
uint[] kw = KW[0];
uint t0 = this.C0 ^ kw[0];
uint t1 = this.C1 ^ kw[1];
uint t2 = this.C2 ^ kw[2];
uint r0, r1, r2, r3 = this.C3 ^ kw[3];
int r = 1;
while (r < ROUNDS - 1)
{
kw = KW[r++];
r0 = T0[t0 & 255] ^ T1[(t1 >> 8) & 255] ^ T2[(t2 >> 16) & 255] ^ T3[r3 >> 24] ^ kw[0];
r1 = T0[t1 & 255] ^ T1[(t2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[t0 >> 24] ^ kw[1];
r2 = T0[t2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(t0 >> 16) & 255] ^ T3[t1 >> 24] ^ kw[2];
r3 = T0[r3 & 255] ^ T1[(t0 >> 8) & 255] ^ T2[(t1 >> 16) & 255] ^ T3[t2 >> 24] ^ kw[3];
kw = KW[r++];
t0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[r3 >> 24] ^ kw[0];
t1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[r0 >> 24] ^ kw[1];
t2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[r1 >> 24] ^ kw[2];
r3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[r2 >> 24] ^ kw[3];
}
kw = KW[r++];
r0 = T0[t0 & 255] ^ T1[(t1 >> 8) & 255] ^ T2[(t2 >> 16) & 255] ^ T3[r3 >> 24] ^ kw[0];
r1 = T0[t1 & 255] ^ T1[(t2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[t0 >> 24] ^ kw[1];
r2 = T0[t2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(t0 >> 16) & 255] ^ T3[t1 >> 24] ^ kw[2];
r3 = T0[r3 & 255] ^ T1[(t0 >> 8) & 255] ^ T2[(t1 >> 16) & 255] ^ T3[t2 >> 24] ^ kw[3];
// the final round's table is a simple function of S so we don't use a whole other four tables for it
kw = KW[r];
this.C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[r3 >> 24]) << 24) ^ kw[0];
this.C1 = (uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[r0 >> 24]) << 24) ^ kw[1];
this.C2 = (uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[r1 >> 24]) << 24) ^ kw[2];
this.C3 = (uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[r2 >> 24]) << 24) ^ kw[3];
}
private void DecryptBlock(uint[][] KW)
{
uint[] kw = KW[ROUNDS];
uint t0 = this.C0 ^ kw[0];
uint t1 = this.C1 ^ kw[1];
uint t2 = this.C2 ^ kw[2];
uint r0, r1, r2, r3 = this.C3 ^ kw[3];
int r = ROUNDS - 1;
while (r > 1)
{
kw = KW[r--];
r0 = Tinv0[t0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(t2 >> 16) & 255] ^ Tinv3[t1 >> 24] ^ kw[0];
r1 = Tinv0[t1 & 255] ^ Tinv1[(t0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[t2 >> 24] ^ kw[1];
r2 = Tinv0[t2 & 255] ^ Tinv1[(t1 >> 8) & 255] ^ Tinv2[(t0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ kw[2];
r3 = Tinv0[r3 & 255] ^ Tinv1[(t2 >> 8) & 255] ^ Tinv2[(t1 >> 16) & 255] ^ Tinv3[t0 >> 24] ^ kw[3];
kw = KW[r--];
t0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[r1 >> 24] ^ kw[0];
t1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[r2 >> 24] ^ kw[1];
t2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ kw[2];
r3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[r0 >> 24] ^ kw[3];
}
kw = KW[1];
r0 = Tinv0[t0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(t2 >> 16) & 255] ^ Tinv3[t1 >> 24] ^ kw[0];
r1 = Tinv0[t1 & 255] ^ Tinv1[(t0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[t2 >> 24] ^ kw[1];
r2 = Tinv0[t2 & 255] ^ Tinv1[(t1 >> 8) & 255] ^ Tinv2[(t0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ kw[2];
r3 = Tinv0[r3 & 255] ^ Tinv1[(t2 >> 8) & 255] ^ Tinv2[(t1 >> 16) & 255] ^ Tinv3[t0 >> 24] ^ kw[3];
// the final round's table is a simple function of Si so we don't use a whole other four tables for it
kw = KW[0];
this.C0 = (uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[r1 >> 24]) << 24) ^ kw[0];
this.C1 = (uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)Si[r2 >> 24]) << 24) ^ kw[1];
this.C2 = (uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)Si[r3 >> 24]) << 24) ^ kw[2];
this.C3 = (uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ (((uint)Si[r0 >> 24]) << 24) ^ kw[3];
}
}
}

View file

@ -0,0 +1,367 @@
using System;
using System.Diagnostics;
using Org.BouncyCastle.Crypto.Parameters;
namespace Org.BouncyCastle.Crypto
{
/**
* A wrapper class that allows block ciphers to be used to process data in
* a piecemeal fashion. The BufferedBlockCipher outputs a block only when the
* buffer is full and more data is being added, or on a doFinal.
* <p>
* Note: in the case where the underlying cipher is either a CFB cipher or an
* OFB one the last block may not be a multiple of the block size.
* </p>
*/
public class BufferedBlockCipher
: BufferedCipherBase
{
internal byte[] buf;
internal int bufOff;
internal bool forEncryption;
internal IBlockCipher cipher;
/**
* constructor for subclasses
*/
protected BufferedBlockCipher()
{
}
/**
* Create a buffered block cipher without padding.
*
* @param cipher the underlying block cipher this buffering object wraps.
* false otherwise.
*/
public BufferedBlockCipher(
IBlockCipher cipher)
{
if (cipher == null)
throw new ArgumentNullException("cipher");
this.cipher = cipher;
buf = new byte[cipher.GetBlockSize()];
bufOff = 0;
}
public override string AlgorithmName
{
get { return cipher.AlgorithmName; }
}
/**
* initialise the cipher.
*
* @param forEncryption if true the cipher is initialised for
* encryption, if false for decryption.
* @param param the key and other data required by the cipher.
* @exception ArgumentException if the parameters argument is
* inappropriate.
*/
// Note: This doubles as the Init in the event that this cipher is being used as an IWrapper
public override void Init(
bool forEncryption,
ICipherParameters parameters)
{
this.forEncryption = forEncryption;
//ParametersWithRandom pwr = parameters as ParametersWithRandom;
//if (pwr != null)
// parameters = pwr.Parameters;
Reset();
cipher.Init(forEncryption, parameters);
}
/**
* return the blocksize for the underlying cipher.
*
* @return the blocksize for the underlying cipher.
*/
public override int GetBlockSize()
{
return cipher.GetBlockSize();
}
/**
* return the size of the output buffer required for an update
* an input of len bytes.
*
* @param len the length of the input.
* @return the space required to accommodate a call to update
* with len bytes of input.
*/
public override int GetUpdateOutputSize(
int length)
{
int total = length + bufOff;
int leftOver = total % buf.Length;
return total - leftOver;
}
/**
* return the size of the output buffer required for an update plus a
* doFinal with an input of len bytes.
*
* @param len the length of the input.
* @return the space required to accommodate a call to update and doFinal
* with len bytes of input.
*/
public override int GetOutputSize(
int length)
{
// Note: Can assume IsPartialBlockOkay is true for purposes of this calculation
return length + bufOff;
}
/**
* process a single byte, producing an output block if necessary.
*
* @param in the input byte.
* @param out the space for any output that might be produced.
* @param outOff the offset from which the output will be copied.
* @return the number of output bytes copied to out.
* @exception DataLengthException if there isn't enough space in out.
* @exception InvalidOperationException if the cipher isn't initialised.
*/
public override int ProcessByte(
byte input,
byte[] output,
int outOff)
{
buf[bufOff++] = input;
if (bufOff == buf.Length)
{
if ((outOff + buf.Length) > output.Length)
throw new DataLengthException("output buffer too short");
bufOff = 0;
return cipher.ProcessBlock(buf, 0, output, outOff);
}
return 0;
}
public override byte[] ProcessByte(
byte input)
{
int outLength = GetUpdateOutputSize(1);
byte[] outBytes = outLength > 0 ? new byte[outLength] : null;
int pos = ProcessByte(input, outBytes, 0);
if (outLength > 0 && pos < outLength)
{
byte[] tmp = new byte[pos];
Array.Copy(outBytes, 0, tmp, 0, pos);
outBytes = tmp;
}
return outBytes;
}
public override byte[] ProcessBytes(
byte[] input,
int inOff,
int length)
{
if (input == null)
throw new ArgumentNullException("input");
if (length < 1)
return null;
int outLength = GetUpdateOutputSize(length);
byte[] outBytes = outLength > 0 ? new byte[outLength] : null;
int pos = ProcessBytes(input, inOff, length, outBytes, 0);
if (outLength > 0 && pos < outLength)
{
byte[] tmp = new byte[pos];
Array.Copy(outBytes, 0, tmp, 0, pos);
outBytes = tmp;
}
return outBytes;
}
/**
* process an array of bytes, producing output if necessary.
*
* @param in the input byte array.
* @param inOff the offset at which the input data starts.
* @param len the number of bytes to be copied out of the input array.
* @param out the space for any output that might be produced.
* @param outOff the offset from which the output will be copied.
* @return the number of output bytes copied to out.
* @exception DataLengthException if there isn't enough space in out.
* @exception InvalidOperationException if the cipher isn't initialised.
*/
public override int ProcessBytes(
byte[] input,
int inOff,
int length,
byte[] output,
int outOff)
{
if (length < 1)
{
if (length < 0)
throw new ArgumentException("Can't have a negative input length!");
return 0;
}
int blockSize = GetBlockSize();
int outLength = GetUpdateOutputSize(length);
if (outLength > 0)
{
Check.OutputLength(output, outOff, outLength, "output buffer too short");
}
int resultLen = 0;
int gapLen = buf.Length - bufOff;
if (length > gapLen)
{
Array.Copy(input, inOff, buf, bufOff, gapLen);
resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
bufOff = 0;
length -= gapLen;
inOff += gapLen;
while (length > buf.Length)
{
resultLen += cipher.ProcessBlock(input, inOff, output, outOff + resultLen);
length -= blockSize;
inOff += blockSize;
}
}
Array.Copy(input, inOff, buf, bufOff, length);
bufOff += length;
if (bufOff == buf.Length)
{
resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen);
bufOff = 0;
}
return resultLen;
}
public override byte[] DoFinal()
{
byte[] outBytes = EmptyBuffer;
int length = GetOutputSize(0);
if (length > 0)
{
outBytes = new byte[length];
int pos = DoFinal(outBytes, 0);
if (pos < outBytes.Length)
{
byte[] tmp = new byte[pos];
Array.Copy(outBytes, 0, tmp, 0, pos);
outBytes = tmp;
}
}
else
{
Reset();
}
return outBytes;
}
public override byte[] DoFinal(
byte[] input,
int inOff,
int inLen)
{
if (input == null)
throw new ArgumentNullException("input");
int length = GetOutputSize(inLen);
byte[] outBytes = EmptyBuffer;
if (length > 0)
{
outBytes = new byte[length];
int pos = (inLen > 0)
? ProcessBytes(input, inOff, inLen, outBytes, 0)
: 0;
pos += DoFinal(outBytes, pos);
if (pos < outBytes.Length)
{
byte[] tmp = new byte[pos];
Array.Copy(outBytes, 0, tmp, 0, pos);
outBytes = tmp;
}
}
else
{
Reset();
}
return outBytes;
}
/**
* Process the last block in the buffer.
*
* @param out the array the block currently being held is copied into.
* @param outOff the offset at which the copying starts.
* @return the number of output bytes copied to out.
* @exception DataLengthException if there is insufficient space in out for
* the output, or the input is not block size aligned and should be.
* @exception InvalidOperationException if the underlying cipher is not
* initialised.
* @exception InvalidCipherTextException if padding is expected and not found.
* @exception DataLengthException if the input is not block size
* aligned.
*/
public override int DoFinal(
byte[] output,
int outOff)
{
try
{
if (bufOff != 0)
{
Check.DataLength(!cipher.IsPartialBlockOkay, "data not block size aligned");
Check.OutputLength(output, outOff, bufOff, "output buffer too short for DoFinal()");
// NB: Can't copy directly, or we may write too much output
cipher.ProcessBlock(buf, 0, buf, 0);
Array.Copy(buf, 0, output, outOff, bufOff);
}
return bufOff;
}
finally
{
Reset();
}
}
/**
* Reset the buffer and cipher. After resetting the object is in the same
* state as it was after the last init (if there was one).
*/
public override void Reset()
{
Array.Clear(buf, 0, buf.Length);
bufOff = 0;
cipher.Reset();
}
}
}

View file

@ -0,0 +1,113 @@
using System;
namespace Org.BouncyCastle.Crypto
{
public abstract class BufferedCipherBase
: IBufferedCipher
{
protected static readonly byte[] EmptyBuffer = new byte[0];
public abstract string AlgorithmName { get; }
public abstract void Init(bool forEncryption, ICipherParameters parameters);
public abstract int GetBlockSize();
public abstract int GetOutputSize(int inputLen);
public abstract int GetUpdateOutputSize(int inputLen);
public abstract byte[] ProcessByte(byte input);
public virtual int ProcessByte(
byte input,
byte[] output,
int outOff)
{
byte[] outBytes = ProcessByte(input);
if (outBytes == null)
return 0;
if (outOff + outBytes.Length > output.Length)
throw new DataLengthException("output buffer too short");
outBytes.CopyTo(output, outOff);
return outBytes.Length;
}
public virtual byte[] ProcessBytes(
byte[] input)
{
return ProcessBytes(input, 0, input.Length);
}
public abstract byte[] ProcessBytes(byte[] input, int inOff, int length);
public virtual int ProcessBytes(
byte[] input,
byte[] output,
int outOff)
{
return ProcessBytes(input, 0, input.Length, output, outOff);
}
public virtual int ProcessBytes(
byte[] input,
int inOff,
int length,
byte[] output,
int outOff)
{
byte[] outBytes = ProcessBytes(input, inOff, length);
if (outBytes == null)
return 0;
if (outOff + outBytes.Length > output.Length)
throw new DataLengthException("output buffer too short");
outBytes.CopyTo(output, outOff);
return outBytes.Length;
}
public abstract byte[] DoFinal();
public virtual byte[] DoFinal(
byte[] input)
{
return DoFinal(input, 0, input.Length);
}
public abstract byte[] DoFinal(
byte[] input,
int inOff,
int length);
public virtual int DoFinal(
byte[] output,
int outOff)
{
byte[] outBytes = DoFinal();
if (outOff + outBytes.Length > output.Length)
throw new DataLengthException("output buffer too short");
outBytes.CopyTo(output, outOff);
return outBytes.Length;
}
public virtual int DoFinal(
byte[] input,
byte[] output,
int outOff)
{
return DoFinal(input, 0, input.Length, output, outOff);
}
public virtual int DoFinal(
byte[] input,
int inOff,
int length,
byte[] output,
int outOff)
{
int len = ProcessBytes(input, inOff, length, output, outOff);
len += DoFinal(output, outOff + len);
return len;
}
public abstract void Reset();
}
}

View file

@ -0,0 +1,224 @@
using System;
using Org.BouncyCastle.Crypto.Parameters;
namespace Org.BouncyCastle.Crypto.Modes
{
/**
* implements a Cipher-FeedBack (CFB) mode on top of a simple cipher.
*/
public class CfbBlockCipher
: IBlockCipher
{
private byte[] IV;
private byte[] cfbV;
private byte[] cfbOutV;
private bool encrypting;
private readonly int blockSize;
private readonly IBlockCipher cipher;
/**
* Basic constructor.
*
* @param cipher the block cipher to be used as the basis of the
* feedback mode.
* @param blockSize the block size in bits (note: a multiple of 8)
*/
public CfbBlockCipher(
IBlockCipher cipher,
int bitBlockSize)
{
this.cipher = cipher;
this.blockSize = bitBlockSize / 8;
this.IV = new byte[cipher.GetBlockSize()];
this.cfbV = new byte[cipher.GetBlockSize()];
this.cfbOutV = new byte[cipher.GetBlockSize()];
}
/**
* return the underlying block cipher that we are wrapping.
*
* @return the underlying block cipher that we are wrapping.
*/
public IBlockCipher GetUnderlyingCipher()
{
return cipher;
}
/**
* Initialise the cipher and, possibly, the initialisation vector (IV).
* If an IV isn't passed as part of the parameter, the IV will be all zeros.
* An IV which is too short is handled in FIPS compliant fashion.
*
* @param forEncryption if true the cipher is initialised for
* encryption, if false for decryption.
* @param param the key and other data required by the cipher.
* @exception ArgumentException if the parameters argument is
* inappropriate.
*/
public void Init(
bool forEncryption,
ICipherParameters parameters)
{
this.encrypting = forEncryption;
if (parameters is ParametersWithIV)
{
ParametersWithIV ivParam = (ParametersWithIV) parameters;
byte[] iv = ivParam.GetIV();
int diff = IV.Length - iv.Length;
Array.Copy(iv, 0, IV, diff, iv.Length);
Array.Clear(IV, 0, diff);
parameters = ivParam.Parameters;
}
Reset();
// if it's null, key is to be reused.
if (parameters != null)
{
cipher.Init(true, parameters);
}
}
/**
* return the algorithm name and mode.
*
* @return the name of the underlying algorithm followed by "/CFB"
* and the block size in bits.
*/
public string AlgorithmName
{
get { return cipher.AlgorithmName + "/CFB" + (blockSize * 8); }
}
public bool IsPartialBlockOkay
{
get { return true; }
}
/**
* return the block size we are operating at.
*
* @return the block size we are operating at (in bytes).
*/
public int GetBlockSize()
{
return blockSize;
}
/**
* Process one block of input from the array in and write it to
* the out array.
*
* @param in the array containing the input data.
* @param inOff offset into the in array the data starts at.
* @param out the array the output data will be copied into.
* @param outOff the offset into the out array the output will start at.
* @exception DataLengthException if there isn't enough data in in, or
* space in out.
* @exception InvalidOperationException if the cipher isn't initialised.
* @return the number of bytes processed and produced.
*/
public int ProcessBlock(
byte[] input,
int inOff,
byte[] output,
int outOff)
{
return (encrypting)
? EncryptBlock(input, inOff, output, outOff)
: DecryptBlock(input, inOff, output, outOff);
}
/**
* Do the appropriate processing for CFB mode encryption.
*
* @param in the array containing the data to be encrypted.
* @param inOff offset into the in array the data starts at.
* @param out the array the encrypted data will be copied into.
* @param outOff the offset into the out array the output will start at.
* @exception DataLengthException if there isn't enough data in in, or
* space in out.
* @exception InvalidOperationException if the cipher isn't initialised.
* @return the number of bytes processed and produced.
*/
public int EncryptBlock(
byte[] input,
int inOff,
byte[] outBytes,
int outOff)
{
if ((inOff + blockSize) > input.Length)
{
throw new DataLengthException("input buffer too short");
}
if ((outOff + blockSize) > outBytes.Length)
{
throw new DataLengthException("output buffer too short");
}
cipher.ProcessBlock(cfbV, 0, cfbOutV, 0);
//
// XOR the cfbV with the plaintext producing the ciphertext
//
for (int i = 0; i < blockSize; i++)
{
outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]);
}
//
// change over the input block.
//
Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize);
Array.Copy(outBytes, outOff, cfbV, cfbV.Length - blockSize, blockSize);
return blockSize;
}
/**
* Do the appropriate processing for CFB mode decryption.
*
* @param in the array containing the data to be decrypted.
* @param inOff offset into the in array the data starts at.
* @param out the array the encrypted data will be copied into.
* @param outOff the offset into the out array the output will start at.
* @exception DataLengthException if there isn't enough data in in, or
* space in out.
* @exception InvalidOperationException if the cipher isn't initialised.
* @return the number of bytes processed and produced.
*/
public int DecryptBlock(
byte[] input,
int inOff,
byte[] outBytes,
int outOff)
{
if ((inOff + blockSize) > input.Length)
{
throw new DataLengthException("input buffer too short");
}
if ((outOff + blockSize) > outBytes.Length)
{
throw new DataLengthException("output buffer too short");
}
cipher.ProcessBlock(cfbV, 0, cfbOutV, 0);
//
// change over the input block.
//
Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize);
Array.Copy(input, inOff, cfbV, cfbV.Length - blockSize, blockSize);
//
// XOR the cfbV with the ciphertext producing the plaintext
//
for (int i = 0; i < blockSize; i++)
{
outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]);
}
return blockSize;
}
/**
* reset the chaining vector back to the IV and reset the underlying
* cipher.
*/
public void Reset()
{
Array.Copy(IV, 0, cfbV, 0, IV.Length);
cipher.Reset();
}
}
}

View file

@ -0,0 +1,25 @@
using System;
namespace Org.BouncyCastle.Crypto
{
internal class Check
{
internal static void DataLength(bool condition, string msg)
{
if (condition)
throw new DataLengthException(msg);
}
internal static void DataLength(byte[] buf, int off, int len, string msg)
{
if (off + len > buf.Length)
throw new DataLengthException(msg);
}
internal static void OutputLength(byte[] buf, int off, int len, string msg)
{
if (off + len > buf.Length)
throw new OutputLengthException(msg);
}
}
}

View file

@ -0,0 +1,234 @@
using System;
using System.Diagnostics;
using System.IO;
using Org.BouncyCastle.Crypto;
namespace Org.BouncyCastle.Crypto.IO
{
public class CipherStream
: Stream
{
internal Stream stream;
internal IBufferedCipher inCipher, outCipher;
private byte[] mInBuf;
private int mInPos;
private bool inStreamEnded;
public CipherStream(
Stream stream,
IBufferedCipher readCipher,
IBufferedCipher writeCipher)
{
this.stream = stream;
if (readCipher != null)
{
this.inCipher = readCipher;
mInBuf = null;
}
if (writeCipher != null)
{
this.outCipher = writeCipher;
}
}
public IBufferedCipher ReadCipher
{
get { return inCipher; }
}
public IBufferedCipher WriteCipher
{
get { return outCipher; }
}
public override int ReadByte()
{
if (inCipher == null)
return stream.ReadByte();
if (mInBuf == null || mInPos >= mInBuf.Length)
{
if (!FillInBuf())
return -1;
}
return mInBuf[mInPos++];
}
public override int Read(
byte[] buffer,
int offset,
int count)
{
if (inCipher == null)
return stream.Read(buffer, offset, count);
int num = 0;
while (num < count)
{
if (mInBuf == null || mInPos >= mInBuf.Length)
{
if (!FillInBuf())
break;
}
int numToCopy = System.Math.Min(count - num, mInBuf.Length - mInPos);
Array.Copy(mInBuf, mInPos, buffer, offset + num, numToCopy);
mInPos += numToCopy;
num += numToCopy;
}
return num;
}
private bool FillInBuf()
{
if (inStreamEnded)
return false;
mInPos = 0;
do
{
mInBuf = ReadAndProcessBlock();
}
while (!inStreamEnded && mInBuf == null);
return mInBuf != null;
}
private byte[] ReadAndProcessBlock()
{
int blockSize = inCipher.GetBlockSize();
int readSize = (blockSize == 0) ? 256 : blockSize;
byte[] block = new byte[readSize];
int numRead = 0;
do
{
int count = stream.Read(block, numRead, block.Length - numRead);
if (count < 1)
{
inStreamEnded = true;
break;
}
numRead += count;
}
while (numRead < block.Length);
Debug.Assert(inStreamEnded || numRead == block.Length);
byte[] bytes = inStreamEnded
? inCipher.DoFinal(block, 0, numRead)
: inCipher.ProcessBytes(block);
if (bytes != null && bytes.Length == 0)
{
bytes = null;
}
return bytes;
}
public override void Write(
byte[] buffer,
int offset,
int count)
{
Debug.Assert(buffer != null);
Debug.Assert(0 <= offset && offset <= buffer.Length);
Debug.Assert(count >= 0);
int end = offset + count;
Debug.Assert(0 <= end && end <= buffer.Length);
if (outCipher == null)
{
stream.Write(buffer, offset, count);
return;
}
byte[] data = outCipher.ProcessBytes(buffer, offset, count);
if (data != null)
{
stream.Write(data, 0, data.Length);
}
}
public override void WriteByte(
byte b)
{
if (outCipher == null)
{
stream.WriteByte(b);
return;
}
byte[] data = outCipher.ProcessByte(b);
if (data != null)
{
stream.Write(data, 0, data.Length);
}
}
public override bool CanRead
{
get { return stream.CanRead && (inCipher != null); }
}
public override bool CanWrite
{
get { return stream.CanWrite && (outCipher != null); }
}
public override bool CanSeek
{
get { return false; }
}
public sealed override long Length
{
get { throw new NotSupportedException(); }
}
public sealed override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override void Close()
{
if (outCipher != null)
{
byte[] data = outCipher.DoFinal();
stream.Write(data, 0, data.Length);
stream.Flush();
}
stream.Close();
}
public override void Flush()
{
// Note: outCipher.DoFinal is only called during Close()
stream.Flush();
}
public sealed override long Seek(
long offset,
SeekOrigin origin)
{
throw new NotSupportedException();
}
public sealed override void SetLength(
long length)
{
throw new NotSupportedException();
}
}
}

View file

@ -0,0 +1,28 @@
using System;
namespace Org.BouncyCastle.Crypto
{
#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT)
[Serializable]
#endif
public class CryptoException
: Exception
{
public CryptoException()
{
}
public CryptoException(
string message)
: base(message)
{
}
public CryptoException(
string message,
Exception exception)
: base(message, exception)
{
}
}
}

View file

@ -0,0 +1,42 @@
using System;
namespace Org.BouncyCastle.Crypto
{
/**
* this exception is thrown if a buffer that is meant to have output
* copied into it turns out to be too short, or if we've been given
* insufficient input. In general this exception will Get thrown rather
* than an ArrayOutOfBounds exception.
*/
#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT)
[Serializable]
#endif
public class DataLengthException
: CryptoException
{
/**
* base constructor.
*/
public DataLengthException()
{
}
/**
* create a DataLengthException with the given message.
*
* @param message the message to be carried with the exception.
*/
public DataLengthException(
string message)
: base(message)
{
}
public DataLengthException(
string message,
Exception exception)
: base(message, exception)
{
}
}
}

View file

@ -0,0 +1,36 @@
using System;
namespace Org.BouncyCastle.Crypto
{
/// <remarks>Base interface for a symmetric key block cipher.</remarks>
public interface IBlockCipher
{
/// <summary>The name of the algorithm this cipher implements.</summary>
string AlgorithmName { get; }
/// <summary>Initialise the cipher.</summary>
/// <param name="forEncryption">Initialise for encryption if true, for decryption if false.</param>
/// <param name="parameters">The key or other data required by the cipher.</param>
void Init(bool forEncryption, ICipherParameters parameters);
/// <returns>The block size for this cipher, in bytes.</returns>
int GetBlockSize();
/// <summary>Indicates whether this cipher can handle partial blocks.</summary>
bool IsPartialBlockOkay { get; }
/// <summary>Process a block.</summary>
/// <param name="inBuf">The input buffer.</param>
/// <param name="inOff">The offset into <paramref>inBuf</paramref> that the input block begins.</param>
/// <param name="outBuf">The output buffer.</param>
/// <param name="outOff">The offset into <paramref>outBuf</paramref> to write the output block.</param>
/// <exception cref="DataLengthException">If input block is wrong size, or outBuf too small.</exception>
/// <returns>The number of bytes processed and produced.</returns>
int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff);
/// <summary>
/// Reset the cipher to the same state as it was after the last init (if there was one).
/// </summary>
void Reset();
}
}

View file

@ -0,0 +1,44 @@
using System;
namespace Org.BouncyCastle.Crypto
{
/// <remarks>Block cipher engines are expected to conform to this interface.</remarks>
public interface IBufferedCipher
{
/// <summary>The name of the algorithm this cipher implements.</summary>
string AlgorithmName { get; }
/// <summary>Initialise the cipher.</summary>
/// <param name="forEncryption">If true the cipher is initialised for encryption,
/// if false for decryption.</param>
/// <param name="parameters">The key and other data required by the cipher.</param>
void Init(bool forEncryption, ICipherParameters parameters);
int GetBlockSize();
int GetOutputSize(int inputLen);
int GetUpdateOutputSize(int inputLen);
byte[] ProcessByte(byte input);
int ProcessByte(byte input, byte[] output, int outOff);
byte[] ProcessBytes(byte[] input);
byte[] ProcessBytes(byte[] input, int inOff, int length);
int ProcessBytes(byte[] input, byte[] output, int outOff);
int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff);
byte[] DoFinal();
byte[] DoFinal(byte[] input);
byte[] DoFinal(byte[] input, int inOff, int length);
int DoFinal(byte[] output, int outOff);
int DoFinal(byte[] input, byte[] output, int outOff);
int DoFinal(byte[] input, int inOff, int length, byte[] output, int outOff);
/// <summary>
/// Reset the cipher. After resetting the cipher is in the same state
/// as it was after the last init (if there was one).
/// </summary>
void Reset();
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Org.BouncyCastle.Crypto
{
/**
* all parameter classes implement this.
*/
public interface ICipherParameters
{
}
}

View file

@ -0,0 +1,43 @@
using System;
using Org.BouncyCastle.Crypto;
namespace Org.BouncyCastle.Crypto.Parameters
{
public class KeyParameter
: ICipherParameters
{
private readonly byte[] key;
public KeyParameter(
byte[] key)
{
if (key == null)
throw new ArgumentNullException("key");
this.key = (byte[]) key.Clone();
}
public KeyParameter(
byte[] key,
int keyOff,
int keyLen)
{
if (key == null)
throw new ArgumentNullException("key");
if (keyOff < 0 || keyOff > key.Length)
throw new ArgumentOutOfRangeException("keyOff");
if (keyLen < 0 || (keyOff + keyLen) > key.Length)
throw new ArgumentOutOfRangeException("keyLen");
this.key = new byte[keyLen];
Array.Copy(key, keyOff, this.key, 0, keyLen);
}
public byte[] GetKey()
{
return (byte[]) key.Clone();
}
}
}

View file

@ -0,0 +1,28 @@
using System;
namespace Org.BouncyCastle.Crypto
{
#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT)
[Serializable]
#endif
public class OutputLengthException
: DataLengthException
{
public OutputLengthException()
{
}
public OutputLengthException(
string message)
: base(message)
{
}
public OutputLengthException(
string message,
Exception exception)
: base(message, exception)
{
}
}
}

View file

@ -0,0 +1,266 @@
using System;
namespace Org.BouncyCastle.Crypto.Utilities
{
internal sealed class Pack
{
private Pack()
{
}
internal static void UInt16_To_BE(ushort n, byte[] bs)
{
bs[0] = (byte)(n >> 8);
bs[1] = (byte)(n);
}
internal static void UInt16_To_BE(ushort n, byte[] bs, int off)
{
bs[off] = (byte)(n >> 8);
bs[off + 1] = (byte)(n);
}
internal static ushort BE_To_UInt16(byte[] bs)
{
uint n = (uint)bs[0] << 8
| (uint)bs[1];
return (ushort)n;
}
internal static ushort BE_To_UInt16(byte[] bs, int off)
{
uint n = (uint)bs[off] << 8
| (uint)bs[off + 1];
return (ushort)n;
}
internal static byte[] UInt32_To_BE(uint n)
{
byte[] bs = new byte[4];
UInt32_To_BE(n, bs, 0);
return bs;
}
internal static void UInt32_To_BE(uint n, byte[] bs)
{
bs[0] = (byte)(n >> 24);
bs[1] = (byte)(n >> 16);
bs[2] = (byte)(n >> 8);
bs[3] = (byte)(n);
}
internal static void UInt32_To_BE(uint n, byte[] bs, int off)
{
bs[off] = (byte)(n >> 24);
bs[off + 1] = (byte)(n >> 16);
bs[off + 2] = (byte)(n >> 8);
bs[off + 3] = (byte)(n);
}
internal static byte[] UInt32_To_BE(uint[] ns)
{
byte[] bs = new byte[4 * ns.Length];
UInt32_To_BE(ns, bs, 0);
return bs;
}
internal static void UInt32_To_BE(uint[] ns, byte[] bs, int off)
{
for (int i = 0; i < ns.Length; ++i)
{
UInt32_To_BE(ns[i], bs, off);
off += 4;
}
}
internal static uint BE_To_UInt32(byte[] bs)
{
return (uint)bs[0] << 24
| (uint)bs[1] << 16
| (uint)bs[2] << 8
| (uint)bs[3];
}
internal static uint BE_To_UInt32(byte[] bs, int off)
{
return (uint)bs[off] << 24
| (uint)bs[off + 1] << 16
| (uint)bs[off + 2] << 8
| (uint)bs[off + 3];
}
internal static void BE_To_UInt32(byte[] bs, int off, uint[] ns)
{
for (int i = 0; i < ns.Length; ++i)
{
ns[i] = BE_To_UInt32(bs, off);
off += 4;
}
}
internal static byte[] UInt64_To_BE(ulong n)
{
byte[] bs = new byte[8];
UInt64_To_BE(n, bs, 0);
return bs;
}
internal static void UInt64_To_BE(ulong n, byte[] bs)
{
UInt32_To_BE((uint)(n >> 32), bs);
UInt32_To_BE((uint)(n), bs, 4);
}
internal static void UInt64_To_BE(ulong n, byte[] bs, int off)
{
UInt32_To_BE((uint)(n >> 32), bs, off);
UInt32_To_BE((uint)(n), bs, off + 4);
}
internal static ulong BE_To_UInt64(byte[] bs)
{
uint hi = BE_To_UInt32(bs);
uint lo = BE_To_UInt32(bs, 4);
return ((ulong)hi << 32) | (ulong)lo;
}
internal static ulong BE_To_UInt64(byte[] bs, int off)
{
uint hi = BE_To_UInt32(bs, off);
uint lo = BE_To_UInt32(bs, off + 4);
return ((ulong)hi << 32) | (ulong)lo;
}
internal static void UInt16_To_LE(ushort n, byte[] bs)
{
bs[0] = (byte)(n);
bs[1] = (byte)(n >> 8);
}
internal static void UInt16_To_LE(ushort n, byte[] bs, int off)
{
bs[off] = (byte)(n);
bs[off + 1] = (byte)(n >> 8);
}
internal static ushort LE_To_UInt16(byte[] bs)
{
uint n = (uint)bs[0]
| (uint)bs[1] << 8;
return (ushort)n;
}
internal static ushort LE_To_UInt16(byte[] bs, int off)
{
uint n = (uint)bs[off]
| (uint)bs[off + 1] << 8;
return (ushort)n;
}
internal static byte[] UInt32_To_LE(uint n)
{
byte[] bs = new byte[4];
UInt32_To_LE(n, bs, 0);
return bs;
}
internal static void UInt32_To_LE(uint n, byte[] bs)
{
bs[0] = (byte)(n);
bs[1] = (byte)(n >> 8);
bs[2] = (byte)(n >> 16);
bs[3] = (byte)(n >> 24);
}
internal static void UInt32_To_LE(uint n, byte[] bs, int off)
{
bs[off] = (byte)(n);
bs[off + 1] = (byte)(n >> 8);
bs[off + 2] = (byte)(n >> 16);
bs[off + 3] = (byte)(n >> 24);
}
internal static byte[] UInt32_To_LE(uint[] ns)
{
byte[] bs = new byte[4 * ns.Length];
UInt32_To_LE(ns, bs, 0);
return bs;
}
internal static void UInt32_To_LE(uint[] ns, byte[] bs, int off)
{
for (int i = 0; i < ns.Length; ++i)
{
UInt32_To_LE(ns[i], bs, off);
off += 4;
}
}
internal static uint LE_To_UInt32(byte[] bs)
{
return (uint)bs[0]
| (uint)bs[1] << 8
| (uint)bs[2] << 16
| (uint)bs[3] << 24;
}
internal static uint LE_To_UInt32(byte[] bs, int off)
{
return (uint)bs[off]
| (uint)bs[off + 1] << 8
| (uint)bs[off + 2] << 16
| (uint)bs[off + 3] << 24;
}
internal static void LE_To_UInt32(byte[] bs, int off, uint[] ns)
{
for (int i = 0; i < ns.Length; ++i)
{
ns[i] = LE_To_UInt32(bs, off);
off += 4;
}
}
internal static void LE_To_UInt32(byte[] bs, int bOff, uint[] ns, int nOff, int count)
{
for (int i = 0; i < count; ++i)
{
ns[nOff + i] = LE_To_UInt32(bs, bOff);
bOff += 4;
}
}
internal static byte[] UInt64_To_LE(ulong n)
{
byte[] bs = new byte[8];
UInt64_To_LE(n, bs, 0);
return bs;
}
internal static void UInt64_To_LE(ulong n, byte[] bs)
{
UInt32_To_LE((uint)(n), bs);
UInt32_To_LE((uint)(n >> 32), bs, 4);
}
internal static void UInt64_To_LE(ulong n, byte[] bs, int off)
{
UInt32_To_LE((uint)(n), bs, off);
UInt32_To_LE((uint)(n >> 32), bs, off + 4);
}
internal static ulong LE_To_UInt64(byte[] bs)
{
uint lo = LE_To_UInt32(bs);
uint hi = LE_To_UInt32(bs, 4);
return ((ulong)hi << 32) | (ulong)lo;
}
internal static ulong LE_To_UInt64(byte[] bs, int off)
{
uint lo = LE_To_UInt32(bs, off);
uint hi = LE_To_UInt32(bs, off + 4);
return ((ulong)hi << 32) | (ulong)lo;
}
}
}

View file

@ -0,0 +1,43 @@
using System;
namespace Org.BouncyCastle.Crypto.Parameters
{
public class ParametersWithIV
: ICipherParameters
{
private readonly ICipherParameters parameters;
private readonly byte[] iv;
public ParametersWithIV(
ICipherParameters parameters,
byte[] iv)
: this(parameters, iv, 0, iv.Length)
{
}
public ParametersWithIV(
ICipherParameters parameters,
byte[] iv,
int ivOff,
int ivLen)
{
// NOTE: 'parameters' may be null to imply key re-use
if (iv == null)
throw new ArgumentNullException("iv");
this.parameters = parameters;
this.iv = new byte[ivLen];
Array.Copy(iv, ivOff, this.iv, 0, ivLen);
}
public byte[] GetIV()
{
return (byte[]) iv.Clone();
}
public ICipherParameters Parameters
{
get { return parameters; }
}
}
}

View file

@ -4,29 +4,28 @@ using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.IO;
namespace MinecraftClient.Crypto.Streams
{
/// <summary>
/// An encrypted stream using AES, used for encrypting network data on the fly using AES.
/// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks.
/// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible.
/// This is a mono-compatible adaptation which uses AES engine from the BouncyCastle project.
/// </summary>
public class MonoAesStream : Stream, IAesStream
{
IPaddingProvider pad;
ICryptoTransform enc;
ICryptoTransform dec;
List<byte> dec_cache = new List<byte>();
List<byte> tosend_cache = new List<byte>();
public MonoAesStream(System.IO.Stream stream, byte[] key, IPaddingProvider provider)
CipherStream cstream;
public MonoAesStream(System.IO.Stream stream, byte[] key)
{
BaseStream = stream;
RijndaelManaged aes = GenerateAES(key);
enc = aes.CreateEncryptor();
dec = aes.CreateDecryptor();
pad = provider;
BufferedBlockCipher enc = GenerateAES(key, true);
BufferedBlockCipher dec = GenerateAES(key, false);
cstream = new CipherStream(stream, dec, enc);
}
public System.IO.Stream BaseStream { get; set; }
@ -76,25 +75,7 @@ namespace MinecraftClient.Crypto.Streams
public override int Read(byte[] buffer, int offset, int count)
{
while (dec_cache.Count < count)
{
byte[] temp_in = new byte[16];
byte[] temp_out = new byte[16];
int read = 0;
while (read < 16)
read += BaseStream.Read(temp_in, read, 16 - read);
dec.TransformBlock(temp_in, 0, 16, temp_out, 0);
foreach (byte b in temp_out)
dec_cache.Add(b);
}
for (int i = offset; i - offset < count; i++)
{
buffer[i] = dec_cache[0];
dec_cache.RemoveAt(0);
}
return count;
return cstream.Read(buffer, offset, count);
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
@ -114,35 +95,13 @@ namespace MinecraftClient.Crypto.Streams
public override void Write(byte[] buffer, int offset, int count)
{
for (int i = offset; i - offset < count; i++)
tosend_cache.Add(buffer[i]);
if (tosend_cache.Count < 16)
tosend_cache.AddRange(pad.getPaddingPacket());
while (tosend_cache.Count > 16)
{
byte[] temp_in = new byte[16];
byte[] temp_out = new byte[16];
for (int i = 0; i < 16; i++)
{
temp_in[i] = tosend_cache[0];
tosend_cache.RemoveAt(0);
}
enc.TransformBlock(temp_in, 0, 16, temp_out, 0);
BaseStream.Write(temp_out, 0, 16);
}
cstream.Write(buffer, offset, count);
}
private RijndaelManaged GenerateAES(byte[] key)
private BufferedBlockCipher GenerateAES(byte[] key, bool forEncryption)
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CFB;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 128;
cipher.FeedbackSize = 8;
cipher.Key = key;
cipher.IV = key;
BufferedBlockCipher cipher = new BufferedBlockCipher(new CfbBlockCipher(new AesFastEngine(), 8));
cipher.Init(forEncryption, new ParametersWithIV(new KeyParameter(key), key));
return cipher;
}
}

View file

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represents a Minecraft Block
/// </summary>
public struct Block
{
/// <summary>
/// Storage for block ID and metadata
/// </summary>
private ushort blockIdAndMeta;
/// <summary>
/// Id of the block
/// </summary>
public short BlockId
{
get
{
return (short)(blockIdAndMeta >> 4);
}
set
{
blockIdAndMeta = (ushort)(value << 4 | BlockMeta);
}
}
/// <summary>
/// Metadata of the block
/// </summary>
public byte BlockMeta
{
get
{
return (byte)(blockIdAndMeta & 0x0F);
}
set
{
blockIdAndMeta = (ushort)((blockIdAndMeta & ~0x0F) | (value & 0x0F));
}
}
/// <summary>
/// Material of the block
/// </summary>
public Material Type
{
get
{
return (Material)BlockId;
}
}
/// <summary>
/// Get a block of the specified type and metadata
/// </summary>
/// <param name="type">Block type</param>
/// <param name="metadata">Block metadata</param>
public Block(short type, byte metadata = 0)
{
this.blockIdAndMeta = 0;
this.BlockId = type;
this.BlockMeta = metadata;
}
/// <summary>
/// Get a block of the specified type and metadata
/// </summary>
/// <param name="typeAndMeta">Type and metadata packed in the same value</param>
public Block(ushort typeAndMeta)
{
this.blockIdAndMeta = typeAndMeta;
}
/// <summary>
/// Get a block of the specified type and metadata
/// </summary>
/// <param name="type">Block type</param>
public Block(Material type, byte metadata = 0)
: this((short)type, metadata) { }
/// <summary>
/// String representation of the block
/// </summary>
public override string ToString()
{
return BlockId.ToString() + (BlockMeta != 0 ? ":" + BlockMeta.ToString() : "");
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represent a chunk of terrain in a Minecraft world
/// </summary>
public class Chunk
{
public const int SizeX = 16;
public const int SizeY = 16;
public const int SizeZ = 16;
/// <summary>
/// Blocks contained into the chunk
/// </summary>
private readonly Block[,,] blocks = new Block[SizeX, SizeY, SizeZ];
/// <summary>
/// Read, or set the specified block
/// </summary>
/// <param name="blockX">Block X</param>
/// <param name="blockY">Block Y</param>
/// <param name="blockZ">Block Z</param>
/// <returns>chunk at the given location</returns>
public Block this[int blockX, int blockY, int blockZ]
{
get
{
if (blockX < 0 || blockX >= SizeX)
throw new ArgumentOutOfRangeException("blockX", "Must be between 0 and " + (SizeX - 1) + " (inclusive)");
if (blockY < 0 || blockY >= SizeY)
throw new ArgumentOutOfRangeException("blockY", "Must be between 0 and " + (SizeY - 1) + " (inclusive)");
if (blockZ < 0 || blockZ >= SizeZ)
throw new ArgumentOutOfRangeException("blockZ", "Must be between 0 and " + (SizeZ - 1) + " (inclusive)");
return blocks[blockX, blockY, blockZ];
}
set
{
if (blockX < 0 || blockX >= SizeX)
throw new ArgumentOutOfRangeException("blockX", "Must be between 0 and " + (SizeX - 1) + " (inclusive)");
if (blockY < 0 || blockY >= SizeY)
throw new ArgumentOutOfRangeException("blockY", "Must be between 0 and " + (SizeY - 1) + " (inclusive)");
if (blockZ < 0 || blockZ >= SizeZ)
throw new ArgumentOutOfRangeException("blockZ", "Must be between 0 and " + (SizeZ - 1) + " (inclusive)");
blocks[blockX, blockY, blockZ] = value;
}
}
/// <summary>
/// Get block at the specified location
/// </summary>
/// <param name="location">Location, a modulo will be applied</param>
/// <returns>The block</returns>
public Block GetBlock(Location location)
{
return this[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ];
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represent a column of chunks of terrain in a Minecraft world
/// </summary>
public class ChunkColumn
{
public const int ColumnSize = 16;
/// <summary>
/// Blocks contained into the chunk
/// </summary>
private readonly Chunk[] chunks = new Chunk[ColumnSize];
/// <summary>
/// Get or set the specified chunk column
/// </summary>
/// <param name="chunkX">ChunkColumn X</param>
/// <param name="chunkY">ChunkColumn Y</param>
/// <returns>chunk at the given location</returns>
public Chunk this[int chunkY]
{
get
{
return chunks[chunkY];
}
set
{
chunks[chunkY] = value;
}
}
/// <summary>
/// Get chunk at the specified location
/// </summary>
/// <param name="location">Location, a modulo will be applied</param>
/// <returns>The chunk, or null if not loaded</returns>
public Chunk GetChunk(Location location)
{
return this[location.ChunkY];
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represents a unit movement in the world
/// </summary>
/// <see href="http://minecraft.gamepedia.com/Coordinates"/>
public enum Direction
{
South = 0,
West = 1,
North = 2,
East = 3,
Up = 4,
Down = 5
}
}

View file

@ -0,0 +1,331 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represents a location into a Minecraft world
/// </summary>
public struct Location
{
/// <summary>
/// The X Coordinate
/// </summary>
public double X;
/// <summary>
/// The Y Coordinate (vertical)
/// </summary>
public double Y;
/// <summary>
/// The Z coordinate
/// </summary>
public double Z;
/// <summary>
/// Get location with zeroed coordinates
/// </summary>
public static Location Zero
{
get
{
return new Location(0, 0, 0);
}
}
/// <summary>
/// Create a new location
/// </summary>
public Location(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
/// <summary>
/// Create a new location
/// </summary>
/// <param name="chunkX">Location of the chunk into the world</param>
/// <param name="chunkZ">Location of the chunk into the world</param>
/// <param name="blockX">Location of the block into the chunk</param>
/// <param name="blockY">Location of the block into the world</param>
/// <param name="blockZ">Location of the block into the chunk</param>
public Location(int chunkX, int chunkZ, int blockX, int blockY, int blockZ)
{
X = chunkX * Chunk.SizeX + blockX;
Y = blockY;
Z = chunkZ * Chunk.SizeZ + blockZ;
}
/// <summary>
/// The X index of the corresponding chunk in the world
/// </summary>
public int ChunkX
{
get
{
return (int)Math.Floor(X / Chunk.SizeX);
}
}
/// <summary>
/// The Y index of the corresponding chunk in the world
/// </summary>
public int ChunkY
{
get
{
return (int)Math.Floor(Y / Chunk.SizeY);
}
}
/// <summary>
/// The Z index of the corresponding chunk in the world
/// </summary>
public int ChunkZ
{
get
{
return (int)Math.Floor(Z / Chunk.SizeZ);
}
}
/// <summary>
/// The X index of the corresponding block in the corresponding chunk of the world
/// </summary>
public int ChunkBlockX
{
get
{
return ((int)Math.Floor(X) % Chunk.SizeX + Chunk.SizeX) % Chunk.SizeX;
}
}
/// <summary>
/// The Y index of the corresponding block in the corresponding chunk of the world
/// </summary>
public int ChunkBlockY
{
get
{
return ((int)Math.Floor(Y) % Chunk.SizeY + Chunk.SizeY) % Chunk.SizeY;
}
}
/// <summary>
/// The Z index of the corresponding block in the corresponding chunk of the world
/// </summary>
public int ChunkBlockZ
{
get
{
return ((int)Math.Floor(Z) % Chunk.SizeZ + Chunk.SizeZ) % Chunk.SizeZ;
}
}
/// <summary>
/// Get a squared distance to the specified location
/// </summary>
/// <param name="location">Other location for computing distance</param>
/// <returns>Distance to the specified location, without using a square root</returns>
public double DistanceSquared(Location location)
{
return ((X - location.X) * (X - location.X))
+ ((Y - location.Y) * (Y - location.Y))
+ ((Z - location.Z) * (Z - location.Z));
}
/// <summary>
/// Get exact distance to the specified location
/// </summary>
/// <param name="location">Other location for computing distance</param>
/// <returns>Distance to the specified location, with square root so lower performances</returns>
public double Distance(Location location)
{
return Math.Sqrt(DistanceSquared(location));
}
/// <summary>
/// Compare two locations. Locations are equals if the integer part of their coordinates are equals.
/// </summary>
/// <param name="obj">Object to compare to</param>
/// <returns>TRUE if the locations are equals</returns>
public override bool Equals(object obj)
{
if (obj == null)
return false;
if (obj is Location)
{
return ((int)this.X) == ((int)((Location)obj).X)
&& ((int)this.Y) == ((int)((Location)obj).Y)
&& ((int)this.Z) == ((int)((Location)obj).Z);
}
return false;
}
/// <summary>
/// Get a representation of the location as unsigned long
/// </summary>
/// <remarks>
/// A modulo will be applied if the location is outside the following ranges:
/// X: -33,554,432 to +33,554,431
/// Y: -2,048 to +2,047
/// Z: -33,554,432 to +33,554,431
/// </remarks>
/// <returns>Location representation as ulong</returns>
public ulong GetLong()
{
return ((((ulong)X) & 0x3FFFFFF) << 38) | ((((ulong)Y) & 0xFFF) << 26) | (((ulong)Z) & 0x3FFFFFF);
}
/// <summary>
/// Get a location from an unsigned long.
/// </summary>
/// <returns>Location represented by the ulong</returns>
public static Location FromLong(ulong location)
{
int x = (int)(location >> 38);
int y = (int)((location >> 26) & 0xFFF);
int z = (int)(location << 38 >> 38);
if (x >= 33554432)
x -= 67108864;
if (y >= 2048)
y -= 4096;
if (z >= 33554432)
z -= 67108864;
return new Location(x, y, z);
}
/// <summary>
/// Compare two locations. Locations are equals if the integer part of their coordinates are equals.
/// </summary>
/// <param name="loc1">First location to compare</param>
/// <param name="loc2">Second location to compare</param>
/// <returns>TRUE if the locations are equals</returns>
public static bool operator ==(Location loc1, Location loc2)
{
if (loc1 == null && loc2 == null)
return true;
if (loc1 == null || loc2 == null)
return false;
return loc1.Equals(loc2);
}
/// <summary>
/// Compare two locations. Locations are not equals if the integer part of their coordinates are not equals.
/// </summary>
/// <param name="loc1">First location to compare</param>
/// <param name="loc2">Second location to compare</param>
/// <returns>TRUE if the locations are equals</returns>
public static bool operator !=(Location loc1, Location loc2)
{
if (loc1 == null && loc2 == null)
return true;
if (loc1 == null || loc2 == null)
return false;
return !loc1.Equals(loc2);
}
/// <summary>
/// Sums two locations and returns the result.
/// </summary>
/// <exception cref="NullReferenceException">
/// Thrown if one of the provided location is null
/// </exception>
/// <param name="loc1">First location to sum</param>
/// <param name="loc2">Second location to sum</param>
/// <returns>Sum of the two locations</returns>
public static Location operator +(Location loc1, Location loc2)
{
return new Location
(
loc1.X + loc2.X,
loc1.Y + loc2.Y,
loc1.Z + loc2.Z
);
}
/// <summary>
/// Substract a location to another
/// </summary>
/// <exception cref="NullReferenceException">
/// Thrown if one of the provided location is null
/// </exception>
/// <param name="loc1">First location</param>
/// <param name="loc2">Location to substract to the first one</param>
/// <returns>Sum of the two locations</returns>
public static Location operator -(Location loc1, Location loc2)
{
return new Location
(
loc1.X - loc2.X,
loc1.Y - loc2.Y,
loc1.Z - loc2.Z
);
}
/// <summary>
/// Multiply a location by a scalar value
/// </summary>
/// <param name="loc">Location to multiply</param>
/// <param name="val">Scalar value</param>
/// <returns>Product of the location and the scalar value</returns>
public static Location operator *(Location loc, double val)
{
return new Location
(
loc.X * val,
loc.Y * val,
loc.Z * val
);
}
/// <summary>
/// Divide a location by a scalar value
/// </summary>
/// <param name="loc">Location to divide</param>
/// <param name="val">Scalar value</param>
/// <returns>Result of the division</returns>
public static Location operator /(Location loc, double val)
{
return new Location
(
loc.X / val,
loc.Y / val,
loc.Z / val
);
}
/// <summary>
/// DO NOT USE. Defined to comply with C# requirements requiring a GetHashCode() when overriding Equals() or ==
/// </summary>
/// <remarks>
/// A modulo will be applied if the location is outside the following ranges:
/// X: -4096 to +4095
/// Y: -32 to +31
/// Z: -4096 to +4095
/// </remarks>
/// <returns>A simplified version of the location</returns>
public override int GetHashCode()
{
return (((int)X) & ~((~0) << 13)) << 19
| (((int)Y) & ~((~0) << 13)) << 13
| (((int)Z) & ~((~0) << 06)) << 00;
}
/// <summary>
/// Convert the location into a string representation
/// </summary>
/// <returns>String representation of the location</returns>
public override string ToString()
{
return String.Format("X:{0} Y:{1} Z:{2}", X, Y, Z);
}
}
}

View file

@ -0,0 +1,365 @@
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represents Minecraft Materials
/// </summary>
/// <remarks>
/// Mostly ported from CraftBukkit's Material class
/// </remarks>
/// <see href="https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/Material.java"/>
public enum Material
{
Air = 0,
Stone = 1,
Grass = 2,
Dirt = 3,
Cobblestone = 4,
Wood = 5,
Sapling = 6,
Bedrock = 7,
Water = 8,
StationaryWater = 9,
Lava = 10,
StationaryLava = 11,
Sand = 12,
Gravel = 13,
GoldOre = 14,
IronOre = 15,
CoalOre = 16,
Log = 17,
Leaves = 18,
Sponge = 19,
Glass = 20,
LapisOre = 21,
LapisBlock = 22,
Dispenser = 23,
Sandstone = 24,
NoteBlock = 25,
BedBlock = 26,
PoweredRail = 27,
DetectorRail = 28,
PistonStickyBase = 29,
Web = 30,
LongGrass = 31,
DeadBush = 32,
PistonBase = 33,
PistonExtension = 34,
Wool = 35,
PistonMovingPiece = 36,
YellowFlower = 37,
RedRose = 38,
BrownMushroom = 39,
RedMushroom = 40,
GoldBlock = 41,
IronBlock = 42,
DoubleStep = 43,
Step = 44,
Brick = 45,
Tnt = 46,
Bookshelf = 47,
MossyCobblestone = 48,
Obsidian = 49,
Torch = 50,
Fire = 51,
MobSpawner = 52,
WoodStairs = 53,
Chest = 54,
RedstoneWire = 55,
DiamondOre = 56,
DiamondBlock = 57,
Workbench = 58,
Crops = 59,
Soil = 60,
Furnace = 61,
BurningFurnace = 62,
SignPost = 63,
WoodenDoor = 64,
Ladder = 65,
Rails = 66,
CobblestoneStairs = 67,
WallSign = 68,
Lever = 69,
StonePlate = 70,
IronDoorBlock = 71,
WoodPlate = 72,
RedstoneOre = 73,
GlowingRedstoneOre = 74,
RedstoneTorchOff = 75,
RedstoneTorchOn = 76,
StoneButton = 77,
Snow = 78,
Ice = 79,
SnowBlock = 80,
Cactus = 81,
Clay = 82,
SugarCaneBlock = 83,
Jukebox = 84,
Fence = 85,
Pumpkin = 86,
Netherrack = 87,
SoulSand = 88,
Glowstone = 89,
Portal = 90,
JackOLantern = 91,
CakeBlock = 92,
DiodeBlockOff = 93,
DiodeBlockOn = 94,
StainedGlass = 95,
TrapDoor = 96,
MonsterEggs = 97,
SmoothBrick = 98,
HugeMushroom1 = 99,
HugeMushroom2 = 100,
IronFence = 101,
ThinGlass = 102,
MelonBlock = 103,
PumpkinStem = 104,
MelonStem = 105,
Vine = 106,
FenceGate = 107,
BrickStairs = 108,
SmoothStairs = 109,
Mycel = 110,
WaterLily = 111,
NetherBrick = 112,
NetherFence = 113,
NetherBrickStairs = 114,
NetherWarts = 115,
EnchantmentTable = 116,
BrewingStand = 117,
Cauldron = 118,
EnderPortal = 119,
EnderPortalFrame = 120,
EnderStone = 121,
DragonEgg = 122,
RedstoneLampOff = 123,
RedstoneLampOn = 124,
WoodDoubleStep = 125,
WoodStep = 126,
Cocoa = 127,
SandstoneStairs = 128,
EmeraldOre = 129,
EnderChest = 130,
TripwireHook = 131,
Tripwire = 132,
EmeraldBlock = 133,
SpruceWoodStairs = 134,
BirchWoodStairs = 135,
JungleWoodStairs = 136,
Command = 137,
Beacon = 138,
CobbleWall = 139,
FlowerPot = 140,
Carrot = 141,
Potato = 142,
WoodButton = 143,
Skull = 144,
Anvil = 145,
TrappedChest = 146,
GoldPlate = 147,
IronPlate = 148,
RedstoneComparatorOff = 149,
RedstoneComparatorOn = 150,
DaylightDetector = 151,
RedstoneBlock = 152,
QuartzOre = 153,
Hopper = 154,
QuartzBlock = 155,
QuartzStairs = 156,
ActivatorRail = 157,
Dropper = 158,
StainedClay = 159,
StainedGlassPane = 160,
Leaves2 = 161,
Log2 = 162,
AcaciaStairs = 163,
DarkOakStairs = 164,
HayBlock = 170,
Carpet = 171,
HardClay = 172,
CoalBlock = 173,
PackedIce = 174,
DoublePlant = 175
}
/// <summary>
/// Defines extension methods for the Material enumeration
/// </summary>
public static class MaterialExtensions
{
/// <summary>
/// Check if the player cannot pass through the specified material
/// </summary>
/// <param name="m">Material to test</param>
/// <returns>True if the material is harmful</returns>
public static bool IsSolid(this Material m)
{
switch (m)
{
case Material.Stone:
case Material.Grass:
case Material.Dirt:
case Material.Cobblestone:
case Material.Wood:
case Material.Bedrock:
case Material.Sand:
case Material.Gravel:
case Material.GoldOre:
case Material.IronOre:
case Material.CoalOre:
case Material.Log:
case Material.Leaves:
case Material.Sponge:
case Material.Glass:
case Material.LapisOre:
case Material.LapisBlock:
case Material.Dispenser:
case Material.Sandstone:
case Material.NoteBlock:
case Material.BedBlock:
case Material.PistonStickyBase:
case Material.PistonBase:
case Material.PistonExtension:
case Material.Wool:
case Material.PistonMovingPiece:
case Material.GoldBlock:
case Material.IronBlock:
case Material.DoubleStep:
case Material.Step:
case Material.Brick:
case Material.Tnt:
case Material.Bookshelf:
case Material.MossyCobblestone:
case Material.Obsidian:
case Material.MobSpawner:
case Material.WoodStairs:
case Material.Chest:
case Material.DiamondOre:
case Material.DiamondBlock:
case Material.Workbench:
case Material.Soil:
case Material.Furnace:
case Material.BurningFurnace:
case Material.SignPost:
case Material.WoodenDoor:
case Material.CobblestoneStairs:
case Material.WallSign:
case Material.StonePlate:
case Material.IronDoorBlock:
case Material.WoodPlate:
case Material.RedstoneOre:
case Material.GlowingRedstoneOre:
case Material.Ice:
case Material.SnowBlock:
case Material.Cactus:
case Material.Clay:
case Material.Jukebox:
case Material.Fence:
case Material.Pumpkin:
case Material.Netherrack:
case Material.SoulSand:
case Material.Glowstone:
case Material.JackOLantern:
case Material.CakeBlock:
case Material.StainedGlass:
case Material.TrapDoor:
case Material.MonsterEggs:
case Material.SmoothBrick:
case Material.HugeMushroom1:
case Material.HugeMushroom2:
case Material.IronFence:
case Material.ThinGlass:
case Material.MelonBlock:
case Material.FenceGate:
case Material.BrickStairs:
case Material.SmoothStairs:
case Material.Mycel:
case Material.NetherBrick:
case Material.NetherFence:
case Material.NetherBrickStairs:
case Material.EnchantmentTable:
case Material.BrewingStand:
case Material.Cauldron:
case Material.EnderPortalFrame:
case Material.EnderStone:
case Material.DragonEgg:
case Material.RedstoneLampOff:
case Material.RedstoneLampOn:
case Material.WoodDoubleStep:
case Material.WoodStep:
case Material.SandstoneStairs:
case Material.EmeraldOre:
case Material.EnderChest:
case Material.EmeraldBlock:
case Material.SpruceWoodStairs:
case Material.BirchWoodStairs:
case Material.JungleWoodStairs:
case Material.Command:
case Material.Beacon:
case Material.CobbleWall:
case Material.Anvil:
case Material.TrappedChest:
case Material.GoldPlate:
case Material.IronPlate:
case Material.DaylightDetector:
case Material.RedstoneBlock:
case Material.QuartzOre:
case Material.Hopper:
case Material.QuartzBlock:
case Material.QuartzStairs:
case Material.Dropper:
case Material.StainedClay:
case Material.HayBlock:
case Material.HardClay:
case Material.CoalBlock:
case Material.StainedGlassPane:
case Material.Leaves2:
case Material.Log2:
case Material.AcaciaStairs:
case Material.DarkOakStairs:
case Material.PackedIce:
return true;
default:
return false;
}
}
/// <summary>
/// Check if contact with the provided material can harm players
/// </summary>
/// <param name="m">Material to test</param>
/// <returns>True if the material is harmful</returns>
public static bool CanHarmPlayers(this Material m)
{
switch (m)
{
case Material.Fire:
case Material.Cactus:
case Material.Lava:
case Material.StationaryLava:
return true;
default:
return false;
}
}
/// <summary>
/// Check if the provided material is a liquid a player can swim into
/// </summary>
/// <param name="m">Material to test</param>
/// <returns>True if the material is a liquid</returns>
public static bool IsLiquid(this Material m)
{
switch (m)
{
case Material.Water:
case Material.StationaryWater:
case Material.Lava:
case Material.StationaryLava:
return true;
default:
return false;
}
}
}
}

View file

@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Allows moving through a Minecraft world
/// </summary>
public static class Movement
{
/* ========= PATHFINDING METHODS ========= */
/// <summary>
/// Handle movements due to gravity
/// </summary>
/// <param name="world">World the player is currently located in</param>
/// <param name="location">Location the player is currently at</param>
/// <returns>Updated location after applying gravity</returns>
public static Location HandleGravity(World world, Location location)
{
Location onFoots = new Location(location.X, Math.Floor(location.Y), location.Z);
Location belowFoots = Move(location, Direction.Down);
if (!IsOnGround(world, location) && !IsSwimming(world, location))
location = Move2Steps(location, belowFoots).Dequeue();
else if (!(world.GetBlock(onFoots).Type.IsSolid()))
location = Move2Steps(location, onFoots).Dequeue();
return location;
}
/// <summary>
/// Return a list of possible moves for the player
/// </summary>
/// <param name="world">World the player is currently located in</param>
/// <param name="location">Location the player is currently at</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
/// <returns>A list of new locations the player can move to</returns>
public static IEnumerable<Location> GetAvailableMoves(World world, Location location, bool allowUnsafe = false)
{
List<Location> availableMoves = new List<Location>();
if (IsOnGround(world, location) || IsSwimming(world, location))
{
foreach (Direction dir in Enum.GetValues(typeof(Direction)))
if (CanMove(world, location, dir) && (allowUnsafe || IsSafe(world, Move(location, dir))))
availableMoves.Add(Move(location, dir));
}
else
{
foreach (Direction dir in new []{ Direction.East, Direction.West, Direction.North, Direction.South })
if (CanMove(world, location, dir) && IsOnGround(world, Move(location, dir)) && (allowUnsafe || IsSafe(world, Move(location, dir))))
availableMoves.Add(Move(location, dir));
availableMoves.Add(Move(location, Direction.Down));
}
return availableMoves;
}
/// <summary>
/// Decompose a single move from a block to another into several steps
/// </summary>
/// <remarks>
/// Allows moving by little steps instead or directly moving between blocks,
/// which would be rejected by anti-cheat plugins anyway.
/// </remarks>
/// <param name="start">Start location</param>
/// <param name="goal">Destination location</param>
/// <param name="stepsByBlock">Amount of steps by block</param>
/// <returns>A list of locations corresponding to the requested steps</returns>
public static Queue<Location> Move2Steps(Location start, Location goal, int stepsByBlock = 8)
{
if (stepsByBlock <= 0)
stepsByBlock = 1;
double totalStepsDouble = start.Distance(goal) * stepsByBlock;
int totalSteps = (int)Math.Ceiling(totalStepsDouble);
Location step = (goal - start) / totalSteps;
if (totalStepsDouble >= 1)
{
Queue<Location> movementSteps = new Queue<Location>();
for (int i = 1; i <= totalSteps; i++)
movementSteps.Enqueue(start + step * i);
return movementSteps;
}
else return new Queue<Location>(new[] { goal });
}
/// <summary>
/// Calculate a path from the start location to the destination location
/// </summary>
/// <remarks>
/// Based on the A* pathfinding algorithm described on Wikipedia
/// </remarks>
/// <see href="https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode"/>
/// <param name="start">Start location</param>
/// <param name="goal">Destination location</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
/// <returns>A list of locations, or null if calculation failed</returns>
public static Queue<Location> CalculatePath(World world, Location start, Location goal, bool allowUnsafe = false)
{
Queue<Location> result = null;
AutoTimeout.Perform(() =>
{
HashSet<Location> ClosedSet = new HashSet<Location>(); // The set of locations already evaluated.
HashSet<Location> OpenSet = new HashSet<Location>(new[] { start }); // The set of tentative nodes to be evaluated, initially containing the start node
Dictionary<Location, Location> Came_From = new Dictionary<Location, Location>(); // The map of navigated nodes.
Dictionary<Location, int> g_score = new Dictionary<Location, int>(); //:= map with default value of Infinity
g_score[start] = 0; // Cost from start along best known path.
// Estimated total cost from start to goal through y.
Dictionary<Location, int> f_score = new Dictionary<Location, int>(); //:= map with default value of Infinity
f_score[start] = (int)start.DistanceSquared(goal); //heuristic_cost_estimate(start, goal)
while (OpenSet.Count > 0)
{
Location current = //the node in OpenSet having the lowest f_score[] value
OpenSet.Select(location => f_score.ContainsKey(location)
? new KeyValuePair<Location, int>(location, f_score[location])
: new KeyValuePair<Location, int>(location, int.MaxValue))
.OrderBy(pair => pair.Value).First().Key;
if (current == goal)
{ //reconstruct_path(Came_From, goal)
List<Location> total_path = new List<Location>(new[] { current });
while (Came_From.ContainsKey(current))
{
current = Came_From[current];
total_path.Add(current);
}
total_path.Reverse();
result = new Queue<Location>(total_path);
}
OpenSet.Remove(current);
ClosedSet.Add(current);
foreach (Location neighbor in GetAvailableMoves(world, current, allowUnsafe))
{
if (ClosedSet.Contains(neighbor))
continue; // Ignore the neighbor which is already evaluated.
int tentative_g_score = g_score[current] + (int)current.DistanceSquared(neighbor); //dist_between(current,neighbor) // length of this path.
if (!OpenSet.Contains(neighbor)) // Discover a new node
OpenSet.Add(neighbor);
else if (tentative_g_score >= g_score[neighbor])
continue; // This is not a better path.
// This path is the best until now. Record it!
Came_From[neighbor] = current;
g_score[neighbor] = tentative_g_score;
f_score[neighbor] = g_score[neighbor] + (int)neighbor.DistanceSquared(goal); //heuristic_cost_estimate(neighbor, goal)
}
}
}, TimeSpan.FromSeconds(5));
return result;
}
/* ========= LOCATION PROPERTIES ========= */
/// <summary>
/// Check if the specified location is on the ground
/// </summary>
/// <param name="world">World for performing check</param>
/// <param name="location">Location to check</param>
/// <returns>True if the specified location is on the ground</returns>
public static bool IsOnGround(World world, Location location)
{
return world.GetBlock(Move(location, Direction.Down)).Type.IsSolid();
}
/// <summary>
/// Check if the specified location implies swimming
/// </summary>
/// <param name="world">World for performing check</param>
/// <param name="location">Location to check</param>
/// <returns>True if the specified location implies swimming</returns>
public static bool IsSwimming(World world, Location location)
{
return world.GetBlock(location).Type.IsLiquid();
}
/// <summary>
/// Check if the specified location is safe
/// </summary>
/// <param name="world">World for performing check</param>
/// <param name="location">Location to check</param>
/// <returns>True if the destination location won't directly harm the player</returns>
public static bool IsSafe(World world, Location location)
{
return
//No block that can harm the player
!world.GetBlock(location).Type.CanHarmPlayers()
&& !world.GetBlock(Move(location, Direction.Up)).Type.CanHarmPlayers()
&& !world.GetBlock(Move(location, Direction.Down)).Type.CanHarmPlayers()
//No fall from a too high place
&& (world.GetBlock(Move(location, Direction.Down)).Type.IsSolid()
|| world.GetBlock(Move(location, Direction.Down, 2)).Type.IsSolid()
|| world.GetBlock(Move(location, Direction.Down, 3)).Type.IsSolid())
//Not an underwater location
&& !(world.GetBlock(Move(location, Direction.Up)).Type.IsLiquid());
}
/* ========= SIMPLE MOVEMENTS ========= */
/// <summary>
/// Check if the player can move in the specified direction
/// </summary>
/// <param name="world">World the player is currently located in</param>
/// <param name="location">Location the player is currently at</param>
/// <param name="direction">Direction the player is moving to</param>
/// <returns>True if the player can move in the specified direction</returns>
public static bool CanMove(World world, Location location, Direction direction)
{
switch (direction)
{
case Direction.Down:
return !IsOnGround(world, location);
case Direction.Up:
return (IsOnGround(world, location) || IsSwimming(world, location))
&& !world.GetBlock(Move(Move(location, Direction.Up), Direction.Up)).Type.IsSolid();
case Direction.East:
case Direction.West:
case Direction.South:
case Direction.North:
return !world.GetBlock(Move(location, direction)).Type.IsSolid()
&& !world.GetBlock(Move(Move(location, direction), Direction.Up)).Type.IsSolid();
default:
throw new ArgumentException("Unknown direction", "direction");
}
}
/// <summary>
/// Get an updated location for moving in the specified direction
/// </summary>
/// <param name="location">Current location</param>
/// <param name="direction">Direction to move to</param>
/// <param name="length">Distance, in blocks</param>
/// <returns>Updated location</returns>
public static Location Move(Location location, Direction direction, int length = 1)
{
return location + Move(direction) * length;
}
/// <summary>
/// Get a location delta for moving in the specified direction
/// </summary>
/// <param name="direction">Direction to move to</param>
/// <returns>A location delta for moving in that direction</returns>
public static Location Move(Direction direction)
{
switch (direction)
{
case Direction.Down:
return new Location(0, -1, 0);
case Direction.Up:
return new Location(0, 1, 0);
case Direction.East:
return new Location(1, 0, 0);
case Direction.West:
return new Location(-1, 0, 0);
case Direction.South:
return new Location(0, 0, 1);
case Direction.North:
return new Location(0, 0, -1);
default:
throw new ArgumentException("Unknown direction", "direction");
}
}
}
}

View file

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
/// <summary>
/// Represents a Minecraft World
/// </summary>
public class World
{
/// <summary>
/// The chunks contained into the Minecraft world
/// </summary>
private Dictionary<int, Dictionary<int, ChunkColumn>> chunks = new Dictionary<int, Dictionary<int, ChunkColumn>>();
/// <summary>
/// Read, set or unload the specified chunk column
/// </summary>
/// <param name="chunkX">ChunkColumn X</param>
/// <param name="chunkY">ChunkColumn Y</param>
/// <returns>chunk at the given location</returns>
public ChunkColumn this[int chunkX, int chunkZ]
{
get
{
//Read a chunk
if (chunks.ContainsKey(chunkX))
if (chunks[chunkX].ContainsKey(chunkZ))
return chunks[chunkX][chunkZ];
return null;
}
set
{
if (value != null)
{
//Update a chunk column
if (!chunks.ContainsKey(chunkX))
chunks[chunkX] = new Dictionary<int, ChunkColumn>();
chunks[chunkX][chunkZ] = value;
}
else
{
//Unload a chunk column
if (chunks.ContainsKey(chunkX))
{
if (chunks[chunkX].ContainsKey(chunkZ))
{
chunks[chunkX].Remove(chunkZ);
if (chunks[chunkX].Count == 0)
chunks.Remove(chunkX);
}
}
}
}
}
/// <summary>
/// Get chunk column at the specified location
/// </summary>
/// <param name="location">Location to retrieve chunk column</param>
/// <returns>The chunk column</returns>
public ChunkColumn GetChunkColumn(Location location)
{
return this[location.ChunkX, location.ChunkZ];
}
/// <summary>
/// Get block at the specified location
/// </summary>
/// <param name="location">Location to retrieve block from</param>
/// <returns>Block at specified location or Air if the location is not loaded</returns>
public Block GetBlock(Location location)
{
ChunkColumn column = GetChunkColumn(location);
if (column != null)
{
Chunk chunk = column.GetChunk(location);
if (chunk != null)
return chunk.GetBlock(location);
}
return new Block(Material.Air);
}
/// <summary>
/// Set block at the specified location
/// </summary>
/// <param name="location">Location to set block to</param>
/// <param name="block">Block to set</param>
public void SetBlock(Location location, Block block)
{
ChunkColumn column = this[location.ChunkX, location.ChunkZ];
if (column != null)
{
Chunk chunk = column[location.ChunkY];
if (chunk == null)
column[location.ChunkY] = chunk = new Chunk();
chunk[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ] = block;
}
}
}
}

View file

@ -8,6 +8,8 @@ using System.IO;
using System.Net;
using MinecraftClient.Protocol;
using MinecraftClient.Proxy;
using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Mapping;
namespace MinecraftClient
{
@ -17,16 +19,45 @@ namespace MinecraftClient
public class McTcpClient : IMinecraftComHandler
{
private static List<string> cmd_names = new List<string>();
private static Dictionary<string, Command> cmds = new Dictionary<string, Command>();
private List<ChatBot> bots = new List<ChatBot>();
private Dictionary<Guid, string> onlinePlayers = new Dictionary<Guid,string>();
private static List<ChatBots.Script> scripts_on_hold = new List<ChatBots.Script>();
public void BotLoad(ChatBot b) { b.SetHandler(this); bots.Add(b); b.Initialize(); Settings.SingleCommand = ""; }
public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); }
public static int ReconnectionAttemptsLeft = 0;
private static readonly List<string> cmd_names = new List<string>();
private static readonly Dictionary<string, Command> cmds = new Dictionary<string, Command>();
private readonly Dictionary<Guid, string> onlinePlayers = new Dictionary<Guid, string>();
private readonly List<ChatBot> bots = new List<ChatBot>();
private static readonly List<ChatBots.Script> scripts_on_hold = new List<ChatBots.Script>();
public void BotLoad(ChatBot b) {
b.SetHandler(this);
bots.Add(b);
b.Initialize();
if (this.handler != null)
{
b.AfterGameJoined();
}
Settings.SingleCommand = "";
}
public void BotUnLoad(ChatBot b) {
bots.RemoveAll(item => object.ReferenceEquals(item, b));
// ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon.
var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList();
foreach (var entry in botRegistrations)
{
UnregisterPluginChannel(entry.Key, b);
}
}
public void BotClear() { bots.Clear(); }
public static int AttemptsLeft = 0;
private readonly Dictionary<string, List<ChatBot>> registeredBotPluginChannels = new Dictionary<string, List<ChatBot>>();
private readonly List<string> registeredServerPluginChannels = new List<String>();
private object locationLock = new object();
private bool locationReceived = false;
private World world = new World();
private Queue<Location> steps;
private Queue<Location> path;
private Location location;
private string host;
private int port;
@ -34,12 +65,14 @@ namespace MinecraftClient
private string uuid;
private string sessionid;
public int getServerPort() { return port; }
public string getServerHost() { return host; }
public string getUsername() { return username; }
public string getUserUUID() { return uuid; }
public string getSessionID() { return sessionid; }
public int GetServerPort() { return port; }
public string GetServerHost() { return host; }
public string GetUsername() { return username; }
public string GetUserUUID() { return uuid; }
public string GetSessionID() { return sessionid; }
public Location GetCurrentLocation() { return location; }
public World GetWorld() { return world; }
TcpClient client;
IMinecraftCom handler;
Thread cmdprompt;
@ -54,9 +87,9 @@ namespace MinecraftClient
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
public McTcpClient(string username, string uuid, string sessionID, int protocolversion, string server_ip, ushort port)
public McTcpClient(string username, string uuid, string sessionID, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port)
{
StartClient(username, uuid, sessionID, server_ip, port, protocolversion, false, "");
StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, false, "");
}
/// <summary>
@ -70,9 +103,9 @@ namespace MinecraftClient
/// <param name="protocolversion">Minecraft protocol version to use</param>
/// <param name="command">The text or command to send.</param>
public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, string command)
public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command)
{
StartClient(username, uuid, sessionID, server_ip, port, protocolversion, true, command);
StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, true, command);
}
/// <summary>
@ -87,8 +120,9 @@ namespace MinecraftClient
/// <param name="singlecommand">If set to true, the client will send a single command and then disconnect from the server</param>
/// <param name="command">The text or command to send. Will only be sent if singlecommand is set to true.</param>
private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, bool singlecommand, string command)
private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command)
{
bool retry = false;
this.sessionid = sessionID;
this.uuid = uuid;
this.username = user;
@ -100,56 +134,75 @@ namespace MinecraftClient
if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); }
if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); }
if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); }
if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.expandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); }
if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.expandVars(Settings.PlayerLog_File))); }
if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.ExpandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); }
if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.ExpandVars(Settings.PlayerLog_File))); }
if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); }
if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.expandVars(Settings.ScriptScheduler_TasksFile))); }
if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.ExpandVars(Settings.ScriptScheduler_TasksFile))); }
if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); }
if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); }
}
try
{
client = ProxyHandler.newTcpClient(host, port);
client.ReceiveBufferSize = 1024 * 1024;
handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this);
handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, forgeInfo, this);
Console.WriteLine("Version is supported.\nLogging in...");
if (handler.Login())
try
{
if (singlecommand)
if (handler.Login())
{
handler.SendChatMessage(command);
ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent.");
Thread.Sleep(5000);
handler.Disconnect();
Thread.Sleep(1000);
}
else
{
foreach (ChatBot bot in scripts_on_hold)
bot.SetHandler(this);
bots.AddRange(scripts_on_hold);
scripts_on_hold.Clear();
Console.WriteLine("Server was successfully joined.\nType '"
+ (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar)
+ "quit' to leave the server.");
cmdprompt = new Thread(new ThreadStart(CommandPrompt));
cmdprompt.Name = "MCC Command prompt";
cmdprompt.Start();
if (singlecommand)
{
handler.SendChatMessage(command);
ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent.");
Thread.Sleep(5000);
handler.Disconnect();
Thread.Sleep(1000);
}
else
{
foreach (ChatBot bot in scripts_on_hold)
bot.SetHandler(this);
bots.AddRange(scripts_on_hold);
scripts_on_hold.Clear();
Console.WriteLine("Server was successfully joined.\nType '"
+ (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar)
+ "quit' to leave the server.");
cmdprompt = new Thread(new ThreadStart(CommandPrompt));
cmdprompt.Name = "MCC Command prompt";
cmdprompt.Start();
}
}
}
catch (Exception e)
{
ConsoleIO.WriteLineFormatted("§8" + e.Message);
Console.WriteLine("Failed to join this server.");
retry = true;
}
}
catch (SocketException)
catch (SocketException e)
{
ConsoleIO.WriteLineFormatted("§8" + e.Message);
Console.WriteLine("Failed to connect to this IP.");
if (AttemptsLeft > 0)
retry = true;
}
if (retry)
{
if (ReconnectionAttemptsLeft > 0)
{
ChatBot.LogToConsole("Waiting 5 seconds (" + AttemptsLeft + " attempts left)...");
Thread.Sleep(5000); AttemptsLeft--; Program.Restart();
ConsoleIO.WriteLogLine("Waiting 5 seconds (" + ReconnectionAttemptsLeft + " attempts left)...");
Thread.Sleep(5000); ReconnectionAttemptsLeft--; Program.Restart();
}
else if (!singlecommand && Settings.interactiveMode)
{
Program.HandleFailure();
}
else if (!singlecommand) { Console.ReadLine(); }
}
}
@ -189,7 +242,7 @@ namespace MinecraftClient
{
string response_msg = "";
string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1);
if (!performInternalCommand(Settings.expandVars(command), ref response_msg) && Settings.internalCmdChar == '/')
if (!PerformInternalCommand(Settings.ExpandVars(command), ref response_msg) && Settings.internalCmdChar == '/')
{
SendText(text);
}
@ -204,6 +257,7 @@ namespace MinecraftClient
}
}
catch (IOException) { }
catch (NullReferenceException) { }
}
/// <summary>
@ -214,7 +268,7 @@ namespace MinecraftClient
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
public bool performInternalCommand(string command, ref string response_msg)
public bool PerformInternalCommand(string command, ref string response_msg)
{
/* Load commands from the 'Commands' namespace */
@ -294,7 +348,69 @@ namespace MinecraftClient
Thread.Sleep(1000);
if (client != null) { client.Close(); }
if (client != null)
client.Close();
}
/// <summary>
/// Called when a server was successfully joined
/// </summary>
public void OnGameJoined()
{
if (!String.IsNullOrWhiteSpace(Settings.BrandInfo))
handler.SendBrandInfo(Settings.BrandInfo.Trim());
foreach (ChatBot bot in bots)
bot.AfterGameJoined();
}
/// <summary>
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
/// </summary>
/// <param name="location">The new location</param>
/// <param name="relative">If true, the location is relative to the current location</param>
public void UpdateLocation(Location location, bool relative)
{
lock (locationLock)
{
if (relative)
{
this.location += location;
}
else this.location = location;
locationReceived = true;
}
}
/// <summary>
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
/// </summary>
/// <param name="location">The new location</param>
/// <param name="relative">If true, the location is relative to the current location</param>
public void UpdateLocation(Location location)
{
UpdateLocation(location, false);
}
/// <summary>
/// Move to the specified location
/// </summary>
/// <param name="location">Location to reach</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
/// <returns>True if a path has been found</returns>
public bool MoveTo(Location location, bool allowUnsafe = false)
{
lock (locationLock)
{
if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location))
path = new Queue<Location>(new[] { location });
else path = Movement.CalculatePath(world, this.location, location, allowUnsafe);
return path != null;
}
}
/// <summary>
@ -305,8 +421,21 @@ namespace MinecraftClient
public void OnTextReceived(string text)
{
ConsoleIO.WriteLineFormatted(text, false);
foreach (ChatBot bot in new List<ChatBot>(bots))
bot.GetText(text);
for (int i = 0; i < bots.Count; i++)
{
try
{
bots[i].GetText(text);
}
catch (Exception e)
{
if (!(e is ThreadAbortException))
{
ConsoleIO.WriteLineFormatted("§8GetText: Got error from " + bots[i].ToString() + ": " + e.ToString());
}
else throw; //ThreadAbortException should not be caught
}
}
}
/// <summary>
@ -338,7 +467,8 @@ namespace MinecraftClient
foreach (ChatBot bot in bots)
will_restart |= bot.OnDisconnect(reason, message);
if (!will_restart) { Program.OfflineCommandPrompt(); }
if (!will_restart)
Program.HandleFailure();
}
/// <summary>
@ -347,21 +477,38 @@ namespace MinecraftClient
public void OnUpdate()
{
for (int i = 0; i < bots.Count; i++)
foreach (var bot in bots.ToArray())
{
try
{
bots[i].Update();
bot.Update();
bot.ProcessQueuedText();
}
catch (Exception e)
{
if (!(e is ThreadAbortException))
{
ConsoleIO.WriteLineFormatted("§8Got error from " + bots[i].ToString() + ": " + e.ToString());
ConsoleIO.WriteLineFormatted("§8Update: Got error from " + bot.ToString() + ": " + e.ToString());
}
else throw; //ThreadAbortException should not be caught
}
}
if (Settings.TerrainAndMovements && locationReceived)
{
lock (locationLock)
{
for (int i = 0; i < 2; i++) //Needs to run at 20 tps; MCC runs at 10 tps
{
if (steps != null && steps.Count > 0)
location = steps.Dequeue();
else if (path != null && path.Count > 0)
steps = Movement.Move2Steps(location, path.Dequeue());
else location = Movement.HandleGravity(world, location);
handler.SendLocationUpdate(location, Movement.IsOnGround(world, location));
}
}
}
}
/// <summary>
@ -387,6 +534,8 @@ namespace MinecraftClient
{
handler.SendChatMessage(text.Substring(0, 100));
text = text.Substring(100, text.Length - 100);
if (Settings.splitMessageDelay.TotalSeconds > 0)
Thread.Sleep(Settings.splitMessageDelay);
}
return handler.SendChatMessage(text);
}
@ -412,9 +561,16 @@ namespace MinecraftClient
public void OnPlayerJoin(Guid uuid, string name)
{
onlinePlayers[uuid] = name;
//Ignore TabListPlus placeholders
if (name.StartsWith("0000tab#"))
return;
lock (onlinePlayers)
{
onlinePlayers[uuid] = name;
}
}
/// <summary>
/// Triggered when a player has left the game
/// </summary>
@ -422,7 +578,10 @@ namespace MinecraftClient
public void OnPlayerLeave(Guid uuid)
{
onlinePlayers.Remove(uuid);
lock (onlinePlayers)
{
onlinePlayers.Remove(uuid);
}
}
/// <summary>
@ -430,9 +589,115 @@ namespace MinecraftClient
/// </summary>
/// <returns>Online player names</returns>
public string[] getOnlinePlayers()
public string[] GetOnlinePlayers()
{
return onlinePlayers.Values.Distinct().ToArray();
lock (onlinePlayers)
{
return onlinePlayers.Values.Distinct().ToArray();
}
}
/// <summary>
/// Registers the given plugin channel for the given bot.
/// </summary>
/// <param name="channel">The channel to register.</param>
/// <param name="bot">The bot to register the channel for.</param>
public void RegisterPluginChannel(string channel, ChatBot bot)
{
if (registeredBotPluginChannels.ContainsKey(channel))
{
registeredBotPluginChannels[channel].Add(bot);
}
else
{
List<ChatBot> bots = new List<ChatBot>();
bots.Add(bot);
registeredBotPluginChannels[channel] = bots;
SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true);
}
}
/// <summary>
/// Unregisters the given plugin channel for the given bot.
/// </summary>
/// <param name="channel">The channel to unregister.</param>
/// <param name="bot">The bot to unregister the channel for.</param>
public void UnregisterPluginChannel(string channel, ChatBot bot)
{
if (registeredBotPluginChannels.ContainsKey(channel))
{
List<ChatBot> registeredBots = registeredBotPluginChannels[channel];
registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot));
if (registeredBots.Count == 0)
{
registeredBotPluginChannels.Remove(channel);
SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true);
}
}
}
/// <summary>
/// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information
/// about plugin channels.
/// </summary>
/// <param name="channel">The channel to send the packet on.</param>
/// <param name="data">The payload for the packet.</param>
/// <param name="sendEvenIfNotRegistered">Whether the packet should be sent even if the server or the client hasn't registered it yet.</param>
/// <returns>Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered.</returns>
public bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false)
{
if (!sendEvenIfNotRegistered)
{
if (!registeredBotPluginChannels.ContainsKey(channel))
{
return false;
}
if (!registeredServerPluginChannels.Contains(channel))
{
return false;
}
}
return handler.SendPluginChannelPacket(channel, data);
}
/// <summary>
/// Called when a plugin channel message was sent from the server.
/// </summary>
/// <param name="channel">The channel the message was sent on</param>
/// <param name="data">The data from the channel</param>
public void OnPluginChannelMessage(string channel, byte[] data)
{
if (channel == "REGISTER")
{
string[] channels = Encoding.UTF8.GetString(data).Split('\0');
foreach (string chan in channels)
{
if (!registeredServerPluginChannels.Contains(chan))
{
registeredServerPluginChannels.Add(chan);
}
}
}
if (channel == "UNREGISTER")
{
string[] channels = Encoding.UTF8.GetString(data).Split('\0');
foreach (string chan in channels)
{
registeredServerPluginChannels.Remove(chan);
}
}
if (registeredBotPluginChannels.ContainsKey(channel))
{
foreach (ChatBot bot in registeredBotPluginChannels[channel])
{
bot.OnPluginMessage(channel, data);
}
}
}
}
}

View file

@ -63,6 +63,7 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -74,6 +75,7 @@
<Compile Include="AutoTimeout.cs" />
<Compile Include="ChatBots\Alerts.cs" />
<Compile Include="ChatBots\AntiAFK.cs" />
<Compile Include="ChatBots\AutoRespond.cs" />
<Compile Include="ChatBots\AutoRelog.cs" />
<Compile Include="ChatBots\ChatLog.cs" />
<Compile Include="ChatBots\HangmanGame.cs" />
@ -85,6 +87,7 @@
<Compile Include="ChatBot.cs" />
<Compile Include="Command.cs" />
<Compile Include="Commands\Connect.cs" />
<Compile Include="Commands\Move.cs" />
<Compile Include="Commands\Exit.cs" />
<Compile Include="Commands\Log.cs" />
<Compile Include="Commands\Reco.cs" />
@ -94,9 +97,35 @@
<Compile Include="Commands\Set.cs" />
<Compile Include="ConsoleIcon.cs" />
<Compile Include="ConsoleIO.cs" />
<Compile Include="Crypto\Streams\BouncyAes\AesFastEngine.cs" />
<Compile Include="Crypto\Streams\BouncyAes\BufferedBlockCipher.cs" />
<Compile Include="Crypto\Streams\BouncyAes\BufferedCipherBase.cs" />
<Compile Include="Crypto\Streams\BouncyAes\CfbBlockCipher.cs" />
<Compile Include="Crypto\Streams\BouncyAes\Check.cs" />
<Compile Include="Crypto\Streams\BouncyAes\CipherStream.cs" />
<Compile Include="Crypto\Streams\BouncyAes\CryptoException.cs" />
<Compile Include="Crypto\Streams\BouncyAes\DataLengthException.cs" />
<Compile Include="Crypto\Streams\BouncyAes\IBlockCipher.cs" />
<Compile Include="Crypto\Streams\BouncyAes\IBufferedCipher.cs" />
<Compile Include="Crypto\Streams\BouncyAes\ICipherParameters.cs" />
<Compile Include="Crypto\Streams\BouncyAes\KeyParameter.cs" />
<Compile Include="Crypto\Streams\BouncyAes\OutputLengthException.cs" />
<Compile Include="Crypto\Streams\BouncyAes\Pack.cs" />
<Compile Include="Crypto\Streams\BouncyAes\ParametersWithIV.cs" />
<Compile Include="Crypto\Streams\MonoAesStream.cs" />
<Compile Include="Crypto\Streams\RegularAesStream.cs" />
<Compile Include="Crypto\CryptoHandler.cs" />
<Compile Include="CSharpRunner.cs" />
<Compile Include="Mapping\Block.cs" />
<Compile Include="Mapping\Chunk.cs" />
<Compile Include="Mapping\ChunkColumn.cs" />
<Compile Include="Mapping\Direction.cs" />
<Compile Include="Mapping\Material.cs" />
<Compile Include="Mapping\Movement.cs" />
<Compile Include="Mapping\World.cs" />
<Compile Include="Protocol\Handlers\Forge\FMLHandshakeClientState.cs" />
<Compile Include="Protocol\Handlers\Forge\FMLHandshakeDiscriminator.cs" />
<Compile Include="Protocol\Handlers\Forge\ForgeInfo.cs" />
<Compile Include="Protocol\Handlers\Compression\CRC32.cs" />
<Compile Include="Protocol\Handlers\Compression\Deflate.cs" />
<Compile Include="Protocol\Handlers\Compression\GZipStream.cs" />
@ -107,10 +136,10 @@
<Compile Include="Protocol\Handlers\Compression\ZlibBaseStream.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibCodec.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibConstants.cs" />
<Compile Include="Protocol\Handlers\Json.cs" />
<Compile Include="Protocol\Handlers\ZlibUtils.cs" />
<Compile Include="Protocol\Handlers\ChatParser.cs" />
<Compile Include="Crypto\IAesStream.cs" />
<Compile Include="Crypto\IPaddingProvider.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="McTcpClient.cs" />
@ -119,8 +148,10 @@
<Compile Include="Protocol\Handlers\Protocol16.cs" />
<Compile Include="Protocol\IMinecraftCom.cs" />
<Compile Include="Protocol\IMinecraftComHandler.cs" />
<Compile Include="Protocol\Handlers\Protocol17.cs" />
<Compile Include="Protocol\ProtocolHandler.cs" />
<Compile Include="Protocol\SessionCache\CacheType.cs" />
<Compile Include="Protocol\SessionCache\SessionCache.cs" />
<Compile Include="Protocol\SessionToken.cs" />
<Compile Include="Proxy\ProxyHandler.cs" />
<Compile Include="Proxy\Handlers\EventArgs\CreateConnectionAsyncCompletedEventArgs.cs" />
<Compile Include="Proxy\Handlers\Exceptions\ProxyException.cs" />
@ -133,6 +164,7 @@
<Compile Include="Proxy\Handlers\Utils.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Commands\List.cs" />
<Compile Include="Mapping\Location.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">

View file

@ -1,329 +1,412 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Protocol;
using System.Reflection;
using System.Threading;
namespace MinecraftClient
{
/// <summary>
/// Minecraft Console Client by ORelio (c) 2012-2014.
/// Allows to connect to any Minecraft server, send and receive text, automated scripts.
/// This source code is released under the CDDL 1.0 License.
/// </summary>
static class Program
{
private static McTcpClient Client;
public static string[] startupargs;
public const string Version = "1.8.0";
private static Thread offlinePrompt = null;
/// <summary>
/// The main entry point of Minecraft Console Client
/// </summary>
static void Main(string[] args)
{
Console.WriteLine("Console Client for MC 1.4.6 to 1.8.1 - v" + Version + " - By ORelio & Contributors");
//Basic Input/Output ?
if (args.Length >= 1 && args[args.Length - 1] == "BasicIO")
{
ConsoleIO.basicIO = true;
Console.OutputEncoding = Console.InputEncoding = Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage);
args = args.Where(o => !Object.ReferenceEquals(o, args[args.Length - 1])).ToArray();
}
//Process ini configuration file
if (args.Length >= 1 && System.IO.File.Exists(args[0]) && System.IO.Path.GetExtension(args[0]).ToLower() == ".ini")
{
Settings.LoadSettings(args[0]);
//remove ini configuration file from arguments array
List<string> args_tmp = args.ToList<string>();
args_tmp.RemoveAt(0);
args = args_tmp.ToArray();
}
else if (System.IO.File.Exists("MinecraftClient.ini"))
{
Settings.LoadSettings("MinecraftClient.ini");
}
else Settings.WriteDefaultSettings("MinecraftClient.ini");
//Other command-line arguments
if (args.Length >= 1)
{
Settings.Login = args[0];
if (args.Length >= 2)
{
Settings.Password = args[1];
if (args.Length >= 3)
{
Settings.setServerIP(args[2]);
//Single command?
if (args.Length >= 4)
{
Settings.SingleCommand = args[3];
}
}
}
}
if (Settings.ConsoleTitle != "")
{
Settings.Username = "New Window";
Console.Title = Settings.expandVars(Settings.ConsoleTitle);
}
//Asking the user to type in missing data such as Username and Password
if (Settings.Login == "")
{
Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : ");
Settings.Login = Console.ReadLine();
}
if (Settings.Password == "")
{
Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : ");
Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword();
if (Settings.Password == "") { Settings.Password = "-"; }
if (!ConsoleIO.basicIO)
{
//Hide password length
Console.CursorTop--; Console.Write("Password : <******>");
for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); }
}
}
startupargs = args;
InitializeClient();
}
/// <summary>
/// Start a new Client
/// </summary>
private static void InitializeClient()
{
ProtocolHandler.LoginResult result;
Settings.Username = Settings.Login;
string sessionID = "";
string UUID = "";
if (Settings.Password == "-")
{
ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode.");
result = ProtocolHandler.LoginResult.Success;
sessionID = "0";
}
else
{
Console.WriteLine("Connecting to Minecraft.net...");
result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref UUID);
}
if (result == ProtocolHandler.LoginResult.Success)
{
if (Settings.ConsoleTitle != "")
Console.Title = Settings.expandVars(Settings.ConsoleTitle);
if (Settings.playerHeadAsIcon)
ConsoleIcon.setPlayerIconAsync(Settings.Username);
Console.WriteLine("Success. (session ID: " + sessionID + ')');
if (Settings.ServerIP == "")
{
Console.Write("Server IP : ");
Settings.setServerIP(Console.ReadLine());
}
//Get server version
int protocolversion = 0;
if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto")
{
protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion);
if (protocolversion != 0)
{
ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')');
}
else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode.");
}
if (protocolversion == 0)
{
Console.WriteLine("Retrieving Server Info...");
if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion))
{
Console.WriteLine("Failed to ping this IP.");
if (Settings.AutoRelog_Enabled)
{
ChatBots.AutoRelog bot = new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries);
if (!bot.OnDisconnect(ChatBot.DisconnectReason.ConnectionLost, "Failed to ping this IP.")) { OfflineCommandPrompt(); }
}
else OfflineCommandPrompt();
return;
}
}
if (protocolversion != 0)
{
try
{
//Start the main TCP client
if (Settings.SingleCommand != "")
{
Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, Settings.SingleCommand);
}
else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, Settings.ServerIP, Settings.ServerPort);
}
catch (NotSupportedException)
{
Console.WriteLine("Cannot connect to the server : This version is not supported !");
OfflineCommandPrompt();
}
}
else
{
Console.WriteLine("Failed to determine server version.");
OfflineCommandPrompt();
}
}
else
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write("Connection failed : ");
switch (result)
{
case ProtocolHandler.LoginResult.AccountMigrated: Console.WriteLine("Account migrated, use e-mail as username."); break;
case ProtocolHandler.LoginResult.ServiceUnavailable: Console.WriteLine("Login servers are unavailable. Please try again later."); break;
case ProtocolHandler.LoginResult.WrongPassword: Console.WriteLine("Incorrect password."); break;
case ProtocolHandler.LoginResult.NotPremium: Console.WriteLine("User not premium."); break;
case ProtocolHandler.LoginResult.OtherError: Console.WriteLine("Network error."); break;
case ProtocolHandler.LoginResult.SSLError: Console.WriteLine("SSL Error.");
if (isUsingMono)
{
ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program."
+ '\n' + "The first time, you have to import HTTPS certificates using:"
+ '\n' + "mozroots --import --ask-remove");
return;
}
break;
}
while (Console.KeyAvailable) { Console.ReadKey(false); }
if (Settings.SingleCommand == "") { OfflineCommandPrompt(); }
}
}
/// <summary>
/// Disconnect the current client from the server and restart it
/// </summary>
public static void Restart()
{
new Thread(new ThreadStart(delegate
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); }
Console.WriteLine("Restarting Minecraft Console Client...");
InitializeClient();
})).Start();
}
/// <summary>
/// Disconnect the current client from the server and exit the app
/// </summary>
public static void Exit()
{
new Thread(new ThreadStart(delegate
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); }
if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); }
Environment.Exit(0);
})).Start();
}
/// <summary>
/// Pause the program, usually when an error or a kick occured, letting the user press Enter to quit OR type /reconnect
/// </summary>
public static void OfflineCommandPrompt()
{
if (!Settings.exitOnFailure && offlinePrompt == null)
{
offlinePrompt = new Thread(new ThreadStart(delegate
{
string command = " ";
ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help.");
ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client.");
while (command.Length > 0)
{
if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); }
command = Console.ReadLine().Trim();
if (command.Length > 0)
{
if (Settings.internalCmdChar != ' ' && command[0] == Settings.internalCmdChar)
{
string message = "";
command = command.Substring(1);
if (command.StartsWith("reco"))
{
message = new Commands.Reco().Run(null, Settings.expandVars(command));
}
else if (command.StartsWith("connect"))
{
message = new Commands.Connect().Run(null, Settings.expandVars(command));
}
else if (command.StartsWith("exit") || command.StartsWith("quit"))
{
message = new Commands.Exit().Run(null, Settings.expandVars(command));
}
else if (command.StartsWith("help"))
{
ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc);
ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc);
}
else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'.");
if (message != "") { ConsoleIO.WriteLineFormatted("§8MCC: " + message); }
}
else ConsoleIO.WriteLineFormatted("§8Please type a command or press Enter to exit.");
}
}
}));
offlinePrompt.Start();
}
}
/// <summary>
/// Detect if the user is running Minecraft Console Client through Mono
/// </summary>
public static bool isUsingMono
{
get
{
return Type.GetType("Mono.Runtime") != null;
}
}
/// <summary>
/// Enumerate types in namespace through reflection
/// </summary>
/// <param name="nameSpace">Namespace to process</param>
/// <param name="assembly">Assembly to use. Default is Assembly.GetExecutingAssembly()</param>
/// <returns></returns>
public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null)
{
if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); }
return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Protocol;
using System.Reflection;
using System.Threading;
using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.SessionCache;
namespace MinecraftClient
{
/// <summary>
/// Minecraft Console Client by ORelio and Contributors (c) 2012-2016.
/// Allows to connect to any Minecraft server, send and receive text, automated scripts.
/// This source code is released under the CDDL 1.0 License.
/// </summary>
static class Program
{
private static McTcpClient Client;
public static string[] startupargs;
public const string Version = "1.9.0 BETA";
public const string MCLowestVersion = "1.4.6";
public const string MCHighestVersion = "1.9.0";
private static Thread offlinePrompt = null;
private static bool useMcVersionOnce = false;
/// <summary>
/// The main entry point of Minecraft Console Client
/// </summary>
static void Main(string[] args)
{
Console.WriteLine("Console Client for MC {0} to {1} - v{2} - By ORelio & Contributors", MCLowestVersion, MCHighestVersion, Version);
//Basic Input/Output ?
if (args.Length >= 1 && args[args.Length - 1] == "BasicIO")
{
ConsoleIO.basicIO = true;
Console.OutputEncoding = Console.InputEncoding = Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage);
args = args.Where(o => !Object.ReferenceEquals(o, args[args.Length - 1])).ToArray();
}
//Process ini configuration file
if (args.Length >= 1 && System.IO.File.Exists(args[0]) && System.IO.Path.GetExtension(args[0]).ToLower() == ".ini")
{
Settings.LoadSettings(args[0]);
//remove ini configuration file from arguments array
List<string> args_tmp = args.ToList<string>();
args_tmp.RemoveAt(0);
args = args_tmp.ToArray();
}
else if (System.IO.File.Exists("MinecraftClient.ini"))
{
Settings.LoadSettings("MinecraftClient.ini");
}
else Settings.WriteDefaultSettings("MinecraftClient.ini");
//Other command-line arguments
if (args.Length >= 1)
{
Settings.Login = args[0];
if (args.Length >= 2)
{
Settings.Password = args[1];
if (args.Length >= 3)
{
Settings.SetServerIP(args[2]);
//Single command?
if (args.Length >= 4)
{
Settings.SingleCommand = args[3];
}
}
}
}
if (Settings.ConsoleTitle != "")
{
Settings.Username = "New Window";
Console.Title = Settings.ExpandVars(Settings.ConsoleTitle);
}
//Load cached sessions from disk if necessary
if (Settings.SessionCaching == CacheType.Disk)
{
bool cacheLoaded = SessionCache.InitializeDiskCache();
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted(cacheLoaded ? "§8Session cache has been successfully loaded from disk." : "§8Cached sessions could not be loaded from disk");
}
//Asking the user to type in missing data such as Username and Password
if (Settings.Login == "")
{
Console.Write(ConsoleIO.basicIO ? "Please type the username or email of your choice.\n" : "Login : ");
Settings.Login = Console.ReadLine();
}
if (Settings.Password == "" && (Settings.SessionCaching == CacheType.None || !SessionCache.Contains(Settings.Login.ToLower())))
{
RequestPassword();
}
startupargs = args;
InitializeClient();
}
/// <summary>
/// Reduest user to submit password.
/// </summary>
private static void RequestPassword()
{
Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : ");
Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword();
if (Settings.Password == "") { Settings.Password = "-"; }
if (!ConsoleIO.basicIO)
{
//Hide password length
Console.CursorTop--; Console.Write("Password : <******>");
for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); }
}
}
/// <summary>
/// Start a new Client
/// </summary>
private static void InitializeClient()
{
SessionToken session = new SessionToken();
ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired;
if (Settings.Password == "-")
{
ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode.");
result = ProtocolHandler.LoginResult.Success;
session.PlayerID = "0";
session.PlayerName = Settings.Login;
}
else
{
// Validate cached session or login new session.
if (Settings.SessionCaching != CacheType.None && SessionCache.Contains(Settings.Login.ToLower()))
{
session = SessionCache.Get(Settings.Login.ToLower());
result = ProtocolHandler.GetTokenValidation(session);
if (result != ProtocolHandler.LoginResult.Success)
{
ConsoleIO.WriteLineFormatted("§8Cached session is invalid or expired.");
if (Settings.Password == "")
RequestPassword();
}
else ConsoleIO.WriteLineFormatted("§8Cached session is still valid for " + session.PlayerName + '.');
}
if (result != ProtocolHandler.LoginResult.Success)
{
Console.WriteLine("Connecting to Minecraft.net...");
result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session);
if (result == ProtocolHandler.LoginResult.Success && Settings.SessionCaching != CacheType.None)
{
SessionCache.Store(Settings.Login.ToLower(), session);
}
}
}
if (result == ProtocolHandler.LoginResult.Success)
{
Settings.Username = session.PlayerName;
if (Settings.ConsoleTitle != "")
Console.Title = Settings.ExpandVars(Settings.ConsoleTitle);
if (Settings.playerHeadAsIcon)
ConsoleIcon.setPlayerIconAsync(Settings.Username);
if (Settings.DebugMessages)
Console.WriteLine("Success. (session ID: " + session.ID + ')');
//ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE
if (Settings.ServerIP == "")
{
Console.Write("Server IP : ");
Settings.SetServerIP(Console.ReadLine());
}
//Get server version
int protocolversion = 0;
ForgeInfo forgeInfo = null;
if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto")
{
protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion);
if (protocolversion != 0)
{
ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')');
}
else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode.");
if (useMcVersionOnce)
{
useMcVersionOnce = false;
Settings.ServerVersion = "";
}
}
if (protocolversion == 0)
{
Console.WriteLine("Retrieving Server Info...");
if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo))
{
HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost);
return;
}
}
if (protocolversion != 0)
{
try
{
//Start the main TCP client
if (Settings.SingleCommand != "")
{
Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand);
}
else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort);
//Update console title
if (Settings.ConsoleTitle != "")
Console.Title = Settings.ExpandVars(Settings.ConsoleTitle);
}
catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); }
}
else HandleFailure("Failed to determine server version.", true);
}
else
{
Console.ForegroundColor = ConsoleColor.Gray;
string failureMessage = "Minecraft Login failed : ";
switch (result)
{
case ProtocolHandler.LoginResult.AccountMigrated: failureMessage += "Account migrated, use e-mail as username."; break;
case ProtocolHandler.LoginResult.ServiceUnavailable: failureMessage += "Login servers are unavailable. Please try again later."; break;
case ProtocolHandler.LoginResult.WrongPassword: failureMessage += "Incorrect password."; break;
case ProtocolHandler.LoginResult.NotPremium: failureMessage += "User not premium."; break;
case ProtocolHandler.LoginResult.OtherError: failureMessage += "Network error."; break;
case ProtocolHandler.LoginResult.SSLError: failureMessage += "SSL Error."; break;
default: failureMessage += "Unknown Error."; break;
}
if (result == ProtocolHandler.LoginResult.SSLError && isUsingMono)
{
ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program."
+ '\n' + "The first time, you have to import HTTPS certificates using:"
+ '\n' + "mozroots --import --ask-remove");
return;
}
HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected);
}
}
/// <summary>
/// Disconnect the current client from the server and restart it
/// </summary>
public static void Restart()
{
new Thread(new ThreadStart(delegate
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); }
Console.WriteLine("Restarting Minecraft Console Client...");
InitializeClient();
})).Start();
}
/// <summary>
/// Disconnect the current client from the server and exit the app
/// </summary>
public static void Exit()
{
new Thread(new ThreadStart(delegate
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); }
if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); }
Environment.Exit(0);
})).Start();
}
/// <summary>
/// Handle fatal errors such as ping failure, login failure, server disconnection, and so on.
/// Allows AutoRelog to perform on fatal errors, prompt for server version, and offline commands.
/// </summary>
/// <param name="errorMessage">Error message to display and optionally pass to AutoRelog bot</param>
/// <param name="versionError">Specify if the error is related to an incompatible or unkown server version</param>
/// <param name="disconnectReason">If set, the error message will be processed by the AutoRelog bot</param>
public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null)
{
if (!String.IsNullOrEmpty(errorMessage))
{
ConsoleIO.Reset();
while (Console.KeyAvailable)
Console.ReadKey(true);
Console.WriteLine(errorMessage);
if (disconnectReason.HasValue)
{
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage))
return; //AutoRelog is triggering a restart of the client
}
}
if (Settings.interactiveMode)
{
if (versionError)
{
Console.Write("Server version : ");
Settings.ServerVersion = Console.ReadLine();
if (Settings.ServerVersion != "")
{
useMcVersionOnce = true;
Restart();
return;
}
}
if (offlinePrompt == null)
{
offlinePrompt = new Thread(new ThreadStart(delegate
{
string command = " ";
ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help.");
ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client.");
while (command.Length > 0)
{
if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); }
command = Console.ReadLine().Trim();
if (command.Length > 0)
{
string message = "";
if (Settings.internalCmdChar != ' '
&& command[0] == Settings.internalCmdChar)
command = command.Substring(1);
if (command.StartsWith("reco"))
{
message = new Commands.Reco().Run(null, Settings.ExpandVars(command));
}
else if (command.StartsWith("connect"))
{
message = new Commands.Connect().Run(null, Settings.ExpandVars(command));
}
else if (command.StartsWith("exit") || command.StartsWith("quit"))
{
message = new Commands.Exit().Run(null, Settings.ExpandVars(command));
}
else if (command.StartsWith("help"))
{
ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc);
ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc);
}
else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'.");
if (message != "")
ConsoleIO.WriteLineFormatted("§8MCC: " + message);
}
}
}));
offlinePrompt.Start();
}
}
}
/// <summary>
/// Detect if the user is running Minecraft Console Client through Mono
/// </summary>
public static bool isUsingMono
{
get
{
return Type.GetType("Mono.Runtime") != null;
}
}
/// <summary>
/// Enumerate types in namespace through reflection
/// </summary>
/// <param name="nameSpace">Namespace to process</param>
/// <param name="assembly">Assembly to use. Default is Assembly.GetExecutingAssembly()</param>
/// <returns></returns>
public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null)
{
if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); }
return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray();
}
}
}

View file

@ -19,31 +19,7 @@ namespace MinecraftClient.Protocol.Handlers
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;
}
return JSONData2String(Json.ParseJson(json), "");
}
/// <summary>
@ -109,12 +85,12 @@ namespace MinecraftClient.Protocol.Handlers
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 + '\'');
string assets_index = downloadString(Settings.TranslationsFile_Website_Index);
string[] tmp = assets_index.Split(new string[] { "minecraft/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
{
@ -147,7 +123,8 @@ namespace MinecraftClient.Protocol.Handlers
}
}
ConsoleIO.WriteLineFormatted("§8Translations file loaded.");
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted("§8Translations file loaded.");
}
else //No external dictionnary found.
{
@ -169,158 +146,68 @@ namespace MinecraftClient.Protocol.Handlers
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")))
int using_idx = 0;
string rule = TranslationRules[rulename];
StringBuilder result = new StringBuilder();
for (int i = 0; i < rule.Length; i++)
{
while (using_data.Count < 2) { using_data.Add(""); }
string tmp = using_data[0];
using_data[0] = using_data[1];
using_data[1] = tmp;
if (rule[i] == '%' && i + 1 < rule.Length)
{
//Using string or int with %s or %d
if (rule[i + 1] == 's' || rule[i + 1] == 'd')
{
if (using_data.Count > using_idx)
{
result.Append(using_data[using_idx]);
using_idx++;
i += 1;
continue;
}
}
//Using specified string or int with %1$s, %2$s...
else if (char.IsDigit(rule[i + 1])
&& i + 3 < rule.Length && rule[i + 2] == '$'
&& (rule[i + 3] == 's' || rule[i + 3] == 'd'))
{
int specified_idx = rule[i + 1] - '1';
if (using_data.Count > specified_idx)
{
result.Append(using_data[specified_idx]);
using_idx++;
i += 3;
continue;
}
}
}
result.Append(rule[i]);
}
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;
return result.ToString();
}
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)
private static string JSONData2String(Json.JSONData data, string colorcode)
{
string extra_result = "";
switch (data.Type)
{
case JSONData.DataType.Object:
case Json.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)
Json.JSONData[] extras = data.Properties["extra"].DataArray.ToArray();
foreach (Json.JSONData item in extras)
extra_result = extra_result + JSONData2String(item, colorcode) + "§r";
}
if (data.Properties.ContainsKey("text"))
@ -334,7 +221,7 @@ namespace MinecraftClient.Protocol.Handlers
data.Properties["with"] = data.Properties["using"];
if (data.Properties.ContainsKey("with"))
{
JSONData[] array = data.Properties["with"].DataArray.ToArray();
Json.JSONData[] array = data.Properties["with"].DataArray.ToArray();
for (int i = 0; i < array.Length; i++)
{
using_data.Add(JSONData2String(array[i], colorcode));
@ -344,29 +231,21 @@ namespace MinecraftClient.Protocol.Handlers
}
else return extra_result;
case JSONData.DataType.Array:
case Json.JSONData.DataType.Array:
string result = "";
foreach (JSONData item in data.DataArray)
foreach (Json.JSONData item in data.DataArray)
{
result += JSONData2String(item, colorcode);
}
return result;
case JSONData.DataType.String:
case Json.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>

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol.Handlers.Forge
{
/// <summary>
/// Copy of the forge enum for client states.
/// https://github.com/MinecraftForge/MinecraftForge/blob/ebe9b6d4cbc4a5281c386994f1fbda04df5d2e1f/src/main/java/net/minecraftforge/fml/common/network/handshake/FMLHandshakeClientState.java
/// </summary>
enum FMLHandshakeClientState : byte
{
START,
HELLO,
WAITINGSERVERDATA,
WAITINGSERVERCOMPLETE,
PENDINGCOMPLETE,
COMPLETE,
DONE,
ERROR
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol.Handlers.Forge
{
/// <summary>
/// Different "discriminator byte" values for the forge handshake.
/// https://github.com/MinecraftForge/MinecraftForge/blob/ebe9b6d4cbc4a5281c386994f1fbda04df5d2e1f/src/main/java/net/minecraftforge/fml/common/network/handshake/FMLHandshakeCodec.java
/// </summary>
enum FMLHandshakeDiscriminator : byte
{
ServerHello = 0,
ClientHello = 1,
ModList = 2,
RegistryData = 3,
HandshakeAck = 255, //-1
HandshakeReset = 254, //-2
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol.Handlers.Forge
{
/// <summary>
/// Contains information about a modded server install.
/// </summary>
public class ForgeInfo
{
/// <summary>
/// Represents an individual forge mod.
/// </summary>
public class ForgeMod
{
public ForgeMod(String ModID, String Version)
{
this.ModID = ModID;
this.Version = Version;
}
public readonly String ModID;
public readonly String Version;
public override string ToString()
{
return ModID + " v" + Version;
}
}
public List<ForgeMod> Mods;
/// <summary>
/// Create a new ForgeInfo from the given data.
/// </summary>
/// <param name="data">The modinfo JSON tag.</param>
internal ForgeInfo(Json.JSONData data)
{
// Example ModInfo (with spacing):
// "modinfo": {
// "type": "FML",
// "modList": [{
// "modid": "mcp",
// "version": "9.05"
// }, {
// "modid": "FML",
// "version": "8.0.99.99"
// }, {
// "modid": "Forge",
// "version": "11.14.3.1512"
// }, {
// "modid": "rpcraft",
// "version": "Beta 1.3 - 1.8.0"
// }]
// }
this.Mods = new List<ForgeMod>();
foreach (Json.JSONData mod in data.Properties["modList"].DataArray)
{
String modid = mod.Properties["modid"].StringValue;
String version = mod.Properties["version"].StringValue;
this.Mods.Add(new ForgeMod(modid, version));
}
}
}
}

View file

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// This class parses JSON data and returns an object describing that data.
/// Really lightweight JSON handling by ORelio - (c) 2013 - 2014
/// </summary>
static class Json
{
/// <summary>
/// Parse some JSON and return the corresponding JSON object
/// </summary>
public static JSONData ParseJson(string json)
{
int cursorpos = 0;
return String2Data(json, ref cursorpos);
}
/// <summary>
/// The class storing unserialized JSON data
/// The data can be an object, an array or a string
/// </summary>
public 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>
/// 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>
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;
//Number
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.':
data = new JSONData(JSONData.DataType.String);
StringBuilder sb = new StringBuilder();
while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.')
{
sb.Append(toparse[cursorpos]);
cursorpos++;
}
data.StringValue = sb.ToString();
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);
}
while (cursorpos < toparse.Length
&& (char.IsWhiteSpace(toparse[cursorpos])
|| toparse[cursorpos] == '\r'
|| toparse[cursorpos] == '\n'))
cursorpos++;
return data;
}
catch (IndexOutOfRangeException)
{
return new JSONData(JSONData.DataType.String);
}
}
/// <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')); }
}
}

View file

@ -7,6 +7,7 @@ using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
using System.Security.Cryptography;
using MinecraftClient.Mapping;
namespace MinecraftClient.Protocol.Handlers
{
@ -33,6 +34,12 @@ namespace MinecraftClient.Protocol.Handlers
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
if (Settings.TerrainAndMovements)
{
ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version.");
Settings.TerrainAndMovements = false;
}
}
private Protocol16Handler(TcpClient Client)
@ -42,19 +49,11 @@ namespace MinecraftClient.Protocol.Handlers
private void Updater()
{
int keep_alive_interval = 100;
int keep_alive_timer = 100;
try
{
do
{
Thread.Sleep(100);
keep_alive_timer--;
if (keep_alive_timer <= 0)
{
Send(getPaddingPacket());
keep_alive_timer = keep_alive_interval;
}
}
while (Update());
}
@ -171,7 +170,10 @@ namespace MinecraftClient.Protocol.Handlers
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 0xFA: string channel = readNextString();
byte[] payload = readNextByteArray();
handler.OnPluginChannelMessage(channel, payload);
break;
case 0xFF: string reason = readNextString();
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, reason); break;
default: return false; //unknown packet!
@ -449,15 +451,11 @@ namespace MinecraftClient.Protocol.Handlers
byte[] token = readNextByteArray();
if (serverID == "-")
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
return true; //No need to check session or start encryption
}
else
{
else if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted("§8Handshake successful. (Server ID: " + serverID + ')');
return StartEncryption(uuid, username, sessionID, token, serverID, PublicServerkey);
}
return StartEncryption(uuid, username, sessionID, token, serverID, PublicServerkey);
}
else return false;
}
@ -467,7 +465,8 @@ namespace MinecraftClient.Protocol.Handlers
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.");
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.");
if (serverIDhash != "-")
{
@ -504,7 +503,7 @@ namespace MinecraftClient.Protocol.Handlers
if (pid[0] == 0xFC)
{
readData(4);
s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
s = CryptoHandler.getAesStream(c.GetStream(), secretKey);
encrypted = true;
return true;
}
@ -513,7 +512,7 @@ namespace MinecraftClient.Protocol.Handlers
public bool Login()
{
if (Handshake(handler.getUserUUID(), handler.getUsername(), handler.getSessionID(), handler.getServerHost(), handler.getServerPort()))
if (Handshake(handler.GetUserUUID(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort()))
{
Send(new byte[] { 0xCD, 0 });
try
@ -622,6 +621,41 @@ namespace MinecraftClient.Protocol.Handlers
catch (SocketException) { return false; }
}
public bool SendBrandInfo(string brandInfo)
{
return false; //Only supported since MC 1.7
}
public bool SendLocationUpdate(Location location, bool onGround)
{
return false; //Currently not implemented
}
/// <summary>
/// Send a plugin channel packet to the server.
/// </summary>
/// <param name="channel">Channel to send packet on</param>
/// <param name="data">packet Data</param>
public bool SendPluginChannelPacket(string channel, byte[] data)
{
try {
byte[] channelLength = BitConverter.GetBytes((short)channel.Length);
Array.Reverse(channelLength);
byte[] channelData = Encoding.BigEndianUnicode.GetBytes(channel);
byte[] dataLength = BitConverter.GetBytes((short)data.Length);
Array.Reverse(dataLength);
Send(concatBytes(new byte[] { 0xFA }, channelLength, channelData, dataLength, data));
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
}
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
@ -652,25 +686,13 @@ namespace MinecraftClient.Protocol.Handlers
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)
{
try
{
string version = "";
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
tcp.ReceiveTimeout = 5000; //MC 1.7.2+ SpigotMC servers won't answer, so we need a reasonable timeout.
tcp.ReceiveTimeout = 5000; //MC 1.7.2+ SpigotMC servers won't respond, so we need a reasonable timeout.
byte[] ping = new byte[2] { 0xfe, 0x01 };
tcp.Client.Send(ping, SocketFlags.None);

View file

@ -1,614 +0,0 @@
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;
using System.Security.Cryptography;
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()
{
int keep_alive_interval = 100;
int keep_alive_timer = 100;
try
{
do
{
Thread.Sleep(100);
keep_alive_timer--;
if (keep_alive_timer <= 0)
{
Send(getPaddingPacket());
keep_alive_timer = keep_alive_interval;
}
}
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 0x38:
string name = readNextString();
bool online = readNextBool();
short ping = readNextShort();
Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray());
if (online)
{
handler.OnPlayerJoin(FakeUUID, name);
}
else handler.OnPlayerLeave(FakeUUID);
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 uuid from the network
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The uuid</returns>
private Guid readNextUUID()
{
byte[] cache = new byte[16];
Receive(cache, 0, 16, SocketFlags.None);
return new Guid(cache);
}
/// <summary>
/// Read a short from the network
/// </summary>
/// <returns></returns>
private short readNextShort()
{
byte[] tmp = new byte[2];
Receive(tmp, 0, 2, SocketFlags.None);
Array.Reverse(tmp);
return BitConverter.ToInt16(tmp, 0);
}
/// <summary>
/// Read a boolean from the network
/// </summary>
/// <returns></returns>
private bool readNextBool()
{
byte[] tmp = new byte[1];
Receive(tmp, 0, 1, SocketFlags.None);
return tmp[0] != 0x00;
}
/// <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.");
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.");
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; }
catch (System.IO.IOException) { 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)
{
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]);
//Handle if "name" exists twice, eg when connecting to a server with another user logged in.
version = (tmp_name.Length == 2) ? tmp_name[1].Split('"')[0] : tmp_name[2].Split('"')[0];
//Automatic fix for BungeeCord 1.8 not properly reporting protocol version
if (protocolversion < 47 && version.Split(' ').Contains("1.8"))
protocolversion = ProtocolHandler.MCVer2ProtocolVersion("1.8.0");
ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ").");
return true;
}
}
}
}
return false;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Crypto;
using MinecraftClient.Mapping;
namespace MinecraftClient.Protocol
{
@ -13,7 +14,7 @@ namespace MinecraftClient.Protocol
/// The protocol handler will take care of parsing and building the appropriate network packets.
/// </summary>
public interface IMinecraftCom : IDisposable, IAutoComplete, IPaddingProvider
public interface IMinecraftCom : IDisposable, IAutoComplete
{
/// <summary>
/// Start the login procedure once connected to the server
@ -43,5 +44,31 @@ namespace MinecraftClient.Protocol
/// <returns>True if packet successfully sent</returns>
bool SendRespawnPacket();
/// <summary>
/// Inform the server of the client being used to connect
/// </summary>
/// <param name="brandInfo">Client string describing the client</param>
/// <returns>True if brand info was successfully sent</returns>
bool SendBrandInfo(string brandInfo);
/// <summary>
/// Send a location update telling that we moved to that location
/// </summary>
/// <param name="location">The new location</param>
/// <returns>True if packet was successfully sent</returns>
bool SendLocationUpdate(Location location, bool onGround);
/// <summary>
/// Send a plugin channel packet to the server.
/// </summary>
/// <see href="http://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/" />
/// <param name="channel">Channel to send packet on</param>
/// <param name="data">packet Data</param>
/// <returns>True if message was successfully sent</returns>
bool SendPluginChannelPacket(string channel, byte[] data);
}
}

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Mapping;
namespace MinecraftClient.Protocol
{
@ -13,15 +14,23 @@ namespace MinecraftClient.Protocol
public interface IMinecraftComHandler
{
/* The MinecraftCom Hanler must
/* The MinecraftCom Handler must
* provide these getters */
int getServerPort();
string getServerHost();
string getUsername();
string getUserUUID();
string getSessionID();
string[] getOnlinePlayers();
int GetServerPort();
string GetServerHost();
string GetUsername();
string GetUserUUID();
string GetSessionID();
string[] GetOnlinePlayers();
Location GetCurrentLocation();
World GetWorld();
/// <summary>
/// Called when a server was successfully joined
/// </summary>
void OnGameJoined();
/// <summary>
/// This method is called when the protocol handler receives a chat message
@ -44,6 +53,13 @@ namespace MinecraftClient.Protocol
void OnPlayerLeave(Guid uuid);
/// <summary>
/// Called when the server sets the new location for the player
/// </summary>
/// <param name="location">New location of the player</param>
void UpdateLocation(Location location);
/// <summary>
/// This method is called when the connection has been lost
/// </summary>
@ -56,5 +72,40 @@ namespace MinecraftClient.Protocol
/// </summary>
void OnUpdate();
/// <summary>
/// Registers the given plugin channel for the given bot.
/// </summary>
/// <param name="channel">The channel to register.</param>
/// <param name="bot">The bot to register the channel for.</param>
void RegisterPluginChannel(string channel, ChatBot bot);
/// <summary>
/// Unregisters the given plugin channel for the given bot.
/// </summary>
/// <param name="channel">The channel to unregister.</param>
/// <param name="bot">The bot to unregister the channel for.</param>
void UnregisterPluginChannel(string channel, ChatBot bot);
/// <summary>
/// Sends a plugin channel packet to the server.
/// See http://wiki.vg/Plugin_channel for more information about plugin channels.
/// </summary>
/// <param name="channel">The channel to send the packet on.</param>
/// <param name="data">The payload for the packet.</param>
/// <param name="sendEvenIfNotRegistered">Whether the packet should be sent even if the server or the client hasn't registered it yet.</param>
/// <returns>Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered.</returns>
bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false);
/// <summary>
/// Called when a plugin channel message was sent from the server.
/// </summary>
/// <param name="channel">The channel the message was sent on</param>
/// <param name="data">The data from the channel</param>
void OnPluginChannelMessage(string channel, byte[] data);
}
}

View file

@ -1,291 +1,440 @@
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="serverPort">Server Port to ping</param>
/// <param name="protocolversion">Will contain protocol version, if ping successful</param>
/// <returns>TRUE if ping was successful</returns>
public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion)
{
try
{
if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversion))
{
return true;
}
else if (Protocol17Handler.doPing(serverIP, serverPort, ref protocolversion))
{
return true;
}
else
{
ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)");
return false;
}
}
catch
{
ConsoleIO.WriteLineFormatted("§8An error occured while attempting to connect to this IP.");
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);
int[] supportedVersions_Protocol18 = { 47 };
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
return new Protocol18Handler(Client, ProtocolVersion, Handler);
throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported.");
}
/// <summary>
/// Convert a human-readable Minecraft version number to network protocol version number
/// </summary>
/// <param name="MCVersion">The Minecraft version number</param>
/// <returns>The protocol version number or 0 if could not determine protocol version: error, unknown, not supported</returns>
public static int MCVer2ProtocolVersion(string MCVersion)
{
if (MCVersion.Contains('.'))
{
switch (MCVersion.Split(' ')[0].Trim())
{
case "1.4.6":
case "1.4.7":
return 51;
case "1.5.1":
return 60;
case "1.5.2":
return 61;
case "1.6.0":
return 72;
case "1.6.1":
case "1.6.2":
case "1.6.3":
case "1.6.4":
return 73;
case "1.7.2":
case "1.7.3":
case "1.7.4":
case "1.7.5":
return 4;
case "1.7.6":
case "1.7.7":
case "1.7.8":
case "1.7.9":
case "1.7.10":
return 5;
case "1.8.0":
case "1.8.1":
return 47;
default:
return 0;
}
}
else
{
try
{
return Int32.Parse(MCVersion);
}
catch
{
return -1;
}
}
}
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\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\" }";
int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result);
if (code == 200)
{
if (result.Contains("availableProfiles\":[]}"))
{
return LoginResult.NotPremium;
}
else
{
string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { user = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; }
return LoginResult.Success;
}
}
else if (code == 403)
{
if (result.Contains("UserMigratedException"))
{
return LoginResult.AccountMigrated;
}
else return LoginResult.WrongPassword;
}
else if (code == 503)
{
return LoginResult.ServiceUnavailable;
}
else
{
ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code);
return LoginResult.OtherError;
}
}
catch (System.Security.Authentication.AuthenticationException)
{
return LoginResult.SSLError;
}
catch (System.IO.IOException e)
{
if (e.Message.Contains("authentication"))
{
return LoginResult.SSLError;
}
else return LoginResult.OtherError;
}
catch
{
return LoginResult.OtherError;
}
}
/// <summary>
/// Check session using Mojang's Yggdrasil authentication scheme. Allows 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)
{
string postResult = null;
int statusCode = 520;
AutoTimeout.Perform(() =>
{
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"))
{
postResult = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
statusCode = Settings.str2int(raw_result.Split(' ')[1]);
}
else statusCode = 520; //Web server is returning an unknown error
}, 15000);
result = postResult;
return statusCode;
}
/// <summary>
/// Encode a string to a json string.
/// Will convert special chars to \u0000 unicode escape sequences.
/// </summary>
/// <param name="text">Source text</param>
/// <returns>Encoded text</returns>
private static string jsonEncode(string text)
{
StringBuilder result = new StringBuilder();
foreach (char c in text)
{
if (char.IsLetterOrDigit(c))
{
result.Append(c);
}
else
{
result.Append("\\u");
result.Append(((int)c).ToString("x4"));
}
}
return result.ToString();
}
}
}
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;
using MinecraftClient.Protocol.Handlers.Forge;
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="serverPort">Server Port to ping</param>
/// <param name="protocolversion">Will contain protocol version, if ping successful</param>
/// <returns>TRUE if ping was successful</returns>
public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion, ref ForgeInfo forgeInfo)
{
bool success = false;
int protocolversionTmp = 0;
ForgeInfo forgeInfoTmp = null;
if (AutoTimeout.Perform(() =>
{
try
{
if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp)
|| Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp, ref forgeInfoTmp))
{
success = true;
}
else ConsoleIO.WriteLineFormatted("§8Unexpected response from the server (is that a Minecraft server?)");
}
catch (Exception e)
{
ConsoleIO.WriteLineFormatted(String.Format("§8{0}: {1}", e.GetType().FullName, e.Message));
}
}, TimeSpan.FromSeconds(30)))
{
protocolversion = protocolversionTmp;
forgeInfo = forgeInfoTmp;
return success;
}
else
{
ConsoleIO.WriteLineFormatted("§8A timeout occured while attempting to connect to this IP.");
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, ForgeInfo forgeInfo, 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_Protocol18 = { 4, 5, 47, 107 };
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo);
throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported.");
}
/// <summary>
/// Convert a human-readable Minecraft version number to network protocol version number
/// </summary>
/// <param name="MCVersion">The Minecraft version number</param>
/// <returns>The protocol version number or 0 if could not determine protocol version: error, unknown, not supported</returns>
public static int MCVer2ProtocolVersion(string MCVersion)
{
if (MCVersion.Contains('.'))
{
switch (MCVersion.Split(' ')[0].Trim())
{
case "1.4.6":
case "1.4.7":
return 51;
case "1.5.1":
return 60;
case "1.5.2":
return 61;
case "1.6.0":
return 72;
case "1.6.1":
case "1.6.2":
case "1.6.3":
case "1.6.4":
return 73;
case "1.7.2":
case "1.7.3":
case "1.7.4":
case "1.7.5":
return 4;
case "1.7.6":
case "1.7.7":
case "1.7.8":
case "1.7.9":
case "1.7.10":
return 5;
case "1.8.0":
case "1.8.1":
case "1.8.2":
case "1.8.3":
case "1.8.4":
case "1.8.5":
case "1.8.6":
case "1.8.7":
case "1.8.8":
case "1.8.9":
return 47;
case "1.9.0":
return 107;
default:
return 0;
}
}
else
{
try
{
return Int32.Parse(MCVersion);
}
catch
{
return 0;
}
}
}
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, NullError };
/// <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="clienttoken">Will contain the client token generated before sending to Minecraft.net</param>
/// <param name="uuid">Will contain the player's PlayerID, needed for multiplayer</param>
/// <returns>Returns the status of the login (Success, Failure, etc.)</returns>
public static LoginResult GetLogin(string user, string pass, out SessionToken session)
{
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
try
{
string result = "";
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }";
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) { session.ID = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { session.PlayerName = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { session.PlayerID = temp[1].Split('"')[0]; }
return LoginResult.Success;
}
}
else if (code == 403)
{
if (result.Contains("UserMigratedException"))
{
return LoginResult.AccountMigrated;
}
else return LoginResult.WrongPassword;
}
else if (code == 503)
{
return LoginResult.ServiceUnavailable;
}
else
{
ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code);
return LoginResult.OtherError;
}
}
catch (System.Security.Authentication.AuthenticationException)
{
return LoginResult.SSLError;
}
catch (System.IO.IOException e)
{
if (e.Message.Contains("authentication"))
{
return LoginResult.SSLError;
}
else return LoginResult.OtherError;
}
catch
{
return LoginResult.OtherError;
}
}
/// <summary>
/// Validates whether accessToken must be refreshed
/// </summary>
/// <param name="accesstoken">Will contain the cached access token previously returned by Minecraft.net</param>
/// <param name="clienttoken">Will contain the cached client token created on login</param>
/// <returns>Returns the status of the token (Valid, Invalid, etc.)</returns>
///
public static LoginResult GetTokenValidation(SessionToken session)
{
try
{
string result = "";
string json_request = "{\"accessToken\": \"" + jsonEncode(session.ID) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }";
int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result);
if (code == 204)
{
return LoginResult.Success;
}
else if (code == 403)
{
return LoginResult.LoginRequired;
}
else
{
return LoginResult.OtherError;
}
}
catch
{
return LoginResult.OtherError;
}
}
/// <summary>
/// Refreshes invalid token
/// </summary>
/// <param name="user">Login</param>
/// <param name="accesstoken">Will contain the new access token returned by Minecraft.net, if the refresh is successful</param>
/// <param name="clienttoken">Will contain the client token generated before sending to Minecraft.net</param>
/// <param name="uuid">Will contain the player's PlayerID, needed for multiplayer</param>
/// <returns>Returns the status of the new token request (Success, Failure, etc.)</returns>
///
public static LoginResult GetNewToken(SessionToken currentsession, out SessionToken newsession)
{
newsession = new SessionToken();
try
{
string result = "";
string json_request = "{ \"accessToken\": \"" + jsonEncode(currentsession.ID) + "\", \"clientToken\": \"" + jsonEncode(currentsession.ClientID) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(currentsession.PlayerID) + "\", \"name\": \"" + jsonEncode(currentsession.PlayerName) + "\" } }";
int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result);
if (code == 200)
{
if (result == null)
{
return LoginResult.NullError;
}
else {
string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { newsession.ID = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { newsession.ClientID = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { newsession.PlayerName = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { newsession.PlayerID = temp[1].Split('"')[0]; }
return LoginResult.Success;
}
}
else if (code == 403 && result.Contains("InvalidToken"))
{
return LoginResult.InvalidToken;
}
else
{
ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code);
return LoginResult.OtherError;
}
}
catch
{
return LoginResult.OtherError;
}
}
/// <summary>
/// Check session using Mojang's Yggdrasil authentication scheme. Allows 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; }
}
public static void RealmsListWorlds(string username, string uuid, string accesstoken)
{
string result = "";
string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username, Program.MCHighestVersion);
doHTTPSGet("mcoapi.minecraft.net", "/worlds", cookies, ref result);
Console.WriteLine(result);
}
/// <summary>
/// Make a HTTPS GET request to the specified endpoint of the Mojang API
/// </summary>
/// <param name="host">Host to connect to</param>
/// <param name="endpoint">Endpoint for making the request</param>
/// <param name="cookies">Cookies for making the request</param>
/// <param name="result">Request result</param>
/// <returns>HTTP Status code</returns>
private static int doHTTPSGet(string host, string endpoint, string cookies, ref string result)
{
List<String> http_request = new List<string>();
http_request.Add("GET " + endpoint + " HTTP/1.1");
http_request.Add("Cookie: " + cookies);
http_request.Add("Cache-Control: no-cache");
http_request.Add("Pragma: no-cache");
http_request.Add("Host: " + host);
http_request.Add("User-Agent: Java/1.6.0_27");
http_request.Add("Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7");
http_request.Add("Connection: close");
http_request.Add("");
return doHTTPSRequest(http_request, host, ref result);
}
/// <summary>
/// Make a HTTPS POST request to the specified endpoint of the Mojang API
/// </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)
{
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);
return doHTTPSRequest(http_request, host, ref result);
}
/// <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="headers">Request headers and optional body (POST)</param>
/// <param name="host">Host to connect to</param>
/// <param name="result">Request result</param>
/// <returns>HTTP Status code</returns>
private static int doHTTPSRequest(List<string> headers, string host, ref string result)
{
string postResult = null;
int statusCode = 520;
AutoTimeout.Perform(() =>
{
TcpClient client = ProxyHandler.newTcpClient(host, 443, true);
SslStream stream = new SslStream(client.GetStream());
stream.AuthenticateAsClient(host);
stream.Write(Encoding.ASCII.GetBytes(String.Join("\r\n", headers.ToArray())));
System.IO.StreamReader sr = new System.IO.StreamReader(stream);
string raw_result = sr.ReadToEnd();
if (raw_result.StartsWith("HTTP/1.1"))
{
postResult = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
statusCode = Settings.str2int(raw_result.Split(' ')[1]);
}
else statusCode = 520; //Web server is returning an unknown error
}, TimeSpan.FromSeconds(30));
result = postResult;
return statusCode;
}
/// <summary>
/// Encode a string to a json string.
/// Will convert special chars to \u0000 unicode escape sequences.
/// </summary>
/// <param name="text">Source text</param>
/// <returns>Encoded text</returns>
private static string jsonEncode(string text)
{
StringBuilder result = new StringBuilder();
foreach (char c in text)
{
if ((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z'))
{
result.Append(c);
}
else
{
result.AppendFormat(@"\u{0:x4}", (int)c);
}
}
return result.ToString();
}
}
}

View file

@ -0,0 +1,4 @@
namespace MinecraftClient.Protocol.SessionCache
{
public enum CacheType { None, Memory, Disk };
}

View file

@ -0,0 +1,177 @@
using MinecraftClient.Protocol;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Timers;
namespace MinecraftClient.Protocol.SessionCache
{
/// <summary>
/// Handle sessions caching and storage.
/// </summary>
public static class SessionCache
{
private const string SessionCacheFile = "SessionCache.db";
private static Dictionary<string, SessionToken> sessions = new Dictionary<string, SessionToken>();
private static FileSystemWatcher cachemonitor = new FileSystemWatcher();
private static Timer updatetimer = new Timer(100);
private static List<KeyValuePair<string, SessionToken>> pendingadds = new List<KeyValuePair<string, SessionToken>>();
private static BinaryFormatter formatter = new BinaryFormatter();
/// <summary>
/// Retrieve whether SessionCache contains a session for the given login.
/// </summary>
/// <param name="login">User login used with Minecraft.net</param>
/// <returns>TRUE if session is available</returns>
public static bool Contains(string login)
{
return sessions.ContainsKey(login);
}
/// <summary>
/// Store a session and save it to disk if required.
/// </summary>
/// <param name="login">User login used with Minecraft.net</param>
/// <param name="session">User session token used with Minecraft.net</param>
public static void Store(string login, SessionToken session)
{
if (Contains(login))
{
sessions[login] = session;
}
else
{
sessions.Add(login, session);
}
if (Settings.SessionCaching == CacheType.Disk && updatetimer.Enabled == true)
{
pendingadds.Add(new KeyValuePair<string, SessionToken>(login, session));
}
else if (Settings.SessionCaching == CacheType.Disk)
{
SaveToDisk();
}
}
/// <summary>
/// Retrieve a session token for the given login.
/// </summary>
/// <param name="login">User login used with Minecraft.net</param>
/// <returns>SessionToken for given login</returns>
public static SessionToken Get(string login)
{
return sessions[login];
}
/// <summary>
/// Initialize cache monitoring to keep cache updated with external changes.
/// </summary>
/// <returns>TRUE if session tokens are seeded from file</returns>
public static bool InitializeDiskCache()
{
cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory;
cachemonitor.IncludeSubdirectories = false;
cachemonitor.Filter = SessionCacheFile;
cachemonitor.NotifyFilter = NotifyFilters.LastWrite;
cachemonitor.Changed += new FileSystemEventHandler(OnChanged);
cachemonitor.EnableRaisingEvents = true;
updatetimer.Elapsed += HandlePending;
return LoadFromDisk();
}
/// <summary>
/// Reloads cache on external cache file change.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event data</param>
private static void OnChanged(object sender, FileSystemEventArgs e)
{
updatetimer.Stop();
updatetimer.Start();
}
/// <summary>
/// Called after timer elapsed. Reads disk cache and adds new/modified sessions back.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event data</param>
private static void HandlePending(object sender, ElapsedEventArgs e)
{
LoadFromDisk();
foreach(KeyValuePair<string, SessionToken> pending in pendingadds.ToArray())
{
Store(pending.Key, pending.Value);
pendingadds.Remove(pending);
}
}
/// <summary>
/// Reads cache file and loads SessionTokens into SessionCache.
/// </summary>
/// <returns>True if data is successfully loaded</returns>
private static bool LoadFromDisk()
{
if (File.Exists(SessionCacheFile))
{
try
{
using (FileStream fs = new FileStream(SessionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read))
{
sessions = (Dictionary<string, SessionToken>)formatter.Deserialize(fs);
return true;
}
}
catch (IOException ex)
{
Console.WriteLine("Error reading cached sessions from disk: " + ex.Message);
}
catch (SerializationException)
{
Console.WriteLine("Malformed sessions from cache file ");
}
}
return false;
}
/// <summary>
/// Saves SessionToken's from SessionCache into cache file.
/// </summary>
private static void SaveToDisk()
{
bool fileexists = File.Exists(SessionCacheFile);
using (FileStream fs = new FileStream(SessionCacheFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
cachemonitor.EnableRaisingEvents = false;
// delete existing file contents
if (fileexists)
{
fs.SetLength(0);
fs.Flush();
}
formatter.Serialize(fs, sessions);
cachemonitor.EnableRaisingEvents = true;
}
}
}
}

View file

@ -0,0 +1,21 @@
using System;
namespace MinecraftClient.Protocol
{
[Serializable]
public class SessionToken
{
public string ID { get; set; }
public string PlayerName { get; set; }
public string PlayerID { get; set; }
public string ClientID { get; set; }
public SessionToken()
{
ID = String.Empty;
PlayerName = String.Empty;
PlayerID = String.Empty;
ClientID = String.Empty;
}
}
}

View file

@ -24,12 +24,15 @@ namespace MinecraftClient.Proxy
/// <summary>
/// Create a regular TcpClient or a proxied TcpClient according to the app Settings.
/// </summary>
/// <param name="host">Target host</param>
/// <param name="port">Target port</param>
/// <param name="login">True if the purpose is logging in to a Minecraft account</param>
public static TcpClient newTcpClient(string host, int port)
public static TcpClient newTcpClient(string host, int port, bool login = false)
{
try
{
if (Settings.ProxyEnabled)
if (login ? Settings.ProxyEnabledLogin : Settings.ProxyEnabledIngame)
{
ProxyType innerProxytype = ProxyType.Http;
@ -61,7 +64,7 @@ namespace MinecraftClient.Proxy
{
ConsoleIO.WriteLineFormatted("§8" + e.Message);
proxy = null;
return null;
return null;
}
}
}

View file

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using MinecraftClient.Protocol.SessionCache;
namespace MinecraftClient
{
@ -13,6 +15,9 @@ namespace MinecraftClient
public static class Settings
{
//Minecraft Console Client client information used for BrandInfo setting
private const string MCCBrandInfo = "Minecraft-Console-Client/" + Program.Version;
//Main Settings.
//Login: Username or email adress used as login for Minecraft/Mojang account
//Username: The actual username of the user, obtained after login to the account
@ -21,12 +26,13 @@ namespace MinecraftClient
public static string Password = "";
public static string ServerIP = "";
public static ushort ServerPort = 25565;
public static string ServerVersion = "";
public static string ServerVersion = "";
public static string SingleCommand = "";
public static string ConsoleTitle = "";
//Proxy Settings
public static bool ProxyEnabled = false;
public static bool ProxyEnabledLogin = false;
public static bool ProxyEnabledIngame = false;
public static string ProxyHost = "";
public static int ProxyPort = 0;
public static Proxy.ProxyHandler.Type proxyType = Proxy.ProxyHandler.Type.HTTP;
@ -34,16 +40,26 @@ namespace MinecraftClient
public static string ProxyPassword = "";
//Other Settings
public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\9e\9e2fdc43fc1c7024ff5922b998fadb2971a64ee0"; //MC 1.7.4 en_GB.lang
public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.7.4.json";
public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\3d\3d7f778ea0a3baaf826ae75a094d77c46410902f"; //MC 1.9 en_GB.lang
public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.9.json";
public static string TranslationsFile_Website_Download = "http://resources.download.minecraft.net";
public static TimeSpan splitMessageDelay = TimeSpan.FromSeconds(2);
public static List<string> Bots_Owners = new List<string>();
public static TimeSpan botMessageDelay = TimeSpan.FromSeconds(2);
public static string Language = "en_GB";
public static bool chatTimeStamps = false;
public static bool exitOnFailure = false;
public static bool interactiveMode = true;
public static char internalCmdChar = '/';
public static bool playerHeadAsIcon = false;
public static string chatbotLogFile = "";
public static bool CacheScripts = true;
public static string BrandInfo = MCCBrandInfo;
public static bool DisplaySystemMessages = true;
public static bool DisplayXPBarMessages = true;
public static bool TerrainAndMovements = false;
public static string PrivateMsgsCmdName = "tell";
public static CacheType SessionCaching = CacheType.None;
public static bool DebugMessages = false;
//AntiAFK Settings
public static bool AntiAFK_Enabled = false;
@ -88,12 +104,22 @@ namespace MinecraftClient
public static bool RemoteCtrl_AutoTpaccept = true;
public static bool RemoteCtrl_AutoTpaccept_Everyone = false;
//Custom app variables and Minecraft accounts
private static Dictionary<string, string> AppVars = new Dictionary<string, string>();
private static Dictionary<string, KeyValuePair<string, string>> Accounts = new Dictionary<string, KeyValuePair<string, string>>();
private static Dictionary<string, KeyValuePair<string, ushort>> Servers = new Dictionary<string, KeyValuePair<string, ushort>>();
//Chat Message Parsing
public static bool ChatFormat_Builtins = true;
public static Regex ChatFormat_Public = null;
public static Regex ChatFormat_Private = null;
public static Regex ChatFormat_TeleportRequest = null;
private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl };
//Auto Respond
public static bool AutoRespond_Enabled = false;
public static string AutoRespond_Matches = "matches.ini";
//Custom app variables and Minecraft accounts
private static readonly Dictionary<string, object> AppVars = new Dictionary<string, object>();
private static readonly Dictionary<string, KeyValuePair<string, string>> Accounts = new Dictionary<string, KeyValuePair<string, string>>();
private static readonly Dictionary<string, KeyValuePair<string, ushort>> Servers = new Dictionary<string, KeyValuePair<string, ushort>>();
private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, ChatFormat, AutoRespond };
/// <summary>
/// Load settings from the give INI file
@ -127,6 +153,8 @@ namespace MinecraftClient
case "remotecontrol": pMode = ParseMode.RemoteControl; break;
case "proxy": pMode = ParseMode.Proxy; break;
case "appvars": pMode = ParseMode.AppVars; break;
case "autorespond": pMode = ParseMode.AutoRespond; break;
case "chatformat": pMode = ParseMode.ChatFormat; break;
default: pMode = ParseMode.Default; break;
}
}
@ -143,15 +171,23 @@ namespace MinecraftClient
{
case "login": Login = argValue; break;
case "password": Password = argValue; break;
case "serverip": setServerIP(argValue); break;
case "serverip": SetServerIP(argValue); break;
case "singlecommand": SingleCommand = argValue; break;
case "language": Language = argValue; break;
case "consoletitle": ConsoleTitle = argValue; break;
case "timestamps": chatTimeStamps = str2bool(argValue); break;
case "exitonfailure": exitOnFailure = str2bool(argValue); break;
case "exitonfailure": interactiveMode = !str2bool(argValue); break;
case "playerheadicon": playerHeadAsIcon = str2bool(argValue); break;
case "chatbotlogfile": chatbotLogFile = argValue; break;
case "mcversion": ServerVersion = argValue; break;
case "splitmessagedelay": splitMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break;
case "scriptcache": CacheScripts = str2bool(argValue); break;
case "showsystemmessages": DisplaySystemMessages = str2bool(argValue); break;
case "showxpbarmessages": DisplayXPBarMessages = str2bool(argValue); break;
case "terrainandmovements": TerrainAndMovements = str2bool(argValue); break;
case "privatemsgscmdname": PrivateMsgsCmdName = argValue.ToLower().Trim(); break;
case "botmessagedelay": botMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break;
case "debugmessages": DebugMessages = str2bool(argValue); break;
case "botowners":
Bots_Owners.Clear();
@ -168,6 +204,12 @@ namespace MinecraftClient
}
break;
case "sessioncache":
if (argValue == "none") { SessionCaching = CacheType.None; }
else if (argValue == "memory") { SessionCaching = CacheType.Memory; }
else if (argValue == "disk") { SessionCaching = CacheType.Disk; }
break;
case "accountlist":
if (File.Exists(argValue))
{
@ -197,16 +239,25 @@ namespace MinecraftClient
if (server_data.Length == 2
&& server_data[0] != "localhost"
&& !server_data[0].Contains('.')
&& setServerIP(server_data[1]))
&& SetServerIP(server_data[1]))
Servers[server_data[0]]
= new KeyValuePair<string, ushort>(ServerIP, ServerPort);
}
//Restore current server info
ServerIP = server_host_temp;
ServerPort = server_port_temp;
}
break;
case "brandinfo":
switch (argValue.Trim().ToLower())
{
case "mcc": BrandInfo = MCCBrandInfo; break;
case "vanilla": BrandInfo = "vanilla"; break;
default: BrandInfo = null; break;
}
break;
}
break;
@ -276,15 +327,29 @@ namespace MinecraftClient
}
break;
case ParseMode.ChatFormat:
switch (argName.ToLower())
{
case "builtins": ChatFormat_Builtins = str2bool(argValue); break;
case "public": ChatFormat_Public = new Regex(argValue); break;
case "private": ChatFormat_Private = new Regex(argValue); break;
case "tprequest": ChatFormat_TeleportRequest = new Regex(argValue); break;
}
break;
case ParseMode.Proxy:
switch (argName.ToLower())
{
case "enabled": ProxyEnabled = str2bool(argValue); break;
case "enabled":
ProxyEnabledLogin = ProxyEnabledIngame = str2bool(argValue);
if (argValue.Trim().ToLower() == "login")
ProxyEnabledLogin = true;
break;
case "type":
argValue = argValue.ToLower();
if (argValue == "http") { proxyType = Proxy.ProxyHandler.Type.HTTP; }
else if (argValue == "socks4") { proxyType = Proxy.ProxyHandler.Type.SOCKS4; }
else if (argValue == "socks4a"){ proxyType = Proxy.ProxyHandler.Type.SOCKS4a;}
else if (argValue == "socks4a") { proxyType = Proxy.ProxyHandler.Type.SOCKS4a; }
else if (argValue == "socks5") { proxyType = Proxy.ProxyHandler.Type.SOCKS5; }
break;
case "server":
@ -306,7 +371,15 @@ namespace MinecraftClient
break;
case ParseMode.AppVars:
setVar(argName, argValue);
SetVar(argName, argValue);
break;
case ParseMode.AutoRespond:
switch (argName.ToLower())
{
case "enabled": AutoRespond_Enabled = str2bool(argValue); break;
case "matchesfile": AutoRespond_Matches = argValue; break;
}
break;
}
}
@ -342,14 +415,24 @@ namespace MinecraftClient
+ "\r\n"
+ "language=en_GB\r\n"
+ "botowners=Player1,Player2,Player3\r\n"
+ "consoletitle=%username% - Minecraft Console Client\r\n"
+ "consoletitle=%username%@%serverip% - Minecraft Console Client\r\n"
+ "internalcmdchar=slash #use 'none', 'slash' or 'backslash'\r\n"
+ "splitmessagedelay=2 #seconds between each part of a long message\r\n"
+ "botmessagedelay=2 #seconds to delay between message a bot makes to avoid accidental spam\r\n"
+ "mcversion=auto #use 'auto' or '1.X.X' values\r\n"
+ "brandinfo=mcc #use 'mcc','vanilla', or 'none'\r\n"
+ "chatbotlogfile= #leave empty for no logfile\r\n"
+ "privatemsgscmdname=tell #used by RemoteControl bot\r\n"
+ "showsystemmessages=true #system messages for server ops\r\n"
+ "showxpbarmessages=true #messages displayed above xp bar\r\n"
+ "terrainandmovements=false #uses more ram, cpu, bandwidth\r\n"
+ "sessioncache=memory #use 'none', 'memory' or 'disk'\r\n"
+ "accountlist=accounts.txt\r\n"
+ "serverlist=servers.txt\r\n"
+ "playerheadicon=true\r\n"
+ "exitonfailure=false\r\n"
+ "debugmessages=false\r\n"
+ "scriptcache=true\r\n"
+ "timestamps=false\r\n"
+ "\r\n"
+ "[AppVars]\r\n"
@ -358,12 +441,18 @@ namespace MinecraftClient
+ "#%username% and %serverip% are reserved variables.\r\n"
+ "\r\n"
+ "[Proxy]\r\n"
+ "enabled=false\r\n"
+ "enabled=false #use 'false', 'true', or 'login' for login only\r\n"
+ "type=HTTP #Supported types: HTTP, SOCKS4, SOCKS4a, SOCKS5\r\n"
+ "server=0.0.0.0:0000\r\n"
+ "username=\r\n"
+ "password=\r\n"
+ "\r\n"
+ "[ChatFormat]\r\n"
+ "builtins=true #support for handling vanilla and common message formats\r\n"
+ "#public=^<([a-zA-Z0-9_]+)> (.+)$ #uncomment and adapt if necessary\r\n"
+ "#private=^([a-zA-Z0-9_]+) whispers to you: (.+)$ #vanilla example\r\n"
+ "#tprequest=^([a-zA-Z0-9_]+) has requested (?:to|that you) teleport to (?:you|them)\\.$\r\n"
+ "\r\n"
+ "#Bot Settings\r\n"
+ "\r\n"
+ "[Alerts]\r\n"
@ -402,18 +491,48 @@ namespace MinecraftClient
+ "[RemoteControl]\r\n"
+ "enabled=false\r\n"
+ "autotpaccept=true\r\n"
+ "tpaccepteveryone=false\r\n", Encoding.UTF8);
+ "tpaccepteveryone=false\r\n"
+ "\r\n"
+ "[AutoRespond]\r\n"
+ "enabled=false\r\n"
+ "matchesfile=matches.ini\r\n", Encoding.UTF8);
}
public static int str2int(string str) { try { return Convert.ToInt32(str); } catch { return 0; } }
public static bool str2bool(string str) { return str == "true" || str == "1"; }
/// <summary>
/// Convert the specified string to an integer, defaulting to zero if invalid argument
/// </summary>
/// <param name="str">String to parse as an integer</param>
/// <returns>Integer value</returns>
public static int str2int(string str)
{
try
{
return Convert.ToInt32(str);
}
catch { return 0; }
}
/// <summary>
/// Convert the specified string to a boolean value, defaulting to false if invalid argument
/// </summary>
/// <param name="str">String to parse as a boolean</param>
/// <returns>Boolean value</returns>
public static bool str2bool(string str)
{
if (String.IsNullOrEmpty(str))
return false;
str = str.Trim().ToLowerInvariant();
return str == "true" || str == "1";
}
/// <summary>
/// Load login/password using an account alias
/// </summary>
/// <returns>True if the account was found and loaded</returns>
public static bool setAccount(string accountAlias)
public static bool SetAccount(string accountAlias)
{
accountAlias = accountAlias.ToLower();
if (Accounts.ContainsKey(accountAlias))
@ -430,13 +549,13 @@ namespace MinecraftClient
/// </summary>
/// <returns>True if the server IP was valid and loaded, false otherwise</returns>
public static bool setServerIP(string server)
public static bool SetServerIP(string server)
{
server = server.ToLower();
string[] sip = server.Split(':');
string host = sip[0];
ushort port = 25565;
if (sip.Length > 1)
{
try
@ -448,17 +567,19 @@ namespace MinecraftClient
if (host == "localhost" || host.Contains('.'))
{
//Server IP (IP or domain names contains at least a dot)
ServerIP = host;
ServerPort = port;
return true;
}
else if (Servers.ContainsKey(server))
{
//Server Alias (if no dot then treat the server as an alias)
ServerIP = Servers[server].Key;
ServerPort = Servers[server].Value;
return true;
}
return false;
}
@ -469,15 +590,31 @@ namespace MinecraftClient
/// <param name="varData">Value of the variable</param>
/// <returns>True if the parameters were valid</returns>
public static bool setVar(string varName, string varData)
public static bool SetVar(string varName, object varData)
{
varName = new string(varName.TakeWhile(char.IsLetterOrDigit).ToArray()).ToLower();
if (varName.Length > 0)
lock (AppVars)
{
AppVars[varName] = varData;
return true;
varName = new string(varName.TakeWhile(char.IsLetterOrDigit).ToArray()).ToLower();
if (varName.Length > 0)
{
AppVars[varName] = varData;
return true;
}
else return false;
}
else return false;
}
/// <summary>
/// Get a custom %variable% or null if the variable does not exist
/// </summary>
/// <param name="varName">Variable name</param>
/// <returns>The value or null if the variable does not exists</returns>
public static object GetVar(string varName)
{
if (AppVars.ContainsKey(varName))
return AppVars[varName];
return null;
}
/// <summary>
@ -486,7 +623,7 @@ namespace MinecraftClient
/// <param name="str">String to parse</param>
/// <returns>Modifier string</returns>
public static string expandVars(string str)
public static string ExpandVars(string str)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < str.Length; i++)
@ -521,7 +658,7 @@ namespace MinecraftClient
default:
if (AppVars.ContainsKey(varname_lower))
{
result.Append(AppVars[varname_lower]);
result.Append(AppVars[varname_lower].ToString());
}
else result.Append("%" + varname + '%');
break;

View file

@ -1,5 +1,5 @@
==================================================================
Minecraft Client v1.8.1 for Minecraft 1.4.6 to 1.8.0 - By ORelio
==================================================================
Minecraft Client v1.8.2 for Minecraft 1.4.6 to 1.8.3 - By ORelio
==================================================================
Thanks for dowloading Minecraft Console Client!
@ -15,6 +15,7 @@ in a fast and easy way without having to open the main Minecraft game.
First, extract the archive if not already extracted.
On Windows, simply open MinecraftClient.exe by double-clicking on it.
On Mac or Linux, open a terminal in this folder and run "mono MinecraftClient.exe".
If you cannot authenticate on Mono, you'll need to run "mozroots --import --ask-remove" once.
===========================================
Using Configuration files & Enabling bots
@ -118,6 +119,15 @@ These files describe how some messages should be printed depending on your prefe
The client will automatically load en_GB.lang from your Minecraft folder if Minecraft is installed on your
computer, or download it from Mojang's servers. You may choose another language in the config file.
=========================
Detecting chat messages
=========================
Minecraft Console Client can parse messages from the server in order to detect private and public messages.
This is useful for reacting to messages eg when using the AutoRespond, Hangman game, or RemoteControl bots.
However, for unusual chat formats, so you may need to tinker with the ChatFormat section of the config file.
Building regular expressions can be a bit tricky, so you might want to try them out eg on regex101.com
======================
Using the Alerts bot
======================
@ -165,6 +175,14 @@ You can remotely send chat messages or commands using /tell <yourbot> send <thet
Remote control system can auto-accept /tpa and /tpahere requests from the bot owners.
Auto-accept can be disabled or extended to requests from anyone in remote control configuration.
===============================
Using the AutoRespond feature
===============================
The AutoRespond bot allows you to automatically react on specific chat messages or server announcements.
You can use either a string to detect in chat messages, or an advanced regular expression.
For more information about how to define match rules, please refer to sample-matches.ini
=========================
Disclaimer & Last words
=========================

View file

@ -0,0 +1,44 @@
# Minecraft Console Client
# AutoRespond matches
# Example config file
# Structure of a match: [Match] Followed by the match and action
# The match can be a simple match or an advanced regular expression
# You can use $u for username of the player triggering the match
# You can define an action if the match was in a private message
# You can define an action if the match was not sent by a player
# Regex matches are also supported eg $1, $2, $3.. in actions
# Simple example: Respond to a message containing a keyword
[Match]
match=hi
action=send hi, $u!
actionprivate=send /tell $u Hello!
actionother=log detected "hi" message
# You do not need to specify all the "action" fields
# Only one of them is required for each match
# Advanced example: Use a regular expression
# Here a "regex" field is used instead of "match" field
# Do not use both "regex" and "match" fields...
[Match]
regex=^.*hello ([a-zA-Z0-9_]+).*$
action=send hello too, $1!
# Example of using a script
[Match]
match=dotest
action=script test
# Example of matching a server announcement
[Match]
match=server is restarting
actionother=script restart
# Enjoy!
# - ORelio

View file

@ -0,0 +1,31 @@
//MCCScript 1.0
/* This script demonstrates how to use methods and arguments */
string text = "hello";
if (args.Length > 0)
text = args[0];
for (int i = 0; i < 5; i++)
{
int count = MCC.GetVarAsInt("test") + 1;
MCC.SetVar("test", count);
SendHelloWorld(count, text);
SleepBetweenSends();
}
//MCCScript Extensions
/* Here you can define methods for use into your script */
void SendHelloWorld(int count, string text)
{
MCC.SendText("Hello World no. " + count + ": " + text);
}
void SleepBetweenSends()
{
MCC.LogToConsole("Sleeping for 5 seconds...");
Thread.Sleep(5000);
}

View file

@ -0,0 +1,35 @@
//MCCScript 1.0
/* This is a sample script that will load a ChatBot into Minecraft Console Client
* Simply execute the script once with /script or the script scheduler to load the bot */
MCC.LoadBot(new ExampleBot());
//MCCScript Extensions
/* The ChatBot class must be defined as an extension of the script in the Extensions section
* The class can override common methods from ChatBot.cs, take a look at MCC's source code */
public class ExampleBot : ChatBot
{
public override void Initialize()
{
LogToConsole("Sucessfully Initialized!");
}
public override void GetText(string text)
{
string message = "";
string username = "";
text = GetVerbatim(text);
if (IsChatMessage(text, ref message, ref username))
{
LogToConsole("Public message from " + username + ": " + message);
}
else if (IsPrivateMessage(text, ref message, ref username))
{
LogToConsole("Private message from " + username + ": " + message);
}
}
}

View file

@ -0,0 +1,61 @@
//MCCScript 1.0
MCC.LoadBot(new WatchLamp());
//MCCScript Extensions
/* The ChatBot will access the world on a regular basis to watch for a lamp.
* This is an example of how the world around the player can be accessed from a C# script. */
class WatchLamp : ChatBot
{
/* == CONFIG == */
int lampX = 0;
int lampY = 64;
int lampZ = 0;
/* == CODE == */
int checkCount = 0;
Location lampLoc;
public WatchLamp()
{
if (!Settings.TerrainAndMovements)
{
LogToConsole("WARNING: Terrain handling is disabled in INI file.");
LogToConsole("WARNING: This means this bot cannot watch for lamps.");
UnloadBot();
}
else
{
lampLoc = new Location(lampX, lampY, lampZ);
LogToConsole("Watching lamp at " + lampLoc);
}
}
public override void Update()
{
if (checkCount > 10)
{
checkCount = 0;
Material blockType = GetWorld().GetBlock(lampLoc).Type;
switch (blockType)
{
case Material.RedstoneLampOn:
//Lamp is on. All right. Nothing to say here.
break;
case Material.RedstoneLampOff:
LogToConsole("Lamp at " + lampLoc + " is currently turned OFF !!!");
for (int i = 0; i < 3; i++)
Console.Beep();
break;
default:
LogToConsole("Block at " + lampLoc + " is not a lamp: " + blockType + "...");
break;
}
}
else checkCount++;
}
}

View file

@ -0,0 +1,14 @@
//MCCScript 1.0
/* This is a sample script for Minecraft Console Client
* The code provided in this file will be compiled at runtime and executed
* Allowed instructions: Any C# code AND methods provided by the MCC API */
for (int i = 0; i < 5; i++)
{
int count = MCC.GetVarAsInt("test") + 1;
MCC.SetVar("test", count);
MCC.SendText("Hello World no. " + count);
MCC.LogToConsole("Sleeping for 5 seconds...");
Thread.Sleep(5000);
}