@@ -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;
+ }
+
+ ///
+ /// Write a Minecraft Console Client Log line
+ ///
+ /// Text of the log line
+
+ 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
+ ///
+ /// Set an auto-completion engine for TAB autocompletion
+ ///
+ /// Engine implementing the IAutoComplete interface
+
+ public static void SetAutoCompleteEngine(IAutoComplete engine)
+ {
+ autocomplete_engine = engine;
+ }
+ #endregion
}
///
diff --git a/MinecraftClient/ConsoleIcon.cs b/MinecraftClient/ConsoleIcon.cs
index 2ef20783..30dc6460 100644
--- a/MinecraftClient/ConsoleIcon.cs
+++ b/MinecraftClient/ConsoleIcon.cs
@@ -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
diff --git a/MinecraftClient/Crypto/CryptoHandler.cs b/MinecraftClient/Crypto/CryptoHandler.cs
index 7d31e162..ae46ccf5 100644
--- a/MinecraftClient/Crypto/CryptoHandler.cs
+++ b/MinecraftClient/Crypto/CryptoHandler.cs
@@ -197,14 +197,13 @@ namespace MinecraftClient.Crypto
///
/// Stream to encrypt
/// Key to use
- /// Padding provider for Mono implementation
/// Return an appropriate stream depending on the framework being used
- 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);
}
diff --git a/MinecraftClient/Crypto/IPaddingProvider.cs b/MinecraftClient/Crypto/IPaddingProvider.cs
deleted file mode 100644
index 48290494..00000000
--- a/MinecraftClient/Crypto/IPaddingProvider.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace MinecraftClient.Crypto
-{
- ///
- /// Interface for padding provider
- /// Allow to get a padding plugin message from the current network protocol implementation.
- ///
-
- public interface IPaddingProvider
- {
- byte[] getPaddingPacket();
- }
-}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs b/MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs
new file mode 100644
index 00000000..a1b54456
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs
@@ -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.
+ *
+ * For further details see: http://csrc.nist.gov/encryption/aes/.
+ *
+ * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
+ * http://fp.gladman.plus.com/cryptography_technology/rijndael/
+ *
+ * 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
+ *
+ *
+ * This file contains the fast version with 8Kbytes of static tables for round precomputation
+ *
+ */
+ 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];
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/BufferedBlockCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/BufferedBlockCipher.cs
new file mode 100644
index 00000000..3dc21560
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/BufferedBlockCipher.cs
@@ -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.
+ *
+ * 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.
+ *
+ */
+ 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();
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs b/MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs
new file mode 100644
index 00000000..9d861021
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs
@@ -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();
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs
new file mode 100644
index 00000000..43371653
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs
@@ -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();
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/Check.cs b/MinecraftClient/Crypto/Streams/BouncyAes/Check.cs
new file mode 100644
index 00000000..96a05c64
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/Check.cs
@@ -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);
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs b/MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs
new file mode 100644
index 00000000..b6920854
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs
@@ -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();
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs b/MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs
new file mode 100644
index 00000000..67f0d86f
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs b/MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs
new file mode 100644
index 00000000..e9efc0bd
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs
new file mode 100644
index 00000000..a3ad6d6e
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto
+{
+ /// Base interface for a symmetric key block cipher.
+ public interface IBlockCipher
+ {
+ /// The name of the algorithm this cipher implements.
+ string AlgorithmName { get; }
+
+ /// Initialise the cipher.
+ /// Initialise for encryption if true, for decryption if false.
+ /// The key or other data required by the cipher.
+ void Init(bool forEncryption, ICipherParameters parameters);
+
+ /// The block size for this cipher, in bytes.
+ int GetBlockSize();
+
+ /// Indicates whether this cipher can handle partial blocks.
+ bool IsPartialBlockOkay { get; }
+
+ /// Process a block.
+ /// The input buffer.
+ /// The offset into inBuf that the input block begins.
+ /// The output buffer.
+ /// The offset into outBuf to write the output block.
+ /// If input block is wrong size, or outBuf too small.
+ /// The number of bytes processed and produced.
+ int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff);
+
+ ///
+ /// Reset the cipher to the same state as it was after the last init (if there was one).
+ ///
+ void Reset();
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs
new file mode 100644
index 00000000..69dec959
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto
+{
+ /// Block cipher engines are expected to conform to this interface.
+ public interface IBufferedCipher
+ {
+ /// The name of the algorithm this cipher implements.
+ string AlgorithmName { get; }
+
+ /// Initialise the cipher.
+ /// If true the cipher is initialised for encryption,
+ /// if false for decryption.
+ /// The key and other data required by the cipher.
+ 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);
+
+ ///
+ /// Reset the cipher. After resetting the cipher is in the same state
+ /// as it was after the last init (if there was one).
+ ///
+ void Reset();
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs b/MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs
new file mode 100644
index 00000000..fff0941c
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto
+{
+ /**
+ * all parameter classes implement this.
+ */
+ public interface ICipherParameters
+ {
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs b/MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs
new file mode 100644
index 00000000..33dff96d
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs
@@ -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();
+ }
+ }
+
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs b/MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs
new file mode 100644
index 00000000..e1cf4492
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs b/MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs
new file mode 100644
index 00000000..087cb7ce
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs
@@ -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;
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs b/MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs
new file mode 100644
index 00000000..11a8b77a
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs
@@ -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; }
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/MonoAesStream.cs b/MinecraftClient/Crypto/Streams/MonoAesStream.cs
index b7d2875f..57d5598e 100644
--- a/MinecraftClient/Crypto/Streams/MonoAesStream.cs
+++ b/MinecraftClient/Crypto/Streams/MonoAesStream.cs
@@ -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
{
///
/// 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.
///
public class MonoAesStream : Stream, IAesStream
{
- IPaddingProvider pad;
- ICryptoTransform enc;
- ICryptoTransform dec;
- List dec_cache = new List();
- List tosend_cache = new List();
- 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;
}
}
diff --git a/MinecraftClient/Mapping/Block.cs b/MinecraftClient/Mapping/Block.cs
new file mode 100644
index 00000000..e7aa0d95
--- /dev/null
+++ b/MinecraftClient/Mapping/Block.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represents a Minecraft Block
+ ///
+ public struct Block
+ {
+ ///
+ /// Storage for block ID and metadata
+ ///
+ private ushort blockIdAndMeta;
+
+ ///
+ /// Id of the block
+ ///
+ public short BlockId
+ {
+ get
+ {
+ return (short)(blockIdAndMeta >> 4);
+ }
+ set
+ {
+ blockIdAndMeta = (ushort)(value << 4 | BlockMeta);
+ }
+ }
+
+ ///
+ /// Metadata of the block
+ ///
+ public byte BlockMeta
+ {
+ get
+ {
+ return (byte)(blockIdAndMeta & 0x0F);
+ }
+ set
+ {
+ blockIdAndMeta = (ushort)((blockIdAndMeta & ~0x0F) | (value & 0x0F));
+ }
+ }
+
+ ///
+ /// Material of the block
+ ///
+ public Material Type
+ {
+ get
+ {
+ return (Material)BlockId;
+ }
+ }
+
+ ///
+ /// Get a block of the specified type and metadata
+ ///
+ /// Block type
+ /// Block metadata
+ public Block(short type, byte metadata = 0)
+ {
+ this.blockIdAndMeta = 0;
+ this.BlockId = type;
+ this.BlockMeta = metadata;
+ }
+
+ ///
+ /// Get a block of the specified type and metadata
+ ///
+ /// Type and metadata packed in the same value
+ public Block(ushort typeAndMeta)
+ {
+ this.blockIdAndMeta = typeAndMeta;
+ }
+
+ ///
+ /// Get a block of the specified type and metadata
+ ///
+ /// Block type
+ public Block(Material type, byte metadata = 0)
+ : this((short)type, metadata) { }
+
+ ///
+ /// String representation of the block
+ ///
+ public override string ToString()
+ {
+ return BlockId.ToString() + (BlockMeta != 0 ? ":" + BlockMeta.ToString() : "");
+ }
+ }
+}
diff --git a/MinecraftClient/Mapping/Chunk.cs b/MinecraftClient/Mapping/Chunk.cs
new file mode 100644
index 00000000..5b84ecf2
--- /dev/null
+++ b/MinecraftClient/Mapping/Chunk.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represent a chunk of terrain in a Minecraft world
+ ///
+ public class Chunk
+ {
+ public const int SizeX = 16;
+ public const int SizeY = 16;
+ public const int SizeZ = 16;
+
+ ///
+ /// Blocks contained into the chunk
+ ///
+ private readonly Block[,,] blocks = new Block[SizeX, SizeY, SizeZ];
+
+ ///
+ /// Read, or set the specified block
+ ///
+ /// Block X
+ /// Block Y
+ /// Block Z
+ /// chunk at the given location
+ 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;
+ }
+ }
+
+ ///
+ /// Get block at the specified location
+ ///
+ /// Location, a modulo will be applied
+ /// The block
+ public Block GetBlock(Location location)
+ {
+ return this[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ];
+ }
+ }
+}
diff --git a/MinecraftClient/Mapping/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs
new file mode 100644
index 00000000..26cbab39
--- /dev/null
+++ b/MinecraftClient/Mapping/ChunkColumn.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represent a column of chunks of terrain in a Minecraft world
+ ///
+ public class ChunkColumn
+ {
+ public const int ColumnSize = 16;
+
+ ///
+ /// Blocks contained into the chunk
+ ///
+ private readonly Chunk[] chunks = new Chunk[ColumnSize];
+
+ ///
+ /// Get or set the specified chunk column
+ ///
+ /// ChunkColumn X
+ /// ChunkColumn Y
+ /// chunk at the given location
+ public Chunk this[int chunkY]
+ {
+ get
+ {
+ return chunks[chunkY];
+ }
+ set
+ {
+ chunks[chunkY] = value;
+ }
+ }
+
+ ///
+ /// Get chunk at the specified location
+ ///
+ /// Location, a modulo will be applied
+ /// The chunk, or null if not loaded
+ public Chunk GetChunk(Location location)
+ {
+ return this[location.ChunkY];
+ }
+ }
+}
diff --git a/MinecraftClient/Mapping/Direction.cs b/MinecraftClient/Mapping/Direction.cs
new file mode 100644
index 00000000..5092e7ff
--- /dev/null
+++ b/MinecraftClient/Mapping/Direction.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represents a unit movement in the world
+ ///
+ ///
+ public enum Direction
+ {
+ South = 0,
+ West = 1,
+ North = 2,
+ East = 3,
+ Up = 4,
+ Down = 5
+ }
+}
diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs
new file mode 100644
index 00000000..ed683134
--- /dev/null
+++ b/MinecraftClient/Mapping/Location.cs
@@ -0,0 +1,331 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represents a location into a Minecraft world
+ ///
+ public struct Location
+ {
+ ///
+ /// The X Coordinate
+ ///
+ public double X;
+
+ ///
+ /// The Y Coordinate (vertical)
+ ///
+ public double Y;
+
+ ///
+ /// The Z coordinate
+ ///
+ public double Z;
+
+ ///
+ /// Get location with zeroed coordinates
+ ///
+ public static Location Zero
+ {
+ get
+ {
+ return new Location(0, 0, 0);
+ }
+ }
+
+ ///
+ /// Create a new location
+ ///
+ public Location(double x, double y, double z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ ///
+ /// Create a new location
+ ///
+ /// Location of the chunk into the world
+ /// Location of the chunk into the world
+ /// Location of the block into the chunk
+ /// Location of the block into the world
+ /// Location of the block into the chunk
+ public Location(int chunkX, int chunkZ, int blockX, int blockY, int blockZ)
+ {
+ X = chunkX * Chunk.SizeX + blockX;
+ Y = blockY;
+ Z = chunkZ * Chunk.SizeZ + blockZ;
+ }
+
+ ///
+ /// The X index of the corresponding chunk in the world
+ ///
+ public int ChunkX
+ {
+ get
+ {
+ return (int)Math.Floor(X / Chunk.SizeX);
+ }
+ }
+
+ ///
+ /// The Y index of the corresponding chunk in the world
+ ///
+ public int ChunkY
+ {
+ get
+ {
+ return (int)Math.Floor(Y / Chunk.SizeY);
+ }
+ }
+
+ ///
+ /// The Z index of the corresponding chunk in the world
+ ///
+ public int ChunkZ
+ {
+ get
+ {
+ return (int)Math.Floor(Z / Chunk.SizeZ);
+ }
+ }
+
+ ///
+ /// The X index of the corresponding block in the corresponding chunk of the world
+ ///
+ public int ChunkBlockX
+ {
+ get
+ {
+ return ((int)Math.Floor(X) % Chunk.SizeX + Chunk.SizeX) % Chunk.SizeX;
+ }
+ }
+
+ ///
+ /// The Y index of the corresponding block in the corresponding chunk of the world
+ ///
+ public int ChunkBlockY
+ {
+ get
+ {
+ return ((int)Math.Floor(Y) % Chunk.SizeY + Chunk.SizeY) % Chunk.SizeY;
+ }
+ }
+
+ ///
+ /// The Z index of the corresponding block in the corresponding chunk of the world
+ ///
+ public int ChunkBlockZ
+ {
+ get
+ {
+ return ((int)Math.Floor(Z) % Chunk.SizeZ + Chunk.SizeZ) % Chunk.SizeZ;
+ }
+ }
+
+ ///
+ /// Get a squared distance to the specified location
+ ///
+ /// Other location for computing distance
+ /// Distance to the specified location, without using a square root
+ public double DistanceSquared(Location location)
+ {
+ return ((X - location.X) * (X - location.X))
+ + ((Y - location.Y) * (Y - location.Y))
+ + ((Z - location.Z) * (Z - location.Z));
+ }
+
+ ///
+ /// Get exact distance to the specified location
+ ///
+ /// Other location for computing distance
+ /// Distance to the specified location, with square root so lower performances
+ public double Distance(Location location)
+ {
+ return Math.Sqrt(DistanceSquared(location));
+ }
+
+ ///
+ /// Compare two locations. Locations are equals if the integer part of their coordinates are equals.
+ ///
+ /// Object to compare to
+ /// TRUE if the locations are equals
+ 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;
+ }
+
+ ///
+ /// Get a representation of the location as unsigned long
+ ///
+ ///
+ /// 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
+ ///
+ /// Location representation as ulong
+
+ public ulong GetLong()
+ {
+ return ((((ulong)X) & 0x3FFFFFF) << 38) | ((((ulong)Y) & 0xFFF) << 26) | (((ulong)Z) & 0x3FFFFFF);
+ }
+
+ ///
+ /// Get a location from an unsigned long.
+ ///
+ /// Location represented by the ulong
+
+ 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);
+ }
+
+ ///
+ /// Compare two locations. Locations are equals if the integer part of their coordinates are equals.
+ ///
+ /// First location to compare
+ /// Second location to compare
+ /// TRUE if the locations are equals
+ 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);
+ }
+
+ ///
+ /// Compare two locations. Locations are not equals if the integer part of their coordinates are not equals.
+ ///
+ /// First location to compare
+ /// Second location to compare
+ /// TRUE if the locations are equals
+ 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);
+ }
+
+ ///
+ /// Sums two locations and returns the result.
+ ///
+ ///
+ /// Thrown if one of the provided location is null
+ ///
+ /// First location to sum
+ /// Second location to sum
+ /// Sum of the two locations
+ public static Location operator +(Location loc1, Location loc2)
+ {
+ return new Location
+ (
+ loc1.X + loc2.X,
+ loc1.Y + loc2.Y,
+ loc1.Z + loc2.Z
+ );
+ }
+
+ ///
+ /// Substract a location to another
+ ///
+ ///
+ /// Thrown if one of the provided location is null
+ ///
+ /// First location
+ /// Location to substract to the first one
+ /// Sum of the two locations
+ public static Location operator -(Location loc1, Location loc2)
+ {
+ return new Location
+ (
+ loc1.X - loc2.X,
+ loc1.Y - loc2.Y,
+ loc1.Z - loc2.Z
+ );
+ }
+
+ ///
+ /// Multiply a location by a scalar value
+ ///
+ /// Location to multiply
+ /// Scalar value
+ /// Product of the location and the scalar value
+ public static Location operator *(Location loc, double val)
+ {
+ return new Location
+ (
+ loc.X * val,
+ loc.Y * val,
+ loc.Z * val
+ );
+ }
+
+ ///
+ /// Divide a location by a scalar value
+ ///
+ /// Location to divide
+ /// Scalar value
+ /// Result of the division
+ public static Location operator /(Location loc, double val)
+ {
+ return new Location
+ (
+ loc.X / val,
+ loc.Y / val,
+ loc.Z / val
+ );
+ }
+
+ ///
+ /// DO NOT USE. Defined to comply with C# requirements requiring a GetHashCode() when overriding Equals() or ==
+ ///
+ ///
+ /// 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
+ ///
+ /// A simplified version of the location
+ public override int GetHashCode()
+ {
+ return (((int)X) & ~((~0) << 13)) << 19
+ | (((int)Y) & ~((~0) << 13)) << 13
+ | (((int)Z) & ~((~0) << 06)) << 00;
+ }
+
+ ///
+ /// Convert the location into a string representation
+ ///
+ /// String representation of the location
+ public override string ToString()
+ {
+ return String.Format("X:{0} Y:{1} Z:{2}", X, Y, Z);
+ }
+ }
+}
diff --git a/MinecraftClient/Mapping/Material.cs b/MinecraftClient/Mapping/Material.cs
new file mode 100644
index 00000000..005b0b5e
--- /dev/null
+++ b/MinecraftClient/Mapping/Material.cs
@@ -0,0 +1,365 @@
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represents Minecraft Materials
+ ///
+ ///
+ /// Mostly ported from CraftBukkit's Material class
+ ///
+ ///
+ 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
+ }
+
+ ///
+ /// Defines extension methods for the Material enumeration
+ ///
+ public static class MaterialExtensions
+ {
+ ///
+ /// Check if the player cannot pass through the specified material
+ ///
+ /// Material to test
+ /// True if the material is harmful
+ 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;
+ }
+ }
+
+ ///
+ /// Check if contact with the provided material can harm players
+ ///
+ /// Material to test
+ /// True if the material is harmful
+ 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;
+ }
+ }
+
+ ///
+ /// Check if the provided material is a liquid a player can swim into
+ ///
+ /// Material to test
+ /// True if the material is a liquid
+ 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;
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs
new file mode 100644
index 00000000..a12af234
--- /dev/null
+++ b/MinecraftClient/Mapping/Movement.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Allows moving through a Minecraft world
+ ///
+ public static class Movement
+ {
+ /* ========= PATHFINDING METHODS ========= */
+
+ ///
+ /// Handle movements due to gravity
+ ///
+ /// World the player is currently located in
+ /// Location the player is currently at
+ /// Updated location after applying gravity
+ 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;
+ }
+
+ ///
+ /// Return a list of possible moves for the player
+ ///
+ /// World the player is currently located in
+ /// Location the player is currently at
+ /// Allow possible but unsafe locations
+ /// A list of new locations the player can move to
+ public static IEnumerable GetAvailableMoves(World world, Location location, bool allowUnsafe = false)
+ {
+ List availableMoves = new List();
+ 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;
+ }
+
+ ///
+ /// Decompose a single move from a block to another into several steps
+ ///
+ ///
+ /// Allows moving by little steps instead or directly moving between blocks,
+ /// which would be rejected by anti-cheat plugins anyway.
+ ///
+ /// Start location
+ /// Destination location
+ /// Amount of steps by block
+ /// A list of locations corresponding to the requested steps
+ public static Queue 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 movementSteps = new Queue();
+ for (int i = 1; i <= totalSteps; i++)
+ movementSteps.Enqueue(start + step * i);
+ return movementSteps;
+ }
+ else return new Queue(new[] { goal });
+ }
+
+ ///
+ /// Calculate a path from the start location to the destination location
+ ///
+ ///
+ /// Based on the A* pathfinding algorithm described on Wikipedia
+ ///
+ ///
+ /// Start location
+ /// Destination location
+ /// Allow possible but unsafe locations
+ /// A list of locations, or null if calculation failed
+ public static Queue CalculatePath(World world, Location start, Location goal, bool allowUnsafe = false)
+ {
+ Queue result = null;
+
+ AutoTimeout.Perform(() =>
+ {
+ HashSet ClosedSet = new HashSet(); // The set of locations already evaluated.
+ HashSet OpenSet = new HashSet(new[] { start }); // The set of tentative nodes to be evaluated, initially containing the start node
+ Dictionary Came_From = new Dictionary(); // The map of navigated nodes.
+
+ Dictionary g_score = new Dictionary(); //:= 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 f_score = new Dictionary(); //:= 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, f_score[location])
+ : new KeyValuePair(location, int.MaxValue))
+ .OrderBy(pair => pair.Value).First().Key;
+ if (current == goal)
+ { //reconstruct_path(Came_From, goal)
+ List total_path = new List(new[] { current });
+ while (Came_From.ContainsKey(current))
+ {
+ current = Came_From[current];
+ total_path.Add(current);
+ }
+ total_path.Reverse();
+ result = new Queue(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 ========= */
+
+ ///
+ /// Check if the specified location is on the ground
+ ///
+ /// World for performing check
+ /// Location to check
+ /// True if the specified location is on the ground
+ public static bool IsOnGround(World world, Location location)
+ {
+ return world.GetBlock(Move(location, Direction.Down)).Type.IsSolid();
+ }
+
+ ///
+ /// Check if the specified location implies swimming
+ ///
+ /// World for performing check
+ /// Location to check
+ /// True if the specified location implies swimming
+ public static bool IsSwimming(World world, Location location)
+ {
+ return world.GetBlock(location).Type.IsLiquid();
+ }
+
+ ///
+ /// Check if the specified location is safe
+ ///
+ /// World for performing check
+ /// Location to check
+ /// True if the destination location won't directly harm the player
+ 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 ========= */
+
+ ///
+ /// Check if the player can move in the specified direction
+ ///
+ /// World the player is currently located in
+ /// Location the player is currently at
+ /// Direction the player is moving to
+ /// True if the player can move in the specified direction
+ 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");
+ }
+ }
+
+ ///
+ /// Get an updated location for moving in the specified direction
+ ///
+ /// Current location
+ /// Direction to move to
+ /// Distance, in blocks
+ /// Updated location
+ public static Location Move(Location location, Direction direction, int length = 1)
+ {
+ return location + Move(direction) * length;
+ }
+
+ ///
+ /// Get a location delta for moving in the specified direction
+ ///
+ /// Direction to move to
+ /// A location delta for moving in that direction
+ 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");
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs
new file mode 100644
index 00000000..25b79625
--- /dev/null
+++ b/MinecraftClient/Mapping/World.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Mapping
+{
+ ///
+ /// Represents a Minecraft World
+ ///
+ public class World
+ {
+ ///
+ /// The chunks contained into the Minecraft world
+ ///
+ private Dictionary> chunks = new Dictionary>();
+
+ ///
+ /// Read, set or unload the specified chunk column
+ ///
+ /// ChunkColumn X
+ /// ChunkColumn Y
+ /// chunk at the given location
+ 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();
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get chunk column at the specified location
+ ///
+ /// Location to retrieve chunk column
+ /// The chunk column
+ public ChunkColumn GetChunkColumn(Location location)
+ {
+ return this[location.ChunkX, location.ChunkZ];
+ }
+
+ ///
+ /// Get block at the specified location
+ ///
+ /// Location to retrieve block from
+ /// Block at specified location or Air if the location is not loaded
+ 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);
+ }
+
+ ///
+ /// Set block at the specified location
+ ///
+ /// Location to set block to
+ /// Block to set
+ 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;
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs
index 2ba5c7d5..f50baecd 100644
--- a/MinecraftClient/McTcpClient.cs
+++ b/MinecraftClient/McTcpClient.cs
@@ -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 cmd_names = new List();
- private static Dictionary cmds = new Dictionary();
- private List bots = new List();
- private Dictionary onlinePlayers = new Dictionary();
- private static List scripts_on_hold = new List();
- 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 cmd_names = new List();
+ private static readonly Dictionary cmds = new Dictionary();
+ private readonly Dictionary onlinePlayers = new Dictionary();
+
+ private readonly List bots = new List();
+ private static readonly List scripts_on_hold = new List();
+ 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> registeredBotPluginChannels = new Dictionary>();
+ private readonly List registeredServerPluginChannels = new List();
+
+ private object locationLock = new object();
+ private bool locationReceived = false;
+ private World world = new World();
+ private Queue steps;
+ private Queue 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
/// The server port to use
/// Minecraft protocol version to use
- 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, "");
}
///
@@ -70,9 +103,9 @@ namespace MinecraftClient
/// Minecraft protocol version to use
/// The text or command to send.
- 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);
}
///
@@ -87,8 +120,9 @@ namespace MinecraftClient
/// If set to true, the client will send a single command and then disconnect from the server
/// The text or command to send. Will only be sent if singlecommand is set to true.
- private void StartClient(string user, string 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) { }
}
///
@@ -214,7 +268,7 @@ namespace MinecraftClient
/// May contain a confirmation or error message after processing the command, or "" otherwise.
/// TRUE if the command was indeed an internal MCC command
- 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();
+ }
+
+ ///
+ /// Called when a server was successfully joined
+ ///
+
+ public void OnGameJoined()
+ {
+ if (!String.IsNullOrWhiteSpace(Settings.BrandInfo))
+ handler.SendBrandInfo(Settings.BrandInfo.Trim());
+ foreach (ChatBot bot in bots)
+ bot.AfterGameJoined();
+ }
+
+ ///
+ /// Called when the server sends a new player location,
+ /// or if a ChatBot whishes to update the player's location.
+ ///
+ /// The new location
+ /// If true, the location is relative to the current location
+
+ public void UpdateLocation(Location location, bool relative)
+ {
+ lock (locationLock)
+ {
+ if (relative)
+ {
+ this.location += location;
+ }
+ else this.location = location;
+ locationReceived = true;
+ }
+ }
+
+ ///
+ /// Called when the server sends a new player location,
+ /// or if a ChatBot whishes to update the player's location.
+ ///
+ /// The new location
+ /// If true, the location is relative to the current location
+
+ public void UpdateLocation(Location location)
+ {
+ UpdateLocation(location, false);
+ }
+
+ ///
+ /// Move to the specified location
+ ///
+ /// Location to reach
+ /// Allow possible but unsafe locations
+ /// True if a path has been found
+ public bool MoveTo(Location location, bool allowUnsafe = false)
+ {
+ lock (locationLock)
+ {
+ if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location))
+ path = new Queue(new[] { location });
+ else path = Movement.CalculatePath(world, this.location, location, allowUnsafe);
+ return path != null;
+ }
}
///
@@ -305,8 +421,21 @@ namespace MinecraftClient
public void OnTextReceived(string text)
{
ConsoleIO.WriteLineFormatted(text, false);
- foreach (ChatBot bot in new List(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
+ }
+ }
}
///
@@ -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();
}
///
@@ -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));
+ }
+ }
+ }
}
///
@@ -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;
+ }
}
-
+
///
/// Triggered when a player has left the game
///
@@ -422,7 +578,10 @@ namespace MinecraftClient
public void OnPlayerLeave(Guid uuid)
{
- onlinePlayers.Remove(uuid);
+ lock (onlinePlayers)
+ {
+ onlinePlayers.Remove(uuid);
+ }
}
///
@@ -430,9 +589,115 @@ namespace MinecraftClient
///
/// Online player names
- public string[] getOnlinePlayers()
+ public string[] GetOnlinePlayers()
{
- return onlinePlayers.Values.Distinct().ToArray();
+ lock (onlinePlayers)
+ {
+ return onlinePlayers.Values.Distinct().ToArray();
+ }
+ }
+
+ ///
+ /// Registers the given plugin channel for the given bot.
+ ///
+ /// The channel to register.
+ /// The bot to register the channel for.
+
+ public void RegisterPluginChannel(string channel, ChatBot bot)
+ {
+ if (registeredBotPluginChannels.ContainsKey(channel))
+ {
+ registeredBotPluginChannels[channel].Add(bot);
+ }
+ else
+ {
+ List bots = new List();
+ bots.Add(bot);
+ registeredBotPluginChannels[channel] = bots;
+ SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true);
+ }
+ }
+
+ ///
+ /// Unregisters the given plugin channel for the given bot.
+ ///
+ /// The channel to unregister.
+ /// The bot to unregister the channel for.
+
+ public void UnregisterPluginChannel(string channel, ChatBot bot)
+ {
+ if (registeredBotPluginChannels.ContainsKey(channel))
+ {
+ List registeredBots = registeredBotPluginChannels[channel];
+ registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot));
+ if (registeredBots.Count == 0)
+ {
+ registeredBotPluginChannels.Remove(channel);
+ SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true);
+ }
+ }
+ }
+
+ ///
+ /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information
+ /// about plugin channels.
+ ///
+ /// The channel to send the packet on.
+ /// The payload for the packet.
+ /// Whether the packet should be sent even if the server or the client hasn't registered it yet.
+ /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered.
+
+ 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);
+ }
+
+ ///
+ /// Called when a plugin channel message was sent from the server.
+ ///
+ /// The channel the message was sent on
+ /// The data from the channel
+
+ 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);
+ }
+ }
}
}
}
diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
index c174cb1e..35a9ff85 100644
--- a/MinecraftClient/MinecraftClient.csproj
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -63,6 +63,7 @@
+
@@ -74,6 +75,7 @@
+
@@ -85,6 +87,7 @@
+
@@ -94,9 +97,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -107,10 +136,10 @@
+
-
@@ -119,8 +148,10 @@
-
+
+
+
@@ -133,6 +164,7 @@
+
diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs
index c65a6ab3..0dfb168d 100644
--- a/MinecraftClient/Program.cs
+++ b/MinecraftClient/Program.cs
@@ -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
-{
- ///
- /// 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.
- ///
-
- static class Program
- {
- private static McTcpClient Client;
- public static string[] startupargs;
- public const string Version = "1.8.0";
- private static Thread offlinePrompt = null;
-
- ///
- /// The main entry point of Minecraft Console Client
- ///
-
- 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 args_tmp = args.ToList();
- 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();
- }
-
- ///
- /// Start a new Client
- ///
-
- 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(); }
- }
- }
-
- ///
- /// Disconnect the current client from the server and restart it
- ///
-
- 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();
- }
-
- ///
- /// Disconnect the current client from the server and exit the app
- ///
-
- 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();
- }
-
- ///
- /// Pause the program, usually when an error or a kick occured, letting the user press Enter to quit OR type /reconnect
- ///
-
- 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();
- }
- }
-
- ///
- /// Detect if the user is running Minecraft Console Client through Mono
- ///
-
- public static bool isUsingMono
- {
- get
- {
- return Type.GetType("Mono.Runtime") != null;
- }
- }
-
- ///
- /// Enumerate types in namespace through reflection
- ///
- /// Namespace to process
- /// Assembly to use. Default is Assembly.GetExecutingAssembly()
- ///
-
- 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
+{
+ ///
+ /// 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.
+ ///
+
+ 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;
+
+ ///
+ /// The main entry point of Minecraft Console Client
+ ///
+
+ 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 args_tmp = args.ToList();
+ 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();
+ }
+
+ ///
+ /// Reduest user to submit password.
+ ///
+ 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(' '); }
+ }
+ }
+
+ ///
+ /// Start a new Client
+ ///
+
+ 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);
+ }
+ }
+
+ ///
+ /// Disconnect the current client from the server and restart it
+ ///
+
+ 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();
+ }
+
+ ///
+ /// Disconnect the current client from the server and exit the app
+ ///
+
+ 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();
+ }
+
+ ///
+ /// 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.
+ ///
+ /// Error message to display and optionally pass to AutoRelog bot
+ /// Specify if the error is related to an incompatible or unkown server version
+ /// If set, the error message will be processed by the AutoRelog bot
+
+ 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();
+ }
+ }
+ }
+
+ ///
+ /// Detect if the user is running Minecraft Console Client through Mono
+ ///
+
+ public static bool isUsingMono
+ {
+ get
+ {
+ return Type.GetType("Mono.Runtime") != null;
+ }
+ }
+
+ ///
+ /// Enumerate types in namespace through reflection
+ ///
+ /// Namespace to process
+ /// Assembly to use. Default is Assembly.GetExecutingAssembly()
+ ///
+
+ 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();
+ }
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/ChatParser.cs b/MinecraftClient/Protocol/Handlers/ChatParser.cs
index df86f8a4..5c5b6c84 100644
--- a/MinecraftClient/Protocol/Handlers/ChatParser.cs
+++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs
@@ -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, "");
- }
-
- ///
- /// An internal class to store unserialized JSON data
- /// The data can be an object, an array or a string
- ///
-
- private class JSONData
- {
- public enum DataType { Object, Array, String };
- private DataType type;
- public DataType Type { get { return type; } }
- public Dictionary Properties;
- public List DataArray;
- public string StringValue;
- public JSONData(DataType datatype)
- {
- type = datatype;
- Properties = new Dictionary();
- DataArray = new List();
- StringValue = String.Empty;
- }
+ return JSONData2String(Json.ParseJson(json), "");
}
///
@@ -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);
}
- ///
- /// Parse a JSON string to build a JSON object
- ///
- /// String to parse
- /// Cursor start (set to 0 for function init)
- ///
-
- private static JSONData String2Data(string toparse, ref int cursorpos)
- {
- try
- {
- JSONData data;
- switch (toparse[cursorpos])
- {
- //Object
- case '{':
- data = new JSONData(JSONData.DataType.Object);
- cursorpos++;
- while (toparse[cursorpos] != '}')
- {
- if (toparse[cursorpos] == '"')
- {
- JSONData propertyname = String2Data(toparse, ref cursorpos);
- if (toparse[cursorpos] == ':') { cursorpos++; } else { /* parse error ? */ }
- JSONData propertyData = String2Data(toparse, ref cursorpos);
- data.Properties[propertyname.StringValue] = propertyData;
- }
- else cursorpos++;
- }
- cursorpos++;
- break;
-
- //Array
- case '[':
- data = new JSONData(JSONData.DataType.Array);
- cursorpos++;
- while (toparse[cursorpos] != ']')
- {
- if (toparse[cursorpos] == ',') { cursorpos++; }
- JSONData arrayItem = String2Data(toparse, ref cursorpos);
- data.DataArray.Add(arrayItem);
- }
- cursorpos++;
- break;
-
- //String
- case '"':
- data = new JSONData(JSONData.DataType.String);
- cursorpos++;
- while (toparse[cursorpos] != '"')
- {
- if (toparse[cursorpos] == '\\')
- {
- 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
- 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);
- }
- }
-
///
/// Use a JSON Object to build the corresponding string
///
/// JSON object to convert
/// Allow parent color code to affect child elements (set to "" for function init)
/// returns the Minecraft-formatted string
-
- 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 "";
}
- ///
- /// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f)
- ///
- /// Char to test
- /// True if hexadecimal
-
- private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); }
-
///
/// Do a HTTP request to get a webpage or text data from a server file
///
diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs
new file mode 100755
index 00000000..a72b4ad5
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Protocol.Handlers.Forge
+{
+ ///
+ /// 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
+ ///
+ enum FMLHandshakeClientState : byte
+ {
+ START,
+ HELLO,
+ WAITINGSERVERDATA,
+ WAITINGSERVERCOMPLETE,
+ PENDINGCOMPLETE,
+ COMPLETE,
+ DONE,
+ ERROR
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs
new file mode 100755
index 00000000..2402eff0
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Protocol.Handlers.Forge
+{
+ ///
+ /// 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
+ ///
+ enum FMLHandshakeDiscriminator : byte
+ {
+ ServerHello = 0,
+ ClientHello = 1,
+ ModList = 2,
+ RegistryData = 3,
+ HandshakeAck = 255, //-1
+ HandshakeReset = 254, //-2
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
new file mode 100755
index 00000000..03180432
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Protocol.Handlers.Forge
+{
+ ///
+ /// Contains information about a modded server install.
+ ///
+ public class ForgeInfo
+ {
+ ///
+ /// Represents an individual forge mod.
+ ///
+ 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 Mods;
+
+ ///
+ /// Create a new ForgeInfo from the given data.
+ ///
+ /// The modinfo JSON tag.
+ 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();
+ 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));
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/Json.cs b/MinecraftClient/Protocol/Handlers/Json.cs
new file mode 100644
index 00000000..c95fdc89
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/Json.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Protocol.Handlers
+{
+ ///
+ /// This class parses JSON data and returns an object describing that data.
+ /// Really lightweight JSON handling by ORelio - (c) 2013 - 2014
+ ///
+
+ static class Json
+ {
+ ///
+ /// Parse some JSON and return the corresponding JSON object
+ ///
+
+ public static JSONData ParseJson(string json)
+ {
+ int cursorpos = 0;
+ return String2Data(json, ref cursorpos);
+ }
+
+ ///
+ /// The class storing unserialized JSON data
+ /// The data can be an object, an array or a string
+ ///
+
+ public class JSONData
+ {
+ public enum DataType { Object, Array, String };
+ private DataType type;
+ public DataType Type { get { return type; } }
+ public Dictionary Properties;
+ public List DataArray;
+ public string StringValue;
+ public JSONData(DataType datatype)
+ {
+ type = datatype;
+ Properties = new Dictionary();
+ DataArray = new List();
+ StringValue = String.Empty;
+ }
+ }
+
+ ///
+ /// Parse a JSON string to build a JSON object
+ ///
+ /// String to parse
+ /// Cursor start (set to 0 for function init)
+
+ private static JSONData String2Data(string toparse, ref int cursorpos)
+ {
+ try
+ {
+ JSONData data;
+ switch (toparse[cursorpos])
+ {
+ //Object
+ case '{':
+ data = new JSONData(JSONData.DataType.Object);
+ cursorpos++;
+ while (toparse[cursorpos] != '}')
+ {
+ if (toparse[cursorpos] == '"')
+ {
+ JSONData propertyname = String2Data(toparse, ref cursorpos);
+ if (toparse[cursorpos] == ':') { cursorpos++; } else { /* parse error ? */ }
+ JSONData propertyData = String2Data(toparse, ref cursorpos);
+ data.Properties[propertyname.StringValue] = propertyData;
+ }
+ else cursorpos++;
+ }
+ cursorpos++;
+ break;
+
+ //Array
+ case '[':
+ data = new JSONData(JSONData.DataType.Array);
+ cursorpos++;
+ while (toparse[cursorpos] != ']')
+ {
+ if (toparse[cursorpos] == ',') { cursorpos++; }
+ JSONData arrayItem = String2Data(toparse, ref cursorpos);
+ data.DataArray.Add(arrayItem);
+ }
+ cursorpos++;
+ break;
+
+ //String
+ case '"':
+ data = new JSONData(JSONData.DataType.String);
+ cursorpos++;
+ while (toparse[cursorpos] != '"')
+ {
+ if (toparse[cursorpos] == '\\')
+ {
+ 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
+ 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);
+ }
+ }
+
+ ///
+ /// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f)
+ ///
+ /// Char to test
+ /// True if hexadecimal
+
+ private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); }
+ }
+}
diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs
index 740342f5..14dce500 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol16.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs
@@ -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
+ }
+
+ ///
+ /// Send a plugin channel packet to the server.
+ ///
+ /// Channel to send packet on
+ /// packet Data
+
+ 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);
diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs
deleted file mode 100644
index a514ffc5..00000000
--- a/MinecraftClient/Protocol/Handlers/Protocol17.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Implementation for Minecraft 1.7.X Protocol
- ///
-
- 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;
- }
-
- ///
- /// Separate thread. Network reading loop.
- ///
-
- 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, "");
- }
-
- ///
- /// Read and data from the network. Should be called on a separate thread.
- ///
- ///
-
- 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;
- }
-
- ///
- /// Start the updating thread. Should be called after login success.
- ///
-
- private void StartUpdating()
- {
- netRead = new Thread(new ThreadStart(Updater));
- netRead.Name = "ProtocolPacketHandler";
- netRead.Start();
- }
-
- ///
- /// Disconnect from the server, cancel network reading.
- ///
-
- public void Dispose()
- {
- try
- {
- if (netRead != null)
- {
- netRead.Abort();
- c.Close();
- }
- }
- catch { }
- }
-
- ///
- /// Read some data and discard the result
- ///
- /// Amount of bytes to read
-
- private void readData(int offset)
- {
- if (offset > 0)
- {
- try
- {
- byte[] cache = new byte[offset];
- Receive(cache, 0, offset, SocketFlags.None);
- }
- catch (OutOfMemoryException) { }
- }
- }
-
- ///
- /// Read a string from the network
- ///
- /// The string
-
- 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 "";
- }
-
- ///
- /// Read a uuid from the network
- ///
- /// Cache of bytes to read from
- /// The uuid
-
- private Guid readNextUUID()
- {
- byte[] cache = new byte[16];
- Receive(cache, 0, 16, SocketFlags.None);
- return new Guid(cache);
- }
-
- ///
- /// Read a short from the network
- ///
- ///
-
- private short readNextShort()
- {
- byte[] tmp = new byte[2];
- Receive(tmp, 0, 2, SocketFlags.None);
- Array.Reverse(tmp);
- return BitConverter.ToInt16(tmp, 0);
- }
-
- ///
- /// Read a boolean from the network
- ///
- ///
-
- private bool readNextBool()
- {
- byte[] tmp = new byte[1];
- Receive(tmp, 0, 1, SocketFlags.None);
- return tmp[0] != 0x00;
- }
-
- ///
- /// Read a byte array from the network
- ///
- /// The byte array
-
- 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;
- }
-
- ///
- /// Read an integer from the network
- ///
- /// The integer
-
- 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;
- }
-
- ///
- /// Build an integer for sending over the network
- ///
- /// Integer to encode
- /// Byte array for this integer
-
- private static byte[] getVarInt(int paramInt)
- {
- List bytes = new List();
- while ((paramInt & -128) != 0)
- {
- bytes.Add((byte)(paramInt & 127 | 128));
- paramInt = (int)(((uint)paramInt) >> 7);
- }
- bytes.Add((byte)paramInt);
- return bytes.ToArray();
- }
-
- ///
- /// Easily append several byte arrays
- ///
- /// Bytes to append
- /// Array containing all the data
-
- private static byte[] concatBytes(params byte[][] bytes)
- {
- List result = new List();
- foreach (byte[] array in bytes)
- result.AddRange(array);
- return result.ToArray();
- }
-
- ///
- /// C-like atoi function for parsing an int from string
- ///
- /// String to parse
- /// Int parsed
-
- private static int atoi(string str)
- {
- return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray()));
- }
-
- ///
- /// Network reading method. Read bytes from the socket or encrypted socket.
- ///
-
- 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);
- }
- }
-
- ///
- /// Network sending method. Send bytes using the socket or encrypted socket.
- ///
- ///
-
- private void Send(byte[] buffer)
- {
- if (encrypted)
- {
- s.Write(buffer, 0, buffer.Length);
- }
- else c.Client.Send(buffer);
- }
-
- ///
- /// Do the Minecraft login.
- ///
- /// True if login successful
-
- 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;
- }
-
- ///
- /// Start network encryption. Automatically called by Login() if the server requests encryption.
- ///
- /// True if encryption was successful
-
- 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;
- }
-
- ///
- /// Useless padding packet for solving Mono issue.
- ///
- /// The padding packet
-
- 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);
- }
-
- ///
- /// Send a chat message to the server
- ///
- /// Message
- /// True if properly sent
-
- 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; }
- }
-
- ///
- /// Send a respawn packet to the server
- ///
- /// Message
- /// True if properly sent
-
- 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; }
- }
-
- ///
- /// Disconnect from the server
- ///
- /// Optional disconnect reason
-
- 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) { }
- }
-
- ///
- /// Autocomplete text while typing username or command
- ///
- /// Text behind cursor
- /// Completed text
-
- 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;
- }
-
- ///
- /// Ping a Minecraft server to get information about the server
- ///
- /// True if ping was successful
-
- 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;
- }
- }
-}
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs
index a1eb60f3..4e804390 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs
@@ -6,33 +6,51 @@ using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
+using System.Security.Cryptography;
+using MinecraftClient.Protocol.Handlers.Forge;
+using MinecraftClient.Mapping;
namespace MinecraftClient.Protocol.Handlers
{
///
- /// Implementation for Minecraft 1.8.X Protocol
+ /// Implementation for Minecraft 1.7.X, 1.8.X, 1.9.X Protocols
///
class Protocol18Handler : IMinecraftCom
{
- IMinecraftComHandler handler;
+ private const int MC18Version = 47;
+ private const int MC19Version = 107;
+
private int compression_treshold = 0;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool login_phase = true;
private bool encrypted = false;
private int protocolversion;
- private Thread netRead;
- Crypto.IAesStream s;
+
+ // Server forge info -- may be null.
+ private ForgeInfo forgeInfo;
+ private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START;
+
+ IMinecraftComHandler handler;
+ Thread netRead;
+ IAesStream s;
TcpClient c;
- public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
+ public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler, ForgeInfo ForgeInfo)
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
+ this.forgeInfo = ForgeInfo;
+
+ if (Settings.TerrainAndMovements && protocolversion > MC18Version)
+ {
+ ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version.");
+ Settings.TerrainAndMovements = false;
+ }
}
private Protocol18Handler(TcpClient Client)
@@ -46,19 +64,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)
- {
- SendRAW(getPaddingPacket());
- keep_alive_timer = keep_alive_interval;
- }
}
while (Update());
}
@@ -83,12 +93,13 @@ namespace MinecraftClient.Protocol.Handlers
while (c.Client.Available > 0)
{
int packetID = 0;
- byte[] packetData = new byte[] { };
- readNextPacket(ref packetID, ref packetData);
- handlePacket(packetID, packetData);
+ List packetData = new List();
+ readNextPacket(ref packetID, packetData);
+ handlePacket(packetID, new List(packetData));
}
}
catch (SocketException) { return false; }
+ catch (NullReferenceException) { return false; }
return true;
}
@@ -98,19 +109,106 @@ namespace MinecraftClient.Protocol.Handlers
/// will contain packet ID
/// will contain raw packet Data
- private void readNextPacket(ref int packetID, ref byte[] packetData)
+ private void readNextPacket(ref int packetID, List packetData)
{
+ packetData.Clear();
int size = readNextVarIntRAW(); //Packet size
- packetData = readDataRAW(size); //Packet contents
+ packetData.AddRange(readDataRAW(size)); //Packet contents
- if (compression_treshold > 0) //Handle packet decompression
+ //Handle packet decompression
+ if (protocolversion >= MC18Version
+ && compression_treshold > 0)
{
- int size_uncompressed = readNextVarInt(ref packetData);
- if (size_uncompressed != 0) // != 0 means compressed, let's decompress
- packetData = ZlibUtils.Decompress(packetData, size_uncompressed);
+ int sizeUncompressed = readNextVarInt(packetData);
+ if (sizeUncompressed != 0) // != 0 means compressed, let's decompress
+ {
+ byte[] toDecompress = packetData.ToArray();
+ byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed);
+ packetData.Clear();
+ packetData.AddRange(uncompressed);
+ }
}
- packetID = readNextVarInt(ref packetData); //Packet ID
+ packetID = readNextVarInt(packetData); //Packet ID
+ }
+
+ ///
+ /// Abstract incoming packet numbering
+ ///
+
+ private enum PacketIncomingType
+ {
+ KeepAlive,
+ JoinGame,
+ ChatMessage,
+ PlayerPositionAndLook,
+ ChunkData,
+ MultiBlockChange,
+ BlockChange,
+ MapChunkBulk,
+ UnloadChunk,
+ PlayerListUpdate,
+ TabCompleteResult,
+ PluginMessage,
+ KickPacket,
+ NetworkCompressionTreshold,
+ ResourcePackSend,
+ UnknownPacket
+ }
+
+ ///
+ /// Get abstract numbering ov the specified packet ID
+ ///
+ /// Packet ID
+ /// Protocol version
+ /// Abstract numbering
+
+ private PacketIncomingType getPacketIncomingType(int packetID, int protocol)
+ {
+ if (protocol < MC19Version)
+ {
+ switch (packetID)
+ {
+ case 0x00: return PacketIncomingType.KeepAlive;
+ case 0x01: return PacketIncomingType.JoinGame;
+ case 0x02: return PacketIncomingType.ChatMessage;
+ case 0x08: return PacketIncomingType.PlayerPositionAndLook;
+ case 0x21: return PacketIncomingType.ChunkData;
+ case 0x22: return PacketIncomingType.MultiBlockChange;
+ case 0x23: return PacketIncomingType.BlockChange;
+ case 0x26: return PacketIncomingType.MapChunkBulk;
+ //UnloadChunk does not exists prior to 1.9
+ case 0x38: return PacketIncomingType.PlayerListUpdate;
+ case 0x3A: return PacketIncomingType.TabCompleteResult;
+ case 0x3F: return PacketIncomingType.PluginMessage;
+ case 0x40: return PacketIncomingType.KickPacket;
+ case 0x46: return PacketIncomingType.NetworkCompressionTreshold;
+ case 0x48: return PacketIncomingType.ResourcePackSend;
+ default: return PacketIncomingType.UnknownPacket;
+ }
+ }
+ else
+ {
+ switch (packetID)
+ {
+ case 0x1F: return PacketIncomingType.KeepAlive;
+ case 0x23: return PacketIncomingType.JoinGame;
+ case 0x0F: return PacketIncomingType.ChatMessage;
+ case 0x2E: return PacketIncomingType.PlayerPositionAndLook;
+ case 0x20: return PacketIncomingType.ChunkData;
+ case 0x10: return PacketIncomingType.MultiBlockChange;
+ case 0x0B: return PacketIncomingType.BlockChange;
+ //MapChunkBulk removed in 1.9
+ case 0x1D: return PacketIncomingType.UnloadChunk;
+ case 0x2D: return PacketIncomingType.PlayerListUpdate;
+ case 0x0E: return PacketIncomingType.TabCompleteResult;
+ case 0x18: return PacketIncomingType.PluginMessage;
+ case 0x1A: return PacketIncomingType.KickPacket;
+ //NetworkCompressionTreshold removed in 1.9
+ case 0x32: return PacketIncomingType.ResourcePackSend;
+ default: return PacketIncomingType.UnknownPacket;
+ }
+ }
}
///
@@ -120,74 +218,419 @@ namespace MinecraftClient.Protocol.Handlers
/// Packet contents
/// TRUE if the packet was processed, FALSE if ignored or unknown
- private bool handlePacket(int packetID, byte[] packetData)
+ private bool handlePacket(int packetID, List packetData)
{
if (login_phase)
{
switch (packetID) //Packet IDs are different while logging in
{
case 0x03:
- compression_treshold = readNextVarInt(ref packetData);
+ if (protocolversion >= MC18Version)
+ compression_treshold = readNextVarInt(packetData);
break;
default:
return false; //Ignored packet
}
}
- else //Regular in-game packets
+ // Regular in-game packets
+
+ switch (getPacketIncomingType(packetID, protocolversion))
{
- switch (packetID)
- {
- case 0x00: //Keep-Alive
- SendPacket(0x00, getVarInt(readNextVarInt(ref packetData)));
- break;
- case 0x02: //Chat message
- handler.OnTextReceived(ChatParser.ParseText(readNextString(ref packetData)));
- break;
- case 0x38: //Player List update
- int action = readNextVarInt(ref packetData);
- int numActions = readNextVarInt(ref packetData);
- Guid uuid = readNextUUID(ref packetData);
- switch (action)
+ case PacketIncomingType.KeepAlive:
+ SendPacket(protocolversion >= MC19Version ? 0x0B : 0x00, packetData);
+ break;
+ case PacketIncomingType.JoinGame:
+ handler.OnGameJoined();
+ break;
+ case PacketIncomingType.ChatMessage:
+ string message = readNextString(packetData);
+ try
+ {
+ //Hide system messages or xp bar messages?
+ byte messageType = readNextByte(packetData);
+ if ((messageType == 1 && !Settings.DisplaySystemMessages)
+ || (messageType == 2 && !Settings.DisplayXPBarMessages))
+ break;
+ }
+ catch (ArgumentOutOfRangeException) { /* No message type */ }
+ handler.OnTextReceived(ChatParser.ParseText(message));
+ break;
+ case PacketIncomingType.PlayerPositionAndLook:
+ if (Settings.TerrainAndMovements)
+ {
+ double x = readNextDouble(packetData);
+ double y = readNextDouble(packetData);
+ double z = readNextDouble(packetData);
+ readData(8, packetData); //Ignore look
+ byte locMask = readNextByte(packetData);
+ Location location = handler.GetCurrentLocation();
+
+ location.X = (locMask & 1 << 0) != 0 ? location.X + x : x;
+ location.Y = (locMask & 1 << 1) != 0 ? location.Y + y : y;
+ location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z;
+
+ handler.UpdateLocation(location);
+
+ if (protocolversion >= MC19Version)
+ readNextVarInt(packetData);
+ }
+ break;
+ case PacketIncomingType.ChunkData:
+ if (Settings.TerrainAndMovements)
+ {
+ int chunkX = readNextInt(packetData);
+ int chunkZ = readNextInt(packetData);
+ bool chunksContinuous = readNextBool(packetData);
+ ushort chunkMask = protocolversion >= MC19Version ? (ushort)readNextVarInt(packetData) : readNextUShort(packetData);
+ int dataSize = readNextVarInt(packetData);
+ ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData);
+ }
+ break;
+ case PacketIncomingType.MultiBlockChange:
+ if (Settings.TerrainAndMovements)
+ {
+ int chunkX = readNextInt(packetData);
+ int chunkZ = readNextInt(packetData);
+ int recordCount = readNextVarInt(packetData);
+ for (int i = 0; i < recordCount; i++)
{
- case 0x00: //Player Join
- string name = readNextString(ref packetData);
- handler.OnPlayerJoin(uuid, name);
- break;
- case 0x04: //Player Leave
- handler.OnPlayerLeave(uuid);
- break;
- default:
- //Unknown player list item type
- break;
+ byte locationXZ = readNextByte(packetData);
+ int blockX = locationXZ >> 4;
+ int blockZ = locationXZ & 0x0F;
+ int blockY = (ushort)readNextByte(packetData);
+ Block block = new Block((ushort)readNextVarInt(packetData));
+ handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block);
}
- break;
- case 0x3A: //Tab-Complete Result
- int autocomplete_count = readNextVarInt(ref packetData);
- string tab_list = "";
- for (int i = 0; i < autocomplete_count; i++)
+ }
+ break;
+ case PacketIncomingType.BlockChange:
+ if (Settings.TerrainAndMovements)
+ handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData)));
+ break;
+ case PacketIncomingType.MapChunkBulk:
+ if (protocolversion < MC19Version && Settings.TerrainAndMovements)
+ {
+ bool hasSkyLight = readNextBool(packetData);
+ int chunkCount = readNextVarInt(packetData);
+
+ //Read chunk records
+ int[] chunkXs = new int[chunkCount];
+ int[] chunkZs = new int[chunkCount];
+ ushort[] chunkMasks = new ushort[chunkCount];
+ for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
{
- autocomplete_result = readNextString(ref packetData);
- if (autocomplete_result != "")
- tab_list = tab_list + autocomplete_result + " ";
+ chunkXs[chunkColumnNo] = readNextInt(packetData);
+ chunkZs[chunkColumnNo] = readNextInt(packetData);
+ chunkMasks[chunkColumnNo] = readNextUShort(packetData);
}
- autocomplete_received = true;
- tab_list = tab_list.Trim();
- if (tab_list.Length > 0)
- ConsoleIO.WriteLineFormatted("§8" + tab_list, false);
- break;
- case 0x40: //Kick Packet
- handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData)));
- return false;
- case 0x46: //Network Compression Treshold Info
- compression_treshold = readNextVarInt(ref packetData);
- break;
- default:
- return false; //Ignored packet
- }
+
+ //Process chunk records
+ for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
+ ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData);
+ }
+ break;
+ case PacketIncomingType.UnloadChunk:
+ if (protocolversion >= MC19Version && Settings.TerrainAndMovements)
+ {
+ int chunkX = readNextInt(packetData);
+ int chunkZ = readNextInt(packetData);
+ handler.GetWorld()[chunkX, chunkZ] = null;
+ }
+ break;
+ case PacketIncomingType.PlayerListUpdate:
+ if (protocolversion >= MC18Version)
+ {
+ int action = readNextVarInt(packetData);
+ int numActions = readNextVarInt(packetData);
+ for (int i = 0; i < numActions; i++)
+ {
+ Guid uuid = readNextUUID(packetData);
+ switch (action)
+ {
+ case 0x00: //Player Join
+ string name = readNextString(packetData);
+ int propNum = readNextVarInt(packetData);
+ for (int p = 0; p < propNum; p++)
+ {
+ readNextString(packetData);
+ readNextString(packetData);
+ if (readNextBool(packetData))
+ readNextString(packetData);
+ }
+ readNextVarInt(packetData);
+ readNextVarInt(packetData);
+ if (readNextBool(packetData))
+ readNextString(packetData);
+ handler.OnPlayerJoin(uuid, name);
+ break;
+ case 0x01: //Update gamemode
+ case 0x02: //Update latency
+ readNextVarInt(packetData);
+ break;
+ case 0x03: //Update display name
+ if (readNextBool(packetData))
+ readNextString(packetData);
+ break;
+ case 0x04: //Player Leave
+ handler.OnPlayerLeave(uuid);
+ break;
+ default:
+ //Unknown player list item type
+ break;
+ }
+ }
+ }
+ else //MC 1.7.X does not provide UUID in tab-list updates
+ {
+ string name = readNextString(packetData);
+ bool online = readNextBool(packetData);
+ short ping = readNextShort(packetData);
+ 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 PacketIncomingType.TabCompleteResult:
+ int autocomplete_count = readNextVarInt(packetData);
+ string tab_list = "";
+ for (int i = 0; i < autocomplete_count; i++)
+ {
+ autocomplete_result = readNextString(packetData);
+ 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 PacketIncomingType.PluginMessage:
+ String channel = readNextString(packetData);
+ if (protocolversion < MC18Version)
+ {
+ if (forgeInfo == null)
+ {
+ // 1.7 and lower prefix plugin channel packets with the length.
+ // We can skip it, though.
+ readNextShort(packetData);
+ }
+ else
+ {
+ // Forge does something even weirder with the length.
+ readNextVarShort(packetData);
+ }
+ }
+
+ // The remaining data in the array is the entire payload of the packet.
+ handler.OnPluginChannelMessage(channel, packetData.ToArray());
+
+ #region Forge Login
+ if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE)
+ {
+ if (channel == "FML|HS")
+ {
+ FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(packetData);
+
+ if (discriminator == FMLHandshakeDiscriminator.HandshakeReset)
+ {
+ fmlHandshakeState = FMLHandshakeClientState.START;
+ return true;
+ }
+
+ switch (fmlHandshakeState)
+ {
+ case FMLHandshakeClientState.START:
+ if (discriminator != FMLHandshakeDiscriminator.ServerHello)
+ return false;
+
+ // Send the plugin channel registration.
+ // REGISTER is somewhat special in that it doesn't actually include length information,
+ // and is also \0-separated.
+ // Also, yes, "FML" is there twice. Don't ask me why, but that's the way forge does it.
+ string[] channels = { "FML|HS", "FML", "FML|MP", "FML", "FORGE" };
+ SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels)));
+
+ byte fmlProtocolVersion = readNextByte(packetData);
+ // There's another value afterwards for the dimension, but we don't need it.
+
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion);
+
+ // Tell the server we're running the same version.
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion });
+
+ // Then tell the server that we're running the same mods.
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Sending falsified mod list to server...");
+ byte[][] mods = new byte[forgeInfo.Mods.Count][];
+ for (int i = 0; i < forgeInfo.Mods.Count; i++)
+ {
+ ForgeInfo.ForgeMod mod = forgeInfo.Mods[i];
+ mods[i] = concatBytes(getString(mod.ModID), getString(mod.Version));
+ }
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList,
+ concatBytes(getVarInt(forgeInfo.Mods.Count), concatBytes(mods)));
+
+ fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA;
+
+ return true;
+ case FMLHandshakeClientState.WAITINGSERVERDATA:
+ if (discriminator != FMLHandshakeDiscriminator.ModList)
+ return false;
+
+ Thread.Sleep(2000);
+
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Accepting server mod list...");
+ // Tell the server that yes, we are OK with the mods it has
+ // even though we don't actually care what mods it has.
+
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
+ new byte[] { (byte)FMLHandshakeClientState.WAITINGSERVERDATA });
+
+ fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE;
+ return false;
+ case FMLHandshakeClientState.WAITINGSERVERCOMPLETE:
+ // The server now will tell us a bunch of registry information.
+ // We need to read it all, though, until it says that there is no more.
+ if (discriminator != FMLHandshakeDiscriminator.RegistryData)
+ return false;
+
+ if (protocolversion < MC18Version)
+ {
+ // 1.7.10 and below have one registry
+ // with blocks and items.
+ int registrySize = readNextVarInt(packetData);
+
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Received registry with " + registrySize + " entries");
+
+ fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
+ }
+ else
+ {
+ // 1.8+ has more than one registry.
+
+ bool hasNextRegistry = readNextBool(packetData);
+ string registryName = readNextString(packetData);
+ int registrySize = readNextVarInt(packetData);
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + " with " + registrySize + " entries");
+ if (!hasNextRegistry)
+ fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
+ }
+
+ return false;
+ case FMLHandshakeClientState.PENDINGCOMPLETE:
+ // The server will ask us to accept the registries.
+ // Just say yes.
+ if (discriminator != FMLHandshakeDiscriminator.HandshakeAck)
+ return false;
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLineFormatted("§8Accepting server registries...");
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
+ new byte[] { (byte)FMLHandshakeClientState.PENDINGCOMPLETE });
+ fmlHandshakeState = FMLHandshakeClientState.COMPLETE;
+
+ return true;
+ case FMLHandshakeClientState.COMPLETE:
+ // One final "OK". On the actual forge source, a packet is sent from
+ // the client to the client saying that the connection was complete, but
+ // we don't need to do that.
+
+ SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
+ new byte[] { (byte)FMLHandshakeClientState.COMPLETE });
+ if (Settings.DebugMessages)
+ ConsoleIO.WriteLine("Forge server connection complete!");
+ fmlHandshakeState = FMLHandshakeClientState.DONE;
+ return true;
+ }
+ }
+ }
+ #endregion
+
+ return false;
+ case PacketIncomingType.KickPacket:
+ handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(packetData)));
+ return false;
+ case PacketIncomingType.NetworkCompressionTreshold:
+ if (protocolversion >= MC18Version && protocolversion < MC19Version)
+ compression_treshold = readNextVarInt(packetData);
+ break;
+ case PacketIncomingType.ResourcePackSend:
+ string url = readNextString(packetData);
+ string hash = readNextString(packetData);
+ //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory
+ SendPacket(protocolversion >= MC19Version ? 0x16 : 0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3)));
+ SendPacket(protocolversion >= MC19Version ? 0x16 : 0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0)));
+ break;
+ default:
+ return false; //Ignored packet
}
return true; //Packet processed
}
+ ///
+ /// Process chunk column data from the server and (un)load the chunk from the Minecraft world
+ ///
+ /// Chunk X location
+ /// Chunk Z location
+ /// Chunk mask for reading data
+ /// Contains skylight info
+ /// Are the chunk continuous
+ /// Cache for reading chunk data
+
+ private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List cache)
+ {
+ if (protocolversion < MC19Version && chunksContinuous && chunkMask == 0)
+ {
+ //Unload the entire chunk column
+ handler.GetWorld()[chunkX, chunkZ] = null;
+ }
+ else
+ {
+ //Load chunk data from the server
+ for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++)
+ {
+ if ((chunkMask & (1 << chunkY)) != 0)
+ {
+ Chunk chunk = new Chunk();
+
+ //Read chunk data, all at once for performance reasons, and build the chunk object
+ Queue queue = new Queue(readNextUShortsLittleEndian(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ, cache));
+ for (int blockY = 0; blockY < Chunk.SizeY; blockY++)
+ for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++)
+ for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
+ chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue());
+
+ //We have our chunk, save the chunk into the world
+ if (handler.GetWorld()[chunkX, chunkZ] == null)
+ handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
+ handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
+ }
+ }
+
+ //Skip light information
+ for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++)
+ {
+ if ((chunkMask & (1 << chunkY)) != 0)
+ {
+ //Skip block light
+ readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache);
+
+ //Skip sky light
+ if (hasSkyLight)
+ readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache);
+ }
+ }
+
+ //Skip biome metadata
+ if (chunksContinuous)
+ readData(Chunk.SizeX * Chunk.SizeZ, cache);
+ }
+ }
+
///
/// Start the updating thread. Should be called after login success.
///
@@ -221,7 +664,7 @@ namespace MinecraftClient.Protocol.Handlers
///
/// Amount of bytes to read
/// The data read from the network as an array
-
+
private byte[] readDataRAW(int offset)
{
if (offset > 0)
@@ -236,7 +679,7 @@ namespace MinecraftClient.Protocol.Handlers
}
return new byte[] { };
}
-
+
///
/// Read some data from a cache of bytes and remove it from the cache
///
@@ -244,18 +687,11 @@ namespace MinecraftClient.Protocol.Handlers
/// Cache of bytes to read from
/// The data read from the cache as an array
- private byte[] readData(int offset, ref byte[] cache)
+ private static byte[] readData(int offset, List cache)
{
- List read = new List();
- List list = new List(cache);
- while (offset > 0 && list.Count > 0)
- {
- read.Add(list[0]);
- list.RemoveAt(0);
- offset--;
- }
- cache = list.ToArray();
- return read.ToArray();
+ byte[] result = cache.Take(offset).ToArray();
+ cache.RemoveRange(0, offset);
+ return result;
}
///
@@ -264,25 +700,97 @@ namespace MinecraftClient.Protocol.Handlers
/// Cache of bytes to read from
/// The string
- private string readNextString(ref byte[] cache)
+ private static string readNextString(List cache)
{
- int length = readNextVarInt(ref cache);
+ int length = readNextVarInt(cache);
if (length > 0)
{
- return Encoding.UTF8.GetString(readData(length, ref cache));
+ return Encoding.UTF8.GetString(readData(length, cache));
}
else return "";
}
+ ///
+ /// Read a boolean from a cache of bytes and remove it from the cache
+ ///
+ /// The boolean value
+
+ private static bool readNextBool(List cache)
+ {
+ return readNextByte(cache) != 0x00;
+ }
+
+ ///
+ /// Read a short integer from a cache of bytes and remove it from the cache
+ ///
+ /// The short integer value
+
+ private static short readNextShort(List cache)
+ {
+ byte[] rawValue = readData(2, cache);
+ Array.Reverse(rawValue); //Endianness
+ return BitConverter.ToInt16(rawValue, 0);
+ }
+
+ ///
+ /// Read an integer from a cache of bytes and remove it from the cache
+ ///
+ /// The integer value
+
+ private static int readNextInt(List cache)
+ {
+ byte[] rawValue = readData(4, cache);
+ Array.Reverse(rawValue); //Endianness
+ return BitConverter.ToInt32(rawValue, 0);
+ }
+
+ ///
+ /// Read an unsigned short integer from a cache of bytes and remove it from the cache
+ ///
+ /// The unsigned short integer value
+
+ private static ushort readNextUShort(List cache)
+ {
+ byte[] rawValue = readData(2, cache);
+ Array.Reverse(rawValue); //Endianness
+ return BitConverter.ToUInt16(rawValue, 0);
+ }
+
+ ///
+ /// Read an unsigned short integer from a cache of bytes and remove it from the cache
+ ///
+ /// The unsigned short integer value
+
+ private static ulong readNextULong(List cache)
+ {
+ byte[] rawValue = readData(8, cache);
+ Array.Reverse(rawValue); //Endianness
+ return BitConverter.ToUInt64(rawValue, 0);
+ }
+
+ ///
+ /// Read several little endian unsigned short integers at once from a cache of bytes and remove them from the cache
+ ///
+ /// The unsigned short integer value
+
+ private static ushort[] readNextUShortsLittleEndian(int amount, List cache)
+ {
+ byte[] rawValues = readData(2 * amount, cache);
+ ushort[] result = new ushort[amount];
+ for (int i = 0; i < amount; i++)
+ result[i] = BitConverter.ToUInt16(rawValues, i * 2);
+ return result;
+ }
+
///
/// Read a uuid from a cache of bytes and remove it from the cache
///
/// Cache of bytes to read from
/// The uuid
- private Guid readNextUUID(ref byte[] cache)
+ private static Guid readNextUUID(List cache)
{
- return new Guid(readData(16, ref cache));
+ return new Guid(readData(16, cache));
}
///
@@ -291,10 +799,24 @@ namespace MinecraftClient.Protocol.Handlers
/// Cache of bytes to read from
/// The byte array
- private byte[] readNextByteArray(ref byte[] cache)
+ private byte[] readNextByteArray(List cache)
{
- int len = readNextVarInt(ref cache);
- return readData(len, ref cache);
+ int len = protocolversion >= MC18Version
+ ? readNextVarInt(cache)
+ : readNextShort(cache);
+ return readData(len, cache);
+ }
+
+ ///
+ /// Read a double from a cache of bytes and remove it from the cache
+ ///
+ /// The double value
+
+ private static double readNextDouble(List cache)
+ {
+ byte[] rawValue = readData(8, cache);
+ Array.Reverse(rawValue); //Endianness
+ return BitConverter.ToDouble(rawValue, 0);
}
///
@@ -318,23 +840,21 @@ namespace MinecraftClient.Protocol.Handlers
}
return i;
}
-
+
///
/// Read an integer from a cache of bytes and remove it from the cache
///
/// Cache of bytes to read from
/// The integer
- private int readNextVarInt(ref byte[] cache)
+ private static int readNextVarInt(List cache)
{
int i = 0;
int j = 0;
int k = 0;
- byte[] tmp = new byte[1];
while (true)
{
- tmp = readData(1, ref cache);
- k = tmp[0];
+ k = readNextByte(cache);
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((k & 0x80) != 128) break;
@@ -342,6 +862,38 @@ namespace MinecraftClient.Protocol.Handlers
return i;
}
+ ///
+ /// Read an "extended short", which is actually an int of some kind, from the cache of bytes.
+ /// This is only done with forge. It looks like it's a normal short, except that if the high
+ /// bit is set, it has an extra byte.
+ ///
+ /// Cache of bytes to read from
+ /// The int
+
+ private static int readNextVarShort(List cache)
+ {
+ ushort low = readNextUShort(cache);
+ byte high = 0;
+ if ((low & 0x8000) != 0)
+ {
+ low &= 0x7FFF;
+ high = readNextByte(cache);
+ }
+ return ((high & 0xFF) << 15) | low;
+ }
+
+ ///
+ /// Read a single byte from a cache of bytes and remove it from the cache
+ ///
+ /// The byte that was read
+
+ private static byte readNextByte(List cache)
+ {
+ byte result = cache[0];
+ cache.RemoveAt(0);
+ return result;
+ }
+
///
/// Build an integer for sending over the network
///
@@ -360,6 +912,49 @@ namespace MinecraftClient.Protocol.Handlers
return bytes.ToArray();
}
+ ///
+ /// Get byte array representing a double
+ ///
+ /// Array to process
+ /// Array ready to send
+
+ private byte[] getDouble(double number)
+ {
+ byte[] theDouble = BitConverter.GetBytes(number);
+ Array.Reverse(theDouble); //Endianness
+ return theDouble;
+ }
+
+ ///
+ /// Get byte array with length information prepended to it
+ ///
+ /// Array to process
+ /// Array ready to send
+
+ private byte[] getArray(byte[] array)
+ {
+ if (protocolversion < MC18Version)
+ {
+ byte[] length = BitConverter.GetBytes((short)array.Length);
+ Array.Reverse(length);
+ return concatBytes(length, array);
+ }
+ else return concatBytes(getVarInt(array.Length), array);
+ }
+
+ ///
+ /// Get a byte array from the given string for sending over the network, with length information prepended.
+ ///
+ /// String to process
+ /// Array ready to send
+
+ private byte[] getString(string text)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(text);
+
+ return concatBytes(getVarInt(bytes.Length), bytes);
+ }
+
///
/// Easily append several byte arrays
///
@@ -402,16 +997,27 @@ namespace MinecraftClient.Protocol.Handlers
}
}
+ ///
+ /// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically
+ ///
+ /// Discriminator to use.
+ /// packet Data
+
+ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, byte[] data)
+ {
+ SendPluginChannelPacket("FML|HS", concatBytes(new byte[] { (byte)discriminator }, data));
+ }
+
///
/// Send a packet to the server, compression and encryption will be handled automatically
///
/// packet ID
/// packet Data
- private void SendPacket(int packetID, byte[] packetData)
+ private void SendPacket(int packetID, IEnumerable packetData)
{
//The inner packet
- byte[] the_packet = concatBytes(getVarInt(packetID), packetData);
+ byte[] the_packet = concatBytes(getVarInt(packetID), packetData.ToArray());
if (compression_treshold > 0) //Compression enabled?
{
@@ -429,7 +1035,7 @@ namespace MinecraftClient.Protocol.Handlers
}
}
- SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet));
+ SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet));
}
///
@@ -454,41 +1060,51 @@ namespace MinecraftClient.Protocol.Handlers
public bool Login()
{
byte[] protocol_version = getVarInt(protocolversion);
- byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.getServerHost());
+ byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost() + (forgeInfo != null ? "\0FML\0" : ""));
byte[] server_adress_len = getVarInt(server_adress_val.Length);
- byte[] server_port = BitConverter.GetBytes((ushort)handler.getServerPort()); Array.Reverse(server_port);
+ byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port);
byte[] next_state = getVarInt(2);
- byte[] handshake_packet = concatBytes( protocol_version, server_adress_len, server_adress_val, server_port, next_state);
+ byte[] handshake_packet = concatBytes(protocol_version, server_adress_len, server_adress_val, server_port, next_state);
SendPacket(0x00, handshake_packet);
- byte[] username_val = Encoding.UTF8.GetBytes(handler.getUsername());
+ byte[] username_val = Encoding.UTF8.GetBytes(handler.GetUsername());
byte[] username_len = getVarInt(username_val.Length);
byte[] login_packet = concatBytes(username_len, username_val);
SendPacket(0x00, login_packet);
int packetID = -1;
- byte[] packetData = new byte[] { };
+ List packetData = new List();
while (true)
{
- readNextPacket(ref packetID, ref packetData);
+ readNextPacket(ref packetID, packetData);
if (packetID == 0x00) //Login rejected
{
- handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData)));
+ handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData)));
return false;
}
else if (packetID == 0x01) //Encryption request
{
- string serverID = readNextString(ref packetData);
- byte[] Serverkey = readNextByteArray(ref packetData);
- byte[] token = readNextByteArray(ref packetData);
- return StartEncryption(handler.getUserUUID(), handler.getSessionID(), token, serverID, Serverkey);
+ string serverID = readNextString(packetData);
+ byte[] Serverkey = readNextByteArray(packetData);
+ byte[] token = readNextByteArray(packetData);
+ return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey);
}
else if (packetID == 0x02) //Login successful
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
login_phase = false;
+
+ if (forgeInfo != null)
+ {
+ // Do the forge handshake.
+ if (!CompleteForgeHandshake())
+ {
+ return false;
+ }
+ }
+
StartUpdating();
return true; //No need to check session or start encryption
}
@@ -496,6 +1112,33 @@ namespace MinecraftClient.Protocol.Handlers
}
}
+ ///
+ /// Completes the Minecraft Forge handshake.
+ ///
+ /// Whether the handshake was successful.
+ private bool CompleteForgeHandshake()
+ {
+ int packetID = -1;
+ List packetData = new List();
+
+ while (fmlHandshakeState != FMLHandshakeClientState.DONE)
+ {
+ readNextPacket(ref packetID, packetData);
+
+ if (packetID == 0x40) // Disconnect
+ {
+ handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData)));
+ return false;
+ }
+ else
+ {
+ handlePacket(packetID, packetData);
+ }
+ }
+
+ return true;
+ }
+
///
/// Start network encryption. Automatically called by Login() if the server requests encryption.
///
@@ -506,7 +1149,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 != "-")
{
@@ -519,32 +1163,40 @@ namespace MinecraftClient.Protocol.Handlers
}
//Encrypt the data
- byte[] key_enc = RSAService.Encrypt(secretKey, false);
- byte[] token_enc = RSAService.Encrypt(token, false);
- byte[] key_len = getVarInt(key_enc.Length);
- byte[] token_len = getVarInt(token_enc.Length);
+ byte[] key_enc = getArray(RSAService.Encrypt(secretKey, false));
+ byte[] token_enc = getArray(RSAService.Encrypt(token, false));
//Encryption Response packet
- SendPacket(0x01, concatBytes(key_len, key_enc, token_len, token_enc));
+ SendPacket(0x01, concatBytes(key_enc, token_enc));
//Start client-side encryption
- s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
+ s = CryptoHandler.getAesStream(c.GetStream(), secretKey);
encrypted = true;
//Process the next packet
int packetID = -1;
- byte[] packetData = new byte[] { };
+ List packetData = new List();
while (true)
{
- readNextPacket(ref packetID, ref packetData);
+ readNextPacket(ref packetID, packetData);
if (packetID == 0x00) //Login rejected
{
- handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData)));
+ handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData)));
return false;
}
else if (packetID == 0x02) //Login successful
{
login_phase = false;
+
+ if (forgeInfo != null)
+ {
+ // Do the forge handshake.
+ if (!CompleteForgeHandshake())
+ {
+ return false;
+ }
+ }
+
StartUpdating();
return true;
}
@@ -552,25 +1204,6 @@ namespace MinecraftClient.Protocol.Handlers
}
}
- ///
- /// Useless padding packet for solving Mono issue.
- ///
- /// The padding packet
-
- public byte[] getPaddingPacket()
- {
- //Will generate a 15-bytes long padding packet
- byte[] compression = compression_treshold >= 0 ? getVarInt(0) : new byte[] { };
- byte[] id = getVarInt(0x17); //Plugin Message
- byte[] channel_name = Encoding.UTF8.GetBytes("MCC|Pad");
- byte[] channel_name_len = getVarInt(channel_name.Length);
- byte[] data = compression_treshold >= 0 ? new byte[] { 0x00, 0x00, 0x00 } : new byte[] { 0x00, 0x00, 0x00, 0x00 };
- byte[] data_len = getVarInt(data.Length);
- byte[] packet_data = concatBytes(compression, id, channel_name_len, channel_name, data_len, data);
- byte[] packet_length = getVarInt(packet_data.Length);
- return concatBytes(packet_length, packet_data);
- }
-
///
/// Send a chat message to the server
///
@@ -586,7 +1219,7 @@ namespace MinecraftClient.Protocol.Handlers
byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length);
byte[] message_packet = concatBytes(message_len, message_val);
- SendPacket(0x01, message_packet);
+ SendPacket(protocolversion >= MC19Version ? 0x02 : 0x01, message_packet);
return true;
}
catch (SocketException) { return false; }
@@ -603,25 +1236,88 @@ namespace MinecraftClient.Protocol.Handlers
{
try
{
- SendPacket(0x16, new byte[] { 0 });
+ SendPacket(protocolversion >= MC19Version ? 0x03 : 0x16, new byte[] { 0 });
return true;
}
catch (SocketException) { return false; }
}
+ ///
+ /// Tell the server what client is being used to connect to the server
+ ///
+ /// Client string describing the client
+ /// True if brand info was successfully sent
+
+ public bool SendBrandInfo(string brandInfo)
+ {
+ if (String.IsNullOrEmpty(brandInfo))
+ return false;
+
+ return SendPluginChannelPacket("MC|Brand", getString(brandInfo));
+ }
+
+ ///
+ /// Send a location update to the server
+ ///
+ /// The new location of the player
+ /// True if the player is on the ground
+ /// True if the location update was successfully sent
+
+ public bool SendLocationUpdate(Location location, bool onGround)
+ {
+ if (Settings.TerrainAndMovements)
+ {
+ try
+ {
+ SendPacket(protocolversion >= MC19Version ? 0x0C : 0x04, concatBytes(
+ getDouble(location.X), getDouble(location.Y), getDouble(location.Z),
+ new byte[] { onGround ? (byte)1 : (byte)0 }));
+ return true;
+ }
+ catch (SocketException) { return false; }
+ }
+ else return false;
+ }
+
+ ///
+ /// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically
+ ///
+ /// Channel to send packet on
+ /// packet Data
+
+ public bool SendPluginChannelPacket(string channel, byte[] data)
+ {
+ try
+ {
+ // In 1.7, length needs to be included.
+ // In 1.8, it must not be.
+ if (protocolversion < MC18Version)
+ {
+ byte[] length = BitConverter.GetBytes((short)data.Length);
+ Array.Reverse(length);
+
+ SendPacket(0x17, concatBytes(getString(channel), length, data));
+ }
+ else
+ {
+ SendPacket(protocolversion >= MC19Version ? 0x09 : 0x17, concatBytes(getString(channel), data));
+ }
+
+ return true;
+ }
+ catch (SocketException) { return false; }
+ catch (System.IO.IOException) { return false; }
+ }
+
///
/// Disconnect from the server
///
- /// Optional disconnect reason
public void Disconnect()
{
try
{
- byte[] message_val = Encoding.UTF8.GetBytes("\"disconnect.quitting\"");
- byte[] message_len = getVarInt(message_val.Length);
- byte[] disconnect_packet = concatBytes(message_len, message_val);
- SendPacket(0x40, disconnect_packet);
+ c.Close();
}
catch (SocketException) { }
catch (System.IO.IOException) { }
@@ -642,16 +1338,110 @@ namespace MinecraftClient.Protocol.Handlers
byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor);
byte[] tocomplete_len = getVarInt(tocomplete_val.Length);
- byte[] has_position = new byte[] { 0x00 }; //false, no position sent
- byte[] tabcomplete_packet = concatBytes(tocomplete_len, tocomplete_val, has_position);
+ byte[] assume_command = new byte[] { 0x00 };
+ byte[] has_position = new byte[] { 0x00 };
+ byte[] tabcomplete_packet = protocolversion >= MC18Version
+ ? protocolversion >= MC19Version
+ ? concatBytes(tocomplete_len, tocomplete_val, assume_command, has_position)
+ : concatBytes(tocomplete_len, tocomplete_val, has_position)
+ : concatBytes(tocomplete_len, tocomplete_val);
autocomplete_received = false;
autocomplete_result = BehindCursor;
- SendPacket(0x14, tabcomplete_packet);
+ SendPacket(protocolversion >= MC19Version ? 0x01 : 0x14, tabcomplete_packet);
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;
}
+
+ ///
+ /// Ping a Minecraft server to get information about the server
+ ///
+ /// True if ping was successful
+
+ public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo)
+ {
+ 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);
+
+ Protocol18Handler ComTmp = new Protocol18Handler(tcp);
+ int packetLength = ComTmp.readNextVarIntRAW();
+ if (packetLength > 0) //Read Response length
+ {
+ List packetData = new List(ComTmp.readDataRAW(packetLength));
+ if (readNextVarInt(packetData) == 0x00) //Read Packet ID
+ {
+ string result = readNextString(packetData); //Get the Json data
+
+ if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}"))
+ {
+ Json.JSONData jsonData = Json.ParseJson(result);
+ if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version"))
+ {
+ Json.JSONData versionData = jsonData.Properties["version"];
+
+ //Retrieve display name of the Minecraft version
+ if (versionData.Properties.ContainsKey("name"))
+ version = versionData.Properties["name"].StringValue;
+
+ //Retrieve protocol version number for handling this server
+ if (versionData.Properties.ContainsKey("protocol"))
+ protocolversion = atoi(versionData.Properties["protocol"].StringValue);
+
+ //Automatic fix for BungeeCord 1.8 reporting itself as 1.7...
+ if (protocolversion < 47 && version.Split(' ', '/').Contains("1.8"))
+ protocolversion = ProtocolHandler.MCVer2ProtocolVersion("1.8.0");
+
+ // Check for forge on the server.
+ if (jsonData.Properties.ContainsKey("modinfo") && jsonData.Properties["modinfo"].Type == Json.JSONData.DataType.Object)
+ {
+ Json.JSONData modData = jsonData.Properties["modinfo"];
+ if (modData.Properties.ContainsKey("type") && modData.Properties["type"].StringValue == "FML")
+ {
+ forgeInfo = new ForgeInfo(modData);
+
+ if (forgeInfo.Mods.Any())
+ {
+ if (Settings.DebugMessages)
+ {
+ ConsoleIO.WriteLineFormatted("§8Server is running Forge. Mod list:");
+ foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods)
+ {
+ ConsoleIO.WriteLineFormatted("§8 " + mod.ToString());
+ }
+ }
+ else ConsoleIO.WriteLineFormatted("§8Server is running Forge.");
+ }
+ else forgeInfo = null;
+ }
+ }
+
+ ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ")."));
+
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
}
}
diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs
index d35a1fd7..927258fd 100644
--- a/MinecraftClient/Protocol/IMinecraftCom.cs
+++ b/MinecraftClient/Protocol/IMinecraftCom.cs
@@ -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.
///
- public interface IMinecraftCom : IDisposable, IAutoComplete, IPaddingProvider
+ public interface IMinecraftCom : IDisposable, IAutoComplete
{
///
/// Start the login procedure once connected to the server
@@ -43,5 +44,31 @@ namespace MinecraftClient.Protocol
/// True if packet successfully sent
bool SendRespawnPacket();
+
+ ///
+ /// Inform the server of the client being used to connect
+ ///
+ /// Client string describing the client
+ /// True if brand info was successfully sent
+
+ bool SendBrandInfo(string brandInfo);
+
+ ///
+ /// Send a location update telling that we moved to that location
+ ///
+ /// The new location
+ /// True if packet was successfully sent
+
+ bool SendLocationUpdate(Location location, bool onGround);
+
+ ///
+ /// Send a plugin channel packet to the server.
+ ///
+ ///
+ /// Channel to send packet on
+ /// packet Data
+ /// True if message was successfully sent
+
+ bool SendPluginChannelPacket(string channel, byte[] data);
}
}
diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs
index f74d22a4..dae4b516 100644
--- a/MinecraftClient/Protocol/IMinecraftComHandler.cs
+++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs
@@ -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();
+
+ ///
+ /// Called when a server was successfully joined
+ ///
+
+ void OnGameJoined();
///
/// This method is called when the protocol handler receives a chat message
@@ -44,6 +53,13 @@ namespace MinecraftClient.Protocol
void OnPlayerLeave(Guid uuid);
+ ///
+ /// Called when the server sets the new location for the player
+ ///
+ /// New location of the player
+
+ void UpdateLocation(Location location);
+
///
/// This method is called when the connection has been lost
///
@@ -56,5 +72,40 @@ namespace MinecraftClient.Protocol
///
void OnUpdate();
+
+ ///
+ /// Registers the given plugin channel for the given bot.
+ ///
+ /// The channel to register.
+ /// The bot to register the channel for.
+
+ void RegisterPluginChannel(string channel, ChatBot bot);
+
+ ///
+ /// Unregisters the given plugin channel for the given bot.
+ ///
+ /// The channel to unregister.
+ /// The bot to unregister the channel for.
+
+ void UnregisterPluginChannel(string channel, ChatBot bot);
+
+ ///
+ /// Sends a plugin channel packet to the server.
+ /// See http://wiki.vg/Plugin_channel for more information about plugin channels.
+ ///
+ /// The channel to send the packet on.
+ /// The payload for the packet.
+ /// Whether the packet should be sent even if the server or the client hasn't registered it yet.
+ /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered.
+
+ bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false);
+
+ ///
+ /// Called when a plugin channel message was sent from the server.
+ ///
+ /// The channel the message was sent on
+ /// The data from the channel
+
+ void OnPluginChannelMessage(string channel, byte[] data);
}
}
diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs
index 08b309f3..894f1c00 100644
--- a/MinecraftClient/Protocol/ProtocolHandler.cs
+++ b/MinecraftClient/Protocol/ProtocolHandler.cs
@@ -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
-{
- ///
- /// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server.
- ///
-
- public static class ProtocolHandler
- {
- ///
- /// Retrieve information about a Minecraft server
- ///
- /// Server IP to ping
- /// Server Port to ping
- /// Will contain protocol version, if ping successful
- /// TRUE if ping was successful
-
- public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion)
- {
- 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;
- }
- }
-
- ///
- /// Get a protocol handler for the specified Minecraft version
- ///
- /// Tcp Client connected to the server
- /// Protocol version to handle
- /// Handler with the appropriate callbacks
- ///
-
- public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
- {
- int[] supportedVersions_Protocol16 = { 51, 60, 61, 72, 73, 74, 78 };
- if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1)
- return new Protocol16Handler(Client, ProtocolVersion, Handler);
- int[] supportedVersions_Protocol17 = { 4, 5 };
- if (Array.IndexOf(supportedVersions_Protocol17, ProtocolVersion) > -1)
- return new Protocol17Handler(Client, ProtocolVersion, Handler);
- int[] supportedVersions_Protocol18 = { 47 };
- if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
- return new Protocol18Handler(Client, ProtocolVersion, Handler);
- throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported.");
- }
-
- ///
- /// Convert a human-readable Minecraft version number to network protocol version number
- ///
- /// The Minecraft version number
- /// The protocol version number or 0 if could not determine protocol version: error, unknown, not supported
-
- public static int MCVer2ProtocolVersion(string MCVersion)
- {
- if (MCVersion.Contains('.'))
- {
- switch (MCVersion.Split(' ')[0].Trim())
- {
- case "1.4.6":
- case "1.4.7":
- return 51;
- case "1.5.1":
- return 60;
- case "1.5.2":
- return 61;
- case "1.6.0":
- return 72;
- case "1.6.1":
- case "1.6.2":
- case "1.6.3":
- case "1.6.4":
- return 73;
- case "1.7.2":
- case "1.7.3":
- case "1.7.4":
- case "1.7.5":
- return 4;
- case "1.7.6":
- case "1.7.7":
- case "1.7.8":
- case "1.7.9":
- case "1.7.10":
- return 5;
- case "1.8.0":
- case "1.8.1":
- return 47;
- default:
- return 0;
- }
- }
- else
- {
- try
- {
- return Int32.Parse(MCVersion);
- }
- catch
- {
- return -1;
- }
- }
- }
-
- public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium };
-
- ///
- /// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
- ///
- /// Login
- /// Password
- /// Will contain the access token returned by Minecraft.net, if the login is successful
- /// Will contain the player's UUID, needed for multiplayer
- /// Returns the status of the login (Success, Failure, etc.)
-
- public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string uuid)
- {
- try
- {
- string result = "";
- string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\" }";
- int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result);
- if (code == 200)
- {
- if (result.Contains("availableProfiles\":[]}"))
- {
- return LoginResult.NotPremium;
- }
- else
- {
- string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
- if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; }
- temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
- if (temp.Length >= 2) { user = temp[1].Split('"')[0]; }
- temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
- if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; }
- return LoginResult.Success;
- }
- }
- else if (code == 403)
- {
- if (result.Contains("UserMigratedException"))
- {
- return LoginResult.AccountMigrated;
- }
- else return LoginResult.WrongPassword;
- }
- else if (code == 503)
- {
- return LoginResult.ServiceUnavailable;
- }
- else
- {
- ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code);
- return LoginResult.OtherError;
- }
- }
- catch (System.Security.Authentication.AuthenticationException)
- {
- return LoginResult.SSLError;
- }
- catch (System.IO.IOException e)
- {
- if (e.Message.Contains("authentication"))
- {
- return LoginResult.SSLError;
- }
- else return LoginResult.OtherError;
- }
- catch
- {
- return LoginResult.OtherError;
- }
- }
-
- ///
- /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
- ///
- /// Username
- /// Session ID
- /// Server ID
- /// TRUE if session was successfully checked
-
- public static bool SessionCheck(string uuid, string accesstoken, string serverhash)
- {
- try
- {
- string result = "";
- string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}";
- int code = doHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request, ref result);
- return (result == "");
- }
- catch { return false; }
- }
-
- ///
- /// Manual HTTPS request since we must directly use a TcpClient because of the proxy.
- /// This method connects to the server, enables SSL, do the request and read the response.
- ///
- /// Host to connect to
- /// Endpoint for making the request
- /// Request payload
- /// Request result
- /// HTTP Status code
-
- private static int doHTTPSPost(string host, string endpoint, string request, ref string result)
- {
- string postResult = null;
- int statusCode = 520;
- AutoTimeout.Perform(() =>
- {
- TcpClient client = ProxyHandler.newTcpClient(host, 443);
- SslStream stream = new SslStream(client.GetStream());
- stream.AuthenticateAsClient(host);
-
- List http_request = new List();
- http_request.Add("POST " + endpoint + " HTTP/1.1");
- http_request.Add("Host: " + host);
- http_request.Add("User-Agent: MCC/" + Program.Version);
- http_request.Add("Content-Type: application/json");
- http_request.Add("Content-Length: " + Encoding.ASCII.GetBytes(request).Length);
- http_request.Add("Connection: close");
- http_request.Add("");
- http_request.Add(request);
-
- stream.Write(Encoding.ASCII.GetBytes(String.Join("\r\n", http_request.ToArray())));
- System.IO.StreamReader sr = new System.IO.StreamReader(stream);
- string raw_result = sr.ReadToEnd();
-
- if (raw_result.StartsWith("HTTP/1.1"))
- {
- postResult = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
- statusCode = Settings.str2int(raw_result.Split(' ')[1]);
- }
- else statusCode = 520; //Web server is returning an unknown error
- }, 15000);
- result = postResult;
- return statusCode;
- }
-
- ///
- /// Encode a string to a json string.
- /// Will convert special chars to \u0000 unicode escape sequences.
- ///
- /// Source text
- /// Encoded text
-
- private static string jsonEncode(string text)
- {
- StringBuilder result = new StringBuilder();
- foreach (char c in text)
- {
- if (char.IsLetterOrDigit(c))
- {
- result.Append(c);
- }
- else
- {
- result.Append("\\u");
- result.Append(((int)c).ToString("x4"));
- }
- }
- return result.ToString();
- }
- }
-}
+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
+{
+ ///
+ /// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server.
+ ///
+
+ public static class ProtocolHandler
+ {
+ ///
+ /// Retrieve information about a Minecraft server
+ ///
+ /// Server IP to ping
+ /// Server Port to ping
+ /// Will contain protocol version, if ping successful
+ /// TRUE if ping was successful
+
+ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion, 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;
+ }
+ }
+
+ ///
+ /// Get a protocol handler for the specified Minecraft version
+ ///
+ /// Tcp Client connected to the server
+ /// Protocol version to handle
+ /// Handler with the appropriate callbacks
+ ///
+
+ public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, 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.");
+ }
+
+ ///
+ /// Convert a human-readable Minecraft version number to network protocol version number
+ ///
+ /// The Minecraft version number
+ /// The protocol version number or 0 if could not determine protocol version: error, unknown, not supported
+
+ public static int MCVer2ProtocolVersion(string MCVersion)
+ {
+ if (MCVersion.Contains('.'))
+ {
+ switch (MCVersion.Split(' ')[0].Trim())
+ {
+ case "1.4.6":
+ case "1.4.7":
+ return 51;
+ case "1.5.1":
+ return 60;
+ case "1.5.2":
+ return 61;
+ case "1.6.0":
+ return 72;
+ case "1.6.1":
+ case "1.6.2":
+ case "1.6.3":
+ case "1.6.4":
+ return 73;
+ case "1.7.2":
+ case "1.7.3":
+ case "1.7.4":
+ case "1.7.5":
+ return 4;
+ case "1.7.6":
+ case "1.7.7":
+ case "1.7.8":
+ case "1.7.9":
+ case "1.7.10":
+ return 5;
+ case "1.8.0":
+ case "1.8.1":
+ case "1.8.2":
+ case "1.8.3":
+ case "1.8.4":
+ case "1.8.5":
+ case "1.8.6":
+ case "1.8.7":
+ 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 };
+
+ ///
+ /// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
+ ///
+ /// Login
+ /// Password
+ /// Will contain the access token returned by Minecraft.net, if the login is successful
+ /// Will contain the client token generated before sending to Minecraft.net
+ /// Will contain the player's PlayerID, needed for multiplayer
+ /// Returns the status of the login (Success, Failure, etc.)
+
+ 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;
+ }
+ }
+
+ ///
+ /// Validates whether accessToken must be refreshed
+ ///
+ /// Will contain the cached access token previously returned by Minecraft.net
+ /// Will contain the cached client token created on login
+ /// Returns the status of the token (Valid, Invalid, etc.)
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Refreshes invalid token
+ ///
+ /// Login
+ /// Will contain the new access token returned by Minecraft.net, if the refresh is successful
+ /// Will contain the client token generated before sending to Minecraft.net
+ /// Will contain the player's PlayerID, needed for multiplayer
+ /// Returns the status of the new token request (Success, Failure, etc.)
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
+ ///
+ /// Username
+ /// Session ID
+ /// Server ID
+ /// TRUE if session was successfully checked
+
+ public static bool SessionCheck(string uuid, string accesstoken, string serverhash)
+ {
+ try
+ {
+ string result = "";
+ string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}";
+ int code = doHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request, ref result);
+ return (result == "");
+ }
+ catch { return false; }
+ }
+
+ 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);
+ }
+
+ ///
+ /// Make a HTTPS GET request to the specified endpoint of the Mojang API
+ ///
+ /// Host to connect to
+ /// Endpoint for making the request
+ /// Cookies for making the request
+ /// Request result
+ /// HTTP Status code
+
+ private static int doHTTPSGet(string host, string endpoint, string cookies, ref string result)
+ {
+ List http_request = new List();
+ 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);
+ }
+
+ ///
+ /// Make a HTTPS POST request to the specified endpoint of the Mojang API
+ ///
+ /// Host to connect to
+ /// Endpoint for making the request
+ /// Request payload
+ /// Request result
+ /// HTTP Status code
+
+ private static int doHTTPSPost(string host, string endpoint, string request, ref string result)
+ {
+ List http_request = new List();
+ http_request.Add("POST " + endpoint + " HTTP/1.1");
+ http_request.Add("Host: " + host);
+ http_request.Add("User-Agent: MCC/" + Program.Version);
+ http_request.Add("Content-Type: application/json");
+ http_request.Add("Content-Length: " + Encoding.ASCII.GetBytes(request).Length);
+ http_request.Add("Connection: close");
+ http_request.Add("");
+ http_request.Add(request);
+ return doHTTPSRequest(http_request, host, ref result);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// Request headers and optional body (POST)
+ /// Host to connect to
+ /// Request result
+ /// HTTP Status code
+
+ private static int doHTTPSRequest(List 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;
+ }
+
+ ///
+ /// Encode a string to a json string.
+ /// Will convert special chars to \u0000 unicode escape sequences.
+ ///
+ /// Source text
+ /// Encoded text
+
+ private static string jsonEncode(string text)
+ {
+ StringBuilder result = new StringBuilder();
+ foreach (char c in text)
+ {
+ if ((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();
+ }
+ }
+}
diff --git a/MinecraftClient/Protocol/SessionCache/CacheType.cs b/MinecraftClient/Protocol/SessionCache/CacheType.cs
new file mode 100644
index 00000000..886d61f9
--- /dev/null
+++ b/MinecraftClient/Protocol/SessionCache/CacheType.cs
@@ -0,0 +1,4 @@
+namespace MinecraftClient.Protocol.SessionCache
+{
+ public enum CacheType { None, Memory, Disk };
+}
diff --git a/MinecraftClient/Protocol/SessionCache/SessionCache.cs b/MinecraftClient/Protocol/SessionCache/SessionCache.cs
new file mode 100644
index 00000000..7a03fa9d
--- /dev/null
+++ b/MinecraftClient/Protocol/SessionCache/SessionCache.cs
@@ -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
+{
+ ///
+ /// Handle sessions caching and storage.
+ ///
+
+ public static class SessionCache
+ {
+ private const string SessionCacheFile = "SessionCache.db";
+
+ private static Dictionary sessions = new Dictionary();
+ private static FileSystemWatcher cachemonitor = new FileSystemWatcher();
+ private static Timer updatetimer = new Timer(100);
+ private static List> pendingadds = new List>();
+
+ private static BinaryFormatter formatter = new BinaryFormatter();
+
+ ///
+ /// Retrieve whether SessionCache contains a session for the given login.
+ ///
+ /// User login used with Minecraft.net
+ /// TRUE if session is available
+
+ public static bool Contains(string login)
+ {
+ return sessions.ContainsKey(login);
+ }
+
+ ///
+ /// Store a session and save it to disk if required.
+ ///
+ /// User login used with Minecraft.net
+ /// User session token used with Minecraft.net
+
+ 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(login, session));
+ }
+ else if (Settings.SessionCaching == CacheType.Disk)
+ {
+ SaveToDisk();
+ }
+ }
+
+ ///
+ /// Retrieve a session token for the given login.
+ ///
+ /// User login used with Minecraft.net
+ /// SessionToken for given login
+
+ public static SessionToken Get(string login)
+ {
+ return sessions[login];
+ }
+
+ ///
+ /// Initialize cache monitoring to keep cache updated with external changes.
+ ///
+ /// TRUE if session tokens are seeded from file
+
+ 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();
+ }
+
+ ///
+ /// Reloads cache on external cache file change.
+ ///
+ /// Sender
+ /// Event data
+
+ private static void OnChanged(object sender, FileSystemEventArgs e)
+ {
+ updatetimer.Stop();
+ updatetimer.Start();
+ }
+
+ ///
+ /// Called after timer elapsed. Reads disk cache and adds new/modified sessions back.
+ ///
+ /// Sender
+ /// Event data
+
+ private static void HandlePending(object sender, ElapsedEventArgs e)
+ {
+ LoadFromDisk();
+
+ foreach(KeyValuePair pending in pendingadds.ToArray())
+ {
+ Store(pending.Key, pending.Value);
+ pendingadds.Remove(pending);
+ }
+ }
+
+ ///
+ /// Reads cache file and loads SessionTokens into SessionCache.
+ ///
+ /// True if data is successfully loaded
+
+ private static bool LoadFromDisk()
+ {
+ if (File.Exists(SessionCacheFile))
+ {
+ try
+ {
+ using (FileStream fs = new FileStream(SessionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ sessions = (Dictionary)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;
+ }
+
+ ///
+ /// Saves SessionToken's from SessionCache into cache file.
+ ///
+
+ 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;
+ }
+
+ }
+ }
+}
diff --git a/MinecraftClient/Protocol/SessionToken.cs b/MinecraftClient/Protocol/SessionToken.cs
new file mode 100644
index 00000000..d00824f9
--- /dev/null
+++ b/MinecraftClient/Protocol/SessionToken.cs
@@ -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;
+ }
+ }
+}
diff --git a/MinecraftClient/Proxy/ProxyHandler.cs b/MinecraftClient/Proxy/ProxyHandler.cs
index 6ac63582..a017e323 100644
--- a/MinecraftClient/Proxy/ProxyHandler.cs
+++ b/MinecraftClient/Proxy/ProxyHandler.cs
@@ -24,12 +24,15 @@ namespace MinecraftClient.Proxy
///
/// Create a regular TcpClient or a proxied TcpClient according to the app Settings.
///
+ /// Target host
+ /// Target port
+ /// True if the purpose is logging in to a Minecraft account
- 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;
}
}
}
diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs
index 09e508f1..679b86bf 100644
--- a/MinecraftClient/Settings.cs
+++ b/MinecraftClient/Settings.cs
@@ -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 Bots_Owners = new List();
+ 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 AppVars = new Dictionary();
- private static Dictionary> Accounts = new Dictionary>();
- private static Dictionary> Servers = new Dictionary>();
+ //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 AppVars = new Dictionary();
+ private static readonly Dictionary> Accounts = new Dictionary>();
+ private static readonly Dictionary> Servers = new Dictionary>();
+
+ private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, ChatFormat, AutoRespond };
///
/// 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(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"; }
+ ///
+ /// Convert the specified string to an integer, defaulting to zero if invalid argument
+ ///
+ /// String to parse as an integer
+ /// Integer value
+
+ public static int str2int(string str)
+ {
+ try
+ {
+ return Convert.ToInt32(str);
+ }
+ catch { return 0; }
+ }
+
+ ///
+ /// Convert the specified string to a boolean value, defaulting to false if invalid argument
+ ///
+ /// String to parse as a boolean
+ /// Boolean value
+
+ public static bool str2bool(string str)
+ {
+ if (String.IsNullOrEmpty(str))
+ return false;
+ str = str.Trim().ToLowerInvariant();
+ return str == "true" || str == "1";
+ }
///
/// Load login/password using an account alias
///
/// True if the account was found and loaded
- 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
///
/// True if the server IP was valid and loaded, false otherwise
- 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
/// Value of the variable
/// True if the parameters were valid
- 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;
+ }
+
+ ///
+ /// Get a custom %variable% or null if the variable does not exist
+ ///
+ /// Variable name
+ /// The value or null if the variable does not exists
+
+ public static object GetVar(string varName)
+ {
+ if (AppVars.ContainsKey(varName))
+ return AppVars[varName];
+ return null;
}
///
@@ -486,7 +623,7 @@ namespace MinecraftClient
/// String to parse
/// Modifier string
- 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;
diff --git a/MinecraftClient/config/README.txt b/MinecraftClient/config/README.txt
index a6a705e3..04e460ae 100644
--- a/MinecraftClient/config/README.txt
+++ b/MinecraftClient/config/README.txt
@@ -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 send 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);
+}
\ No newline at end of file
diff --git a/MinecraftClient/config/sample-script-with-chatbot.cs b/MinecraftClient/config/sample-script-with-chatbot.cs
new file mode 100644
index 00000000..25cb13eb
--- /dev/null
+++ b/MinecraftClient/config/sample-script-with-chatbot.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/config/sample-script-with-world-access.cs b/MinecraftClient/config/sample-script-with-world-access.cs
new file mode 100644
index 00000000..cfa5f915
--- /dev/null
+++ b/MinecraftClient/config/sample-script-with-world-access.cs
@@ -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++;
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/config/sample-script.cs b/MinecraftClient/config/sample-script.cs
new file mode 100644
index 00000000..34d3354a
--- /dev/null
+++ b/MinecraftClient/config/sample-script.cs
@@ -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);
+}
\ No newline at end of file