From e3c38ed6acdd8e795aa565334af7dc3713f9667e Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 11 Mar 2015 19:58:28 +0100 Subject: [PATCH 001/102] Update version info for 1.8.2 release --- MinecraftClient/Program.cs | 4 ++-- MinecraftClient/Protocol/ProtocolHandler.cs | 2 ++ MinecraftClient/config/README.txt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index c65a6ab371..4ad3ae2711 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -18,7 +18,7 @@ static class Program { private static McTcpClient Client; public static string[] startupargs; - public const string Version = "1.8.0"; + public const string Version = "1.8.2"; private static Thread offlinePrompt = null; /// @@ -27,7 +27,7 @@ static class Program static void Main(string[] args) { - Console.WriteLine("Console Client for MC 1.4.6 to 1.8.1 - v" + Version + " - By ORelio & Contributors"); + Console.WriteLine("Console Client for MC 1.4.6 to 1.8.3 - v" + Version + " - By ORelio & Contributors"); //Basic Input/Output ? if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 08b309f353..cad6036554 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -109,6 +109,8 @@ public static int MCVer2ProtocolVersion(string MCVersion) return 5; case "1.8.0": case "1.8.1": + case "1.8.2": + case "1.8.3": return 47; default: return 0; diff --git a/MinecraftClient/config/README.txt b/MinecraftClient/config/README.txt index a6a705e389..9811ca2801 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! From 858ad12783f3d2626d2a483c79866b7df4f2820f Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 19 Mar 2015 22:08:26 +0100 Subject: [PATCH 002/102] Fix concurrency crashes for player list Bug report by doranchak (forum post no 1136) --- MinecraftClient/McTcpClient.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 2ba5c7d5c1..781b61d6ec 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -20,7 +20,7 @@ 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 readonly 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)); } @@ -412,7 +412,10 @@ public bool SendRespawnPacket() public void OnPlayerJoin(Guid uuid, string name) { - onlinePlayers[uuid] = name; + lock (onlinePlayers) + { + onlinePlayers[uuid] = name; + } } /// @@ -422,7 +425,10 @@ public void OnPlayerJoin(Guid uuid, string name) public void OnPlayerLeave(Guid uuid) { - onlinePlayers.Remove(uuid); + lock (onlinePlayers) + { + onlinePlayers.Remove(uuid); + } } /// @@ -432,7 +438,10 @@ public void OnPlayerLeave(Guid uuid) public string[] getOnlinePlayers() { - return onlinePlayers.Values.Distinct().ToArray(); + lock (onlinePlayers) + { + return onlinePlayers.Values.Distinct().ToArray(); + } } } } From c30d3025f746a857cf0278819da466157cfabf09 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 19 Mar 2015 22:16:42 +0100 Subject: [PATCH 003/102] Use BouncyCastle for handling AES on Mono Framework Mono Framework does not handle CFB-8 AES encryption mode. So now MCC will now use borrowed code from the BouncyCastle project for handling AES when running on Mono framework, instead of using a dirty workaround to try getting Mono encryption working. Regular .NET framework encryption module will still be used when not running under Mono (eg on Windows or using Wine) Should hopefully fix all the issues encountered on Mono including #41 and finally achieve full compatibility of MCC with Mac and Linux. --- .../Crypto/Streams/BouncyAes/AesFastEngine.cs | 855 ++++++++++++++++++ .../Streams/BouncyAes/BufferedBlockCipher.cs | 367 ++++++++ .../Streams/BouncyAes/BufferedCipherBase.cs | 113 +++ .../Streams/BouncyAes/CfbBlockCipher.cs | 224 +++++ .../Crypto/Streams/BouncyAes/Check.cs | 25 + .../Crypto/Streams/BouncyAes/CipherStream.cs | 234 +++++ .../Streams/BouncyAes/CryptoException.cs | 28 + .../Streams/BouncyAes/DataLengthException.cs | 42 + .../Crypto/Streams/BouncyAes/IBlockCipher.cs | 36 + .../Streams/BouncyAes/IBufferedCipher.cs | 44 + .../Streams/BouncyAes/ICipherParameters.cs | 11 + .../Crypto/Streams/BouncyAes/KeyParameter.cs | 43 + .../BouncyAes/OutputLengthException.cs | 28 + .../Crypto/Streams/BouncyAes/Pack.cs | 266 ++++++ .../Streams/BouncyAes/ParametersWithIV.cs | 43 + .../Crypto/Streams/MonoAesStream.cs | 69 +- MinecraftClient/MinecraftClient.csproj | 15 + 17 files changed, 2389 insertions(+), 54 deletions(-) create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/BufferedBlockCipher.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/Check.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs create mode 100644 MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs b/MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs new file mode 100644 index 0000000000..a1b544568a --- /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 0000000000..3dc21560f9 --- /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 0000000000..9d8610211d --- /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 0000000000..433716535d --- /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 0000000000..96a05c64be --- /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 0000000000..b6920854d3 --- /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 0000000000..67f0d86f2f --- /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 0000000000..e9efc0bd39 --- /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 0000000000..a3ad6d6e5f --- /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 0000000000..69dec9596c --- /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 0000000000..fff0941c7e --- /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 0000000000..33dff96d78 --- /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 0000000000..e1cf44925f --- /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 0000000000..087cb7ceaf --- /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 0000000000..11a8b77a0f --- /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 b7d2875fd4..430483b6ab 100644 --- a/MinecraftClient/Crypto/Streams/MonoAesStream.cs +++ b/MinecraftClient/Crypto/Streams/MonoAesStream.cs @@ -4,28 +4,29 @@ 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(); + CipherStream cstream; public MonoAesStream(System.IO.Stream stream, byte[] key, IPaddingProvider provider) { BaseStream = stream; - RijndaelManaged aes = GenerateAES(key); - enc = aes.CreateEncryptor(); - dec = aes.CreateDecryptor(); + BufferedBlockCipher enc = GenerateAES(key, true); + BufferedBlockCipher dec = GenerateAES(key, false); + cstream = new CipherStream(stream, dec, enc); pad = provider; } public System.IO.Stream BaseStream { get; set; } @@ -76,25 +77,7 @@ public override int ReadByte() 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 +97,13 @@ public override void WriteByte(byte b) 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/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index c174cb1ebd..afbcb28b95 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -94,6 +94,21 @@ + + + + + + + + + + + + + + + From aaced855d8e0395d7d756f7611379141b1087a78 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 23 Mar 2015 13:57:31 +0100 Subject: [PATCH 004/102] Catch exceptions for bots for onTextReceived Avoid crashing due to bots not properly processing text --- MinecraftClient/McTcpClient.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 781b61d6ec..8716ffe333 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -305,8 +305,21 @@ public void Disconnect() 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 + } + } } /// @@ -357,7 +370,7 @@ public void OnUpdate() { if (!(e is ThreadAbortException)) { - ConsoleIO.WriteLineFormatted("§8Got error from " + bots[i].ToString() + ": " + e.ToString()); + ConsoleIO.WriteLineFormatted("§8Update: Got error from " + bots[i].ToString() + ": " + e.ToString()); } else throw; //ThreadAbortException should not be caught } From 82c95be611a68d1e14813651140a01d32220c6d4 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 25 Mar 2015 19:45:50 +0100 Subject: [PATCH 005/102] Fix console background color - Save & Restore background color when needed - Remove useless color modifications - Fix issue #71 --- MinecraftClient/ChatBots/Alerts.cs | 9 ++++++--- MinecraftClient/ConsoleIO.cs | 6 ------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/MinecraftClient/ChatBots/Alerts.cs b/MinecraftClient/ChatBots/Alerts.cs index b60a39ee80..5cdff08841 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -67,10 +67,13 @@ public override void GetText(string text) if (ConsoleIO.basicIO) //Using a GUI? Pass text as is. ConsoleIO.WriteLine(text.Replace(alert, "§c" + alert + "§r")); - else //Using Consome Prompt : Print text with alert highlighted + else //Using Console Prompt : Print text with alert highlighted { string[] splitted = text.Split(new string[] { alert }, StringSplitOptions.None); + ConsoleColor fore = Console.ForegroundColor; + ConsoleColor back = Console.BackgroundColor; + if (splitted.Length > 0) { Console.BackgroundColor = ConsoleColor.DarkGray; @@ -89,8 +92,8 @@ public override void GetText(string text) } } - Console.BackgroundColor = ConsoleColor.Black; - Console.ForegroundColor = ConsoleColor.Gray; + Console.BackgroundColor = back; + Console.ForegroundColor = fore; ConsoleIO.Write('\n'); } } diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index f73514a558..ac627c288d 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -193,8 +193,6 @@ public static void Write(string text) writing_lock = true; if (reading) { - ConsoleColor fore = Console.ForegroundColor; - ConsoleColor back = Console.BackgroundColor; string buf = buffer; string buf2 = buffer2; ClearLineAndBuffer(); @@ -208,8 +206,6 @@ public static void Write(string text) } else Console.Write("\b \b"); Console.Write(text); - Console.ForegroundColor = ConsoleColor.Gray; - Console.BackgroundColor = ConsoleColor.Black; buffer = buf; buffer2 = buf2; Console.Write(">" + buffer); @@ -218,8 +214,6 @@ public static void Write(string text) Console.Write(buffer2 + " \b"); for (int i = 0; i < buffer2.Length; i++) { GoBack(); } } - Console.ForegroundColor = fore; - Console.BackgroundColor = back; } else Console.Write(text); writing_lock = false; From 7757d5ae0357344e6a9f3a9c20df7840e6b98d7d Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 25 Mar 2015 22:14:38 +0100 Subject: [PATCH 006/102] Upgrade login/session timeout to 30 seconds Might help when login/session servers take a long time to respond. --- MinecraftClient/Protocol/ProtocolHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index cad6036554..10a0aa02e2 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -260,7 +260,7 @@ private static int doHTTPSPost(string host, string endpoint, string request, ref statusCode = Settings.str2int(raw_result.Split(' ')[1]); } else statusCode = 520; //Web server is returning an unknown error - }, 15000); + }, 30000); result = postResult; return statusCode; } From 05a141c50de2c4faf5f17713359ad92ff09093fe Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 25 Mar 2015 22:50:20 +0100 Subject: [PATCH 007/102] Improve offline interactions - Add prompt for Minecraft version - Improve offline-mode command prompt - Fix default value on parse error in protocol handler - Fix failed to connect not showing offline prompt --- MinecraftClient/McTcpClient.cs | 5 +- MinecraftClient/Program.cs | 97 +++++++++++++++------ MinecraftClient/Protocol/ProtocolHandler.cs | 2 +- MinecraftClient/Settings.cs | 4 +- 4 files changed, 78 insertions(+), 30 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 8716ffe333..ebf99e754e 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -149,7 +149,10 @@ private void StartClient(string user, string uuid, string sessionID, string serv ChatBot.LogToConsole("Waiting 5 seconds (" + AttemptsLeft + " attempts left)..."); Thread.Sleep(5000); AttemptsLeft--; Program.Restart(); } - else if (!singlecommand) { Console.ReadLine(); } + else if (!singlecommand && Settings.interactiveMode) + { + Program.OfflineCommandPrompt(); + } } } diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 4ad3ae2711..1cfdfa60fa 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -20,6 +20,7 @@ static class Program public static string[] startupargs; public const string Version = "1.8.2"; private static Thread offlinePrompt = null; + private static bool useMcVersionOnce = false; /// /// The main entry point of Minecraft Console Client @@ -148,11 +149,18 @@ private static void InitializeClient() 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) @@ -166,7 +174,18 @@ private static void InitializeClient() 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(); + else + { + if (Settings.interactiveMode) + { + if (MinecraftVersionPrompt()) + { + Restart(); + return; + } + OfflineCommandPrompt(); + } + } return; } } @@ -191,7 +210,11 @@ private static void InitializeClient() else { Console.WriteLine("Failed to determine server version."); - OfflineCommandPrompt(); + if (Settings.interactiveMode && MinecraftVersionPrompt()) + { + Restart(); + return; + } } } else @@ -256,7 +279,7 @@ public static void Exit() public static void OfflineCommandPrompt() { - if (!Settings.exitOnFailure && offlinePrompt == null) + if (Settings.interactiveMode && offlinePrompt == null) { offlinePrompt = new Thread(new ThreadStart(delegate { @@ -269,31 +292,33 @@ public static void OfflineCommandPrompt() command = Console.ReadLine().Trim(); if (command.Length > 0) { - if (Settings.internalCmdChar != ' ' && command[0] == Settings.internalCmdChar) - { - string message = ""; + 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); } + + if (command.StartsWith("reco")) + { + message = new Commands.Reco().Run(null, Settings.expandVars(command)); } - else ConsoleIO.WriteLineFormatted("§8Please type a command or press Enter to exit."); + 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); } } })); @@ -301,6 +326,26 @@ public static void OfflineCommandPrompt() } } + /// + /// + /// + /// + + public static bool MinecraftVersionPrompt() + { + if (Settings.interactiveMode) + { + Console.Write("Server version : "); + Settings.ServerVersion = Console.ReadLine(); + if (Settings.ServerVersion != "") + { + useMcVersionOnce = true; + return true; + } + } + return false; + } + /// /// Detect if the user is running Minecraft Console Client through Mono /// diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 10a0aa02e2..5141d16e4e 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -124,7 +124,7 @@ public static int MCVer2ProtocolVersion(string MCVersion) } catch { - return -1; + return 0; } } } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 09e508f1fd..8746eae223 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -40,7 +40,7 @@ public static class Settings public static List Bots_Owners = new List(); 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 = ""; @@ -148,7 +148,7 @@ public static void LoadSettings(string settingsfile) 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; From 2c31efd0c97547483d197da6ca01e0deb3450ce1 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 28 Mar 2015 13:39:56 +0100 Subject: [PATCH 008/102] Add server IP in default window title Unix-Tool-Like syntax : user@host Window title can be changed or disabled in INI file --- MinecraftClient/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 8746eae223..e074da88b3 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -342,7 +342,7 @@ public static void WriteDefaultSettings(string settingsfile) + "\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" + "mcversion=auto #use 'auto' or '1.X.X' values\r\n" + "chatbotlogfile= #leave empty for no logfile\r\n" From ea17ec87f12a2283e3138048945a35fb0ac3acf7 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 6 Apr 2015 11:42:43 +0200 Subject: [PATCH 009/102] Better exception catching - Better catch in proxy handler - Better catch in StartClient (thx doranchak) --- MinecraftClient/McTcpClient.cs | 65 ++++++++++++------- .../Protocol/Handlers/Protocol18.cs | 2 +- MinecraftClient/Proxy/ProxyHandler.cs | 12 ++-- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index ebf99e754e..25144ebde2 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -89,6 +89,7 @@ public McTcpClient(string username, string uuid, string sessionID, string server private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, bool singlecommand, string command) { + bool retry = false; this.sessionid = sessionID; this.uuid = uuid; this.username = user; @@ -113,37 +114,51 @@ private void StartClient(string user, string uuid, string sessionID, string serv client.ReceiveBufferSize = 1024 * 1024; handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this); Console.WriteLine("Version is supported.\nLogging in..."); - - if (handler.Login()) + + try { - if (singlecommand) - { - handler.SendChatMessage(command); - ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent."); - Thread.Sleep(5000); - handler.Disconnect(); - Thread.Sleep(1000); - } - else + if (handler.Login()) { - 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) { Console.WriteLine("Failed to connect to this IP."); + retry = true; + } + + if (retry) + { if (AttemptsLeft > 0) { ChatBot.LogToConsole("Waiting 5 seconds (" + AttemptsLeft + " attempts left)..."); @@ -428,6 +443,10 @@ public bool SendRespawnPacket() public void OnPlayerJoin(Guid uuid, string name) { + //Ignore TabListPlus placeholders + if (name.StartsWith("0000tab#")) + return; + lock (onlinePlayers) { onlinePlayers[uuid] = name; diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index a1eb60f3cf..ae1ae240fb 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -458,7 +458,7 @@ public bool Login() 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( 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); diff --git a/MinecraftClient/Proxy/ProxyHandler.cs b/MinecraftClient/Proxy/ProxyHandler.cs index 6ac635828f..71b5fabaf6 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -57,11 +57,15 @@ public static TcpClient newTcpClient(string host, int port) } else return new TcpClient(host, port); } - catch (ProxyException e) + catch (Exception e) { - ConsoleIO.WriteLineFormatted("§8" + e.Message); - proxy = null; - return null; + if (e is ProxyException || e is SocketException) + { + ConsoleIO.WriteLineFormatted("§8" + e.Message); + proxy = null; + return null; + } + else throw; } } } From 2cf46c04877f8eef92c8354d29dda833bc184a3c Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 11 Apr 2015 12:30:36 +0200 Subject: [PATCH 010/102] Fix crash when resizing terminal Bug report by doranchak --- MinecraftClient/ConsoleIO.cs | 44 ++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index ac627c288d..131768bd8b 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -193,26 +193,36 @@ public static void Write(string text) writing_lock = true; if (reading) { - string buf = buffer; - string buf2 = buffer2; - ClearLineAndBuffer(); - if (Console.CursorLeft == 0) + try { - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; - Console.Write(' '); - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; + string buf = buffer; + string buf2 = buffer2; + ClearLineAndBuffer(); + if (Console.CursorLeft == 0) + { + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; + Console.Write(' '); + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; + } + else Console.Write("\b \b"); + Console.Write(text); + buffer = buf; + buffer2 = buf2; + Console.Write(">" + buffer); + if (buffer2.Length > 0) + { + Console.Write(buffer2 + " \b"); + for (int i = 0; i < buffer2.Length; i++) { GoBack(); } + } } - else Console.Write("\b \b"); - Console.Write(text); - buffer = buf; - buffer2 = buf2; - Console.Write(">" + buffer); - if (buffer2.Length > 0) + catch (ArgumentOutOfRangeException) { - Console.Write(buffer2 + " \b"); - for (int i = 0; i < buffer2.Length; i++) { GoBack(); } + //Console resized: Try again + Console.Write('\n'); + writing_lock = false; + Write(text); } } else Console.Write(text); From 791ecba454aeefb358ee62961654bbb3c5f039b7 Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 14 Apr 2015 15:36:51 +0200 Subject: [PATCH 011/102] Add timeout for server ping Thanks doranchak & FantomHD (post no.1193) + Add missing doc for MinecraftVersionPrompt --- MinecraftClient/Program.cs | 4 +-- MinecraftClient/Protocol/ProtocolHandler.cs | 32 ++++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 1cfdfa60fa..80fa62b132 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -327,9 +327,9 @@ public static void OfflineCommandPrompt() } /// - /// + /// Ask for server version when failed to ping server and/or determinate serveur version /// - /// + /// TRUE if a Minecraft version has been read from prompt public static bool MinecraftVersionPrompt() { diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 5141d16e4e..d33ef7e837 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -25,25 +25,31 @@ public static class ProtocolHandler public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion) { - try + bool success = false; + int protocolversionTmp = 0; + if (AutoTimeout.Perform(() => { - if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversion)) - { - return true; - } - else if (Protocol17Handler.doPing(serverIP, serverPort, ref protocolversion)) + try { - return true; + if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp) + || Protocol17Handler.doPing(serverIP, serverPort, ref protocolversionTmp)) + { + success = true; + } + else ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)"); } - else + catch { - ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)"); - return false; + ConsoleIO.WriteLineFormatted("§8An error occured while attempting to connect to this IP."); } + }, TimeSpan.FromSeconds(30))) + { + protocolversion = protocolversionTmp; + return success; } - catch + else { - ConsoleIO.WriteLineFormatted("§8An error occured while attempting to connect to this IP."); + ConsoleIO.WriteLineFormatted("§8A timeout occured while attempting to connect to this IP."); return false; } } @@ -260,7 +266,7 @@ private static int doHTTPSPost(string host, string endpoint, string request, ref statusCode = Settings.str2int(raw_result.Split(' ')[1]); } else statusCode = 520; //Web server is returning an unknown error - }, 30000); + }, TimeSpan.FromSeconds(30)); result = postResult; return statusCode; } From 6261e7adb7fc4be6458e72cd23a09569057cb523 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 20 Apr 2015 17:26:16 +0200 Subject: [PATCH 012/102] More startup error handling - Pass minecraft login failure message to AutoRelog bot (suggestion by doranchak) - Fix NullReferenceException in McTcpClient caused by SocketException in ProxyHandler - Refactor error handling code in Program.InitializeClient() - More detailed error messages on network errors. --- MinecraftClient/ChatBots/AutoRelog.cs | 10 +++ MinecraftClient/McTcpClient.cs | 8 ++- MinecraftClient/Program.cs | 72 ++++++++------------- MinecraftClient/Protocol/ProtocolHandler.cs | 4 +- MinecraftClient/Proxy/ProxyHandler.cs | 12 ++-- 5 files changed, 48 insertions(+), 58 deletions(-) diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs index 5f2b9e2ce1..19d831f50c 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -62,5 +62,15 @@ public override bool OnDisconnect(DisconnectReason reason, string message) } return false; } + + public static bool OnDisconnectStatic(DisconnectReason reason, string message) + { + if (Settings.AutoRelog_Enabled) + { + AutoRelog bot = new AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries); + return bot.OnDisconnect(reason, message); + } + return false; + } } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 25144ebde2..01d77dd7a2 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -151,8 +151,9 @@ private void StartClient(string user, string uuid, string sessionID, string serv retry = true; } } - catch (SocketException) + catch (SocketException e) { + ConsoleIO.WriteLineFormatted("§8" + e.Message); Console.WriteLine("Failed to connect to this IP."); retry = true; } @@ -166,7 +167,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv } else if (!singlecommand && Settings.interactiveMode) { - Program.OfflineCommandPrompt(); + Program.HandleOfflineMode(); } } } @@ -369,7 +370,8 @@ public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) foreach (ChatBot bot in bots) will_restart |= bot.OnDisconnect(reason, message); - if (!will_restart) { Program.OfflineCommandPrompt(); } + if (!will_restart) + Program.HandleOfflineMode(); } /// diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 80fa62b132..4ece88383e 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -169,24 +169,8 @@ private static void InitializeClient() 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 - { - if (Settings.interactiveMode) - { - if (MinecraftVersionPrompt()) - { - Restart(); - return; - } - OfflineCommandPrompt(); - } - } - return; + if (!ChatBots.AutoRelog.OnDisconnectStatic(ChatBot.DisconnectReason.ConnectionLost, "Failed to ping this IP.")) + HandleServerVersionFailure(); } } @@ -204,42 +188,40 @@ private static void InitializeClient() catch (NotSupportedException) { Console.WriteLine("Cannot connect to the server : This version is not supported !"); - OfflineCommandPrompt(); + HandleServerVersionFailure(); } } else { Console.WriteLine("Failed to determine server version."); - if (Settings.interactiveMode && MinecraftVersionPrompt()) - { - Restart(); - return; - } + HandleServerVersionFailure(); } } else { Console.ForegroundColor = ConsoleColor.Gray; - Console.Write("Connection failed : "); + string failureMessage = "Minecraft Login 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; + 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; + } + Console.WriteLine(failureMessage); + 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; } while (Console.KeyAvailable) { Console.ReadKey(false); } - if (Settings.SingleCommand == "") { OfflineCommandPrompt(); } + if (!ChatBots.AutoRelog.OnDisconnectStatic(ChatBot.DisconnectReason.LoginRejected, failureMessage)) + HandleOfflineMode(); } } @@ -274,10 +256,10 @@ public static void Exit() } /// - /// Pause the program, usually when an error or a kick occured, letting the user press Enter to quit OR type /reconnect + /// Pause the program, usually when an error or a kick occured, letting the user typing commands to reconnect to a server /// - public static void OfflineCommandPrompt() + public static void HandleOfflineMode() { if (Settings.interactiveMode && offlinePrompt == null) { @@ -331,7 +313,7 @@ public static void OfflineCommandPrompt() /// /// TRUE if a Minecraft version has been read from prompt - public static bool MinecraftVersionPrompt() + public static void HandleServerVersionFailure() { if (Settings.interactiveMode) { @@ -340,10 +322,10 @@ public static bool MinecraftVersionPrompt() if (Settings.ServerVersion != "") { useMcVersionOnce = true; - return true; + Restart(); } + else HandleOfflineMode(); } - return false; } /// diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index d33ef7e837..eb7231f078 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -38,9 +38,9 @@ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int pro } else ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)"); } - catch + catch (Exception e) { - ConsoleIO.WriteLineFormatted("§8An error occured while attempting to connect to this IP."); + ConsoleIO.WriteLineFormatted("§8" + e.Message); } }, TimeSpan.FromSeconds(30))) { diff --git a/MinecraftClient/Proxy/ProxyHandler.cs b/MinecraftClient/Proxy/ProxyHandler.cs index 71b5fabaf6..d18d4ea1c6 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -57,15 +57,11 @@ public static TcpClient newTcpClient(string host, int port) } else return new TcpClient(host, port); } - catch (Exception e) + catch (ProxyException e) { - if (e is ProxyException || e is SocketException) - { - ConsoleIO.WriteLineFormatted("§8" + e.Message); - proxy = null; - return null; - } - else throw; + ConsoleIO.WriteLineFormatted("§8" + e.Message); + proxy = null; + return null; } } } From 7deec58b994665df8a47d32f36e96acd5719039a Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 20 Apr 2015 17:54:26 +0200 Subject: [PATCH 013/102] Add Build Configuration Add config for automated builds --- appveyor.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..405187bbcf --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,6 @@ +configuration: Release +build: + project: MinecraftClient.sln +artifacts: + - path: MinecraftClient\bin\$(configuration) + name: MinecraftClient.exe \ No newline at end of file From 8b261894c8cbd2b3b66e5e634de4a1cc93436397 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 20 Apr 2015 18:29:46 +0200 Subject: [PATCH 014/102] Remove Build Configuration Build can be configured directly on AppVeyor. --- appveyor.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 405187bbcf..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,6 +0,0 @@ -configuration: Release -build: - project: MinecraftClient.sln -artifacts: - - path: MinecraftClient\bin\$(configuration) - name: MinecraftClient.exe \ No newline at end of file From 57c66c82d7f269462b73b44b7ddeaabb661fdc72 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 22 Apr 2015 10:27:53 +0200 Subject: [PATCH 015/102] Merge error handlers - Merge all error handling code into one method - Fix ConsoleIO not clearing the line being typed upon reset - Update console title upon logging in to the server - Pass "failed to ping this IP" to AutoRelog (thx doranchak) --- MinecraftClient/ConsoleIO.cs | 2 +- MinecraftClient/McTcpClient.cs | 4 +- MinecraftClient/Program.cs | 155 +++++++++++++++++---------------- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 131768bd8b..463d309ec5 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -14,7 +14,7 @@ namespace MinecraftClient public static class ConsoleIO { - public static void Reset() { if (reading) { reading = false; Console.Write("\b \b"); } } + public static void Reset() { if (reading) { ClearLineAndBuffer(); reading = false; Console.Write("\b \b"); } } public static void SetAutoCompleteEngine(IAutoComplete engine) { autocomplete_engine = engine; } public static bool basicIO = false; private static IAutoComplete autocomplete_engine; diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 01d77dd7a2..cf14046f69 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -167,7 +167,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv } else if (!singlecommand && Settings.interactiveMode) { - Program.HandleOfflineMode(); + Program.HandleFailure(); } } } @@ -371,7 +371,7 @@ public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) will_restart |= bot.OnDisconnect(reason, message); if (!will_restart) - Program.HandleOfflineMode(); + Program.HandleFailure(); } /// diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 4ece88383e..399e2f1475 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -167,11 +167,7 @@ private static void InitializeClient() { Console.WriteLine("Retrieving Server Info..."); if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion)) - { - Console.WriteLine("Failed to ping this IP."); - if (!ChatBots.AutoRelog.OnDisconnectStatic(ChatBot.DisconnectReason.ConnectionLost, "Failed to ping this IP.")) - HandleServerVersionFailure(); - } + HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); } if (protocolversion != 0) @@ -184,18 +180,14 @@ private static void InitializeClient() 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); + + //Update console title + if (Settings.ConsoleTitle != "") + Console.Title = Settings.expandVars(Settings.ConsoleTitle); } - catch (NotSupportedException) - { - Console.WriteLine("Cannot connect to the server : This version is not supported !"); - HandleServerVersionFailure(); - } - } - else - { - Console.WriteLine("Failed to determine server version."); - HandleServerVersionFailure(); + catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); } } + else HandleFailure("Failed to determine server version.", true); } else { @@ -211,7 +203,6 @@ private static void InitializeClient() case ProtocolHandler.LoginResult.SSLError: failureMessage += "SSL Error."; break; default: failureMessage += "Unknown Error."; break; } - Console.WriteLine(failureMessage); if (result == ProtocolHandler.LoginResult.SSLError && isUsingMono) { ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program." @@ -220,8 +211,7 @@ private static void InitializeClient() return; } while (Console.KeyAvailable) { Console.ReadKey(false); } - if (!ChatBots.AutoRelog.OnDisconnectStatic(ChatBot.DisconnectReason.LoginRejected, failureMessage)) - HandleOfflineMode(); + HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); } } @@ -256,75 +246,86 @@ public static void Exit() } /// - /// Pause the program, usually when an error or a kick occured, letting the user typing commands to reconnect to a server + /// 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. /// - - public static void HandleOfflineMode() + /// 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 (Settings.interactiveMode && offlinePrompt == null) + if (!String.IsNullOrEmpty(errorMessage)) { - 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] + "'."); + ConsoleIO.Reset(); + Console.WriteLine(errorMessage); - if (message != "") - ConsoleIO.WriteLineFormatted("§8MCC: " + message); - } - } - })); - offlinePrompt.Start(); + if (disconnectReason.HasValue) + { + if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) + return; //AutoRelog is triggering a restart of the client + } } - } - /// - /// Ask for server version when failed to ping server and/or determinate serveur version - /// - /// TRUE if a Minecraft version has been read from prompt - - public static void HandleServerVersionFailure() - { if (Settings.interactiveMode) { - Console.Write("Server version : "); - Settings.ServerVersion = Console.ReadLine(); - if (Settings.ServerVersion != "") + if (versionError) { - useMcVersionOnce = true; - Restart(); + 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(); } - else HandleOfflineMode(); } } From 72498a675690783530941a3d67bda965134d55ee Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 22 Apr 2015 18:56:43 +0200 Subject: [PATCH 016/102] Fix ping failure causing double failure handling "Failed to ping this IP" also caused "Failed to determine server version" error, calling HandleFailure() twice. --- MinecraftClient/Program.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 399e2f1475..0794b765fa 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -167,7 +167,10 @@ private static void InitializeClient() { Console.WriteLine("Retrieving Server Info..."); if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion)) + { HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); + return; + } } if (protocolversion != 0) @@ -210,7 +213,6 @@ private static void InitializeClient() + '\n' + "mozroots --import --ask-remove"); return; } - while (Console.KeyAvailable) { Console.ReadKey(false); } HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); } } @@ -258,6 +260,8 @@ public static void HandleFailure(string errorMessage = null, bool versionError = if (!String.IsNullOrEmpty(errorMessage)) { ConsoleIO.Reset(); + while (Console.KeyAvailable) + Console.ReadKey(true); Console.WriteLine(errorMessage); if (disconnectReason.HasValue) From 3376247826050a81d7230d65fa2f50a9145c6498 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 10 May 2015 18:57:33 +0200 Subject: [PATCH 017/102] Fix concurrency in ConsoleIO Fix concurrency issues by using proper locks --- MinecraftClient/ConsoleIO.cs | 77 ++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 463d309ec5..9f6f18ab75 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -14,16 +14,30 @@ namespace MinecraftClient public static class ConsoleIO { - public static void Reset() { if (reading) { ClearLineAndBuffer(); reading = false; Console.Write("\b \b"); } } - public static void SetAutoCompleteEngine(IAutoComplete engine) { autocomplete_engine = engine; } public static bool basicIO = false; private static IAutoComplete autocomplete_engine; private static LinkedList previous = new LinkedList(); private static string buffer = ""; private static string buffer2 = ""; private static bool reading = false; - private static bool reading_lock = false; - private static bool writing_lock = false; + private static object io_lock = new object(); + + /// + /// Reset the IO mechanism & clear all buffers + /// + + public static void Reset() + { + lock (io_lock) + { + if (reading) + { + ClearLineAndBuffer(); + reading = false; + Console.Write("\b \b"); + } + } + } /// /// Read a password from the standard input @@ -82,16 +96,20 @@ public static string ReadLine() { if (basicIO) { return Console.ReadLine(); } ConsoleKeyInfo k = new ConsoleKeyInfo(); - Console.Write('>'); - reading = true; - buffer = ""; - buffer2 = ""; + + lock (io_lock) + { + Console.Write('>'); + reading = true; + buffer = ""; + buffer2 = ""; + } while (k.Key != ConsoleKey.Enter) { k = Console.ReadKey(true); - while (writing_lock) { } - reading_lock = true; + lock (io_lock) + { if (k.Key == ConsoleKey.V && k.Modifiers == ConsoleModifiers.Control) { string clip = ReadClipboard(); @@ -173,13 +191,16 @@ public static string ReadLine() AddChar(k.KeyChar); break; } + } } - reading_lock = false; } - while (writing_lock) { } - reading = false; - previous.AddLast(buffer + buffer2); - return buffer + buffer2; + + lock (io_lock) + { + reading = false; + previous.AddLast(buffer + buffer2); + return buffer + buffer2; + } } /// @@ -188,9 +209,10 @@ public static string ReadLine() public static void Write(string text) { - if (basicIO) { Console.Write(text); return; } - while (reading_lock) { } - writing_lock = true; + if (!basicIO) + { + lock (io_lock) + { if (reading) { try @@ -221,12 +243,13 @@ public static void Write(string text) { //Console resized: Try again Console.Write('\n'); - writing_lock = false; Write(text); } } else Console.Write(text); - writing_lock = false; + } + } + else Console.Write(text); } /// @@ -289,7 +312,7 @@ public static void WriteLineFormatted(string str, bool acceptnewlines = true) 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) @@ -387,6 +410,18 @@ private static string ReadClipboard() 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 } /// From 33b84584aa4299fe505aa65728d689becb76f631 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 10 May 2015 18:59:00 +0200 Subject: [PATCH 018/102] Indentation change for Fix concurrency[..] Indentation was intentionally left the same in previous commit for clearer diff, this commit only fixes code indentation from last commit --- MinecraftClient/ConsoleIO.cs | 214 +++++++++++++++++------------------ 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 9f6f18ab75..eee3d3d86d 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -110,87 +110,87 @@ public static string ReadLine() k = Console.ReadKey(true); lock (io_lock) { - if (k.Key == ConsoleKey.V && k.Modifiers == ConsoleModifiers.Control) - { - string clip = ReadClipboard(); - foreach (char c in clip) - AddChar(c); - } - else - { - switch (k.Key) + if (k.Key == ConsoleKey.V && k.Modifiers == ConsoleModifiers.Control) { - case ConsoleKey.Escape: - ClearLineAndBuffer(); - break; - case ConsoleKey.Backspace: - RemoveOneChar(); - break; - case ConsoleKey.Enter: - Console.Write('\n'); - break; - case ConsoleKey.LeftArrow: - GoLeft(); - break; - case ConsoleKey.RightArrow: - GoRight(); - break; - case ConsoleKey.Home: - while (buffer.Length > 0) { GoLeft(); } - break; - case ConsoleKey.End: - while (buffer2.Length > 0) { GoRight(); } - break; - case ConsoleKey.Delete: - if (buffer2.Length > 0) - { - GoRight(); - RemoveOneChar(); - } - break; - case ConsoleKey.Oem6: - break; - case ConsoleKey.DownArrow: - if (previous.Count > 0) - { - ClearLineAndBuffer(); - buffer = previous.First.Value; - previous.AddLast(buffer); - previous.RemoveFirst(); - Console.Write(buffer); - } - break; - case ConsoleKey.UpArrow: - if (previous.Count > 0) - { + string clip = ReadClipboard(); + foreach (char c in clip) + AddChar(c); + } + else + { + switch (k.Key) + { + case ConsoleKey.Escape: ClearLineAndBuffer(); - buffer = previous.Last.Value; - previous.AddFirst(buffer); - previous.RemoveLast(); - Console.Write(buffer); - } - break; - case ConsoleKey.Tab: - if (autocomplete_engine != null && buffer.Length > 0) - { - string[] tmp = buffer.Split(' '); - if (tmp.Length > 0) + break; + case ConsoleKey.Backspace: + RemoveOneChar(); + break; + case ConsoleKey.Enter: + Console.Write('\n'); + break; + case ConsoleKey.LeftArrow: + GoLeft(); + break; + case ConsoleKey.RightArrow: + GoRight(); + break; + case ConsoleKey.Home: + while (buffer.Length > 0) { GoLeft(); } + break; + case ConsoleKey.End: + while (buffer2.Length > 0) { GoRight(); } + break; + case ConsoleKey.Delete: + if (buffer2.Length > 0) + { + GoRight(); + RemoveOneChar(); + } + break; + case ConsoleKey.Oem6: + break; + case ConsoleKey.DownArrow: + if (previous.Count > 0) { - string word_tocomplete = tmp[tmp.Length - 1]; - string word_autocomplete = autocomplete_engine.AutoComplete(buffer); - if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete) + ClearLineAndBuffer(); + buffer = previous.First.Value; + previous.AddLast(buffer); + previous.RemoveFirst(); + Console.Write(buffer); + } + break; + case ConsoleKey.UpArrow: + if (previous.Count > 0) + { + ClearLineAndBuffer(); + buffer = previous.Last.Value; + previous.AddFirst(buffer); + previous.RemoveLast(); + Console.Write(buffer); + } + break; + case ConsoleKey.Tab: + if (autocomplete_engine != null && buffer.Length > 0) + { + string[] tmp = buffer.Split(' '); + if (tmp.Length > 0) { - while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); } - foreach (char c in word_autocomplete) { AddChar(c); } + string word_tocomplete = tmp[tmp.Length - 1]; + string word_autocomplete = autocomplete_engine.AutoComplete(buffer); + if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete) + { + while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); } + foreach (char c in word_autocomplete) { AddChar(c); } + } } } - } - break; - default: - if (k.KeyChar != 0) - AddChar(k.KeyChar); - break; - } + break; + default: + if (k.KeyChar != 0) + AddChar(k.KeyChar); + break; + } } } } @@ -213,40 +213,40 @@ public static void Write(string text) { lock (io_lock) { - if (reading) - { - try - { - string buf = buffer; - string buf2 = buffer2; - ClearLineAndBuffer(); - if (Console.CursorLeft == 0) + if (reading) { - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; - Console.Write(' '); - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; - } - else Console.Write("\b \b"); - Console.Write(text); - buffer = buf; - buffer2 = buf2; - Console.Write(">" + buffer); - if (buffer2.Length > 0) - { - Console.Write(buffer2 + " \b"); - for (int i = 0; i < buffer2.Length; i++) { GoBack(); } + try + { + string buf = buffer; + string buf2 = buffer2; + ClearLineAndBuffer(); + if (Console.CursorLeft == 0) + { + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; + Console.Write(' '); + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; + } + else Console.Write("\b \b"); + Console.Write(text); + buffer = buf; + buffer2 = buf2; + Console.Write(">" + buffer); + if (buffer2.Length > 0) + { + Console.Write(buffer2 + " \b"); + for (int i = 0; i < buffer2.Length; i++) { GoBack(); } + } + } + catch (ArgumentOutOfRangeException) + { + //Console resized: Try again + Console.Write('\n'); + Write(text); + } } - } - catch (ArgumentOutOfRangeException) - { - //Console resized: Try again - Console.Write('\n'); - Write(text); - } - } - else Console.Write(text); + else Console.Write(text); } } else Console.Write(text); From 834e446a74bbc0f5c60fb9fc52c984d11e6d6239 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 13 May 2015 10:59:46 +0200 Subject: [PATCH 019/102] Add 1.8.4 in supported version list + minor fixes to ConsoleIO --- MinecraftClient/ConsoleIO.cs | 6 +++--- MinecraftClient/Program.cs | 2 +- MinecraftClient/Protocol/ProtocolHandler.cs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index eee3d3d86d..1043748b82 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -17,13 +17,13 @@ public static class ConsoleIO public static bool basicIO = false; private static IAutoComplete autocomplete_engine; private static LinkedList previous = new LinkedList(); + private static readonly object io_lock = new object(); + private static bool reading = false; private static string buffer = ""; private static string buffer2 = ""; - private static bool reading = false; - private static object io_lock = new object(); /// - /// Reset the IO mechanism & clear all buffers + /// Reset the IO mechanism and clear all buffers /// public static void Reset() diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 0794b765fa..f33790f49e 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -28,7 +28,7 @@ static class Program static void Main(string[] args) { - Console.WriteLine("Console Client for MC 1.4.6 to 1.8.3 - v" + Version + " - By ORelio & Contributors"); + Console.WriteLine("Console Client for MC 1.4.6 to 1.8.4 - v" + Version + " - By ORelio & Contributors"); //Basic Input/Output ? if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index eb7231f078..9c4f3a2d82 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -117,6 +117,7 @@ public static int MCVer2ProtocolVersion(string MCVersion) case "1.8.1": case "1.8.2": case "1.8.3": + case "1.8.4": return 47; default: return 0; From 93d58a8d816aa8e8bca3b851daa74024032b60c4 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 17 May 2015 21:10:01 +0200 Subject: [PATCH 020/102] Ignore invalid UUIDs for tab-list If the server is sending invalid UUIDs, use an empty UUID instead --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index ae1ae240fb..760f398af4 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -282,7 +282,14 @@ private string readNextString(ref byte[] cache) private Guid readNextUUID(ref byte[] cache) { - return new Guid(readData(16, ref cache)); + try + { + return new Guid(readData(16, ref cache)); + } + catch (ArgumentException) + { + return Guid.Empty; + } } /// From dd5e2f8e3994924e355e6713dbfaa6946e723c11 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 17 May 2015 21:45:00 +0200 Subject: [PATCH 021/102] Rewrite translation rule processing Improve speed and handling of %1$s tags Fix prompt in ConsoleIO not being reset to gray --- MinecraftClient/ConsoleIO.cs | 2 +- .../Protocol/Handlers/ChatParser.cs | 52 +++++++++++++------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 1043748b82..90c714b866 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -321,9 +321,9 @@ public static void WriteLineFormatted(string str, bool acceptnewlines = true) } } } + Console.ForegroundColor = ConsoleColor.Gray; ConsoleIO.Write('\n'); } - Console.ForegroundColor = ConsoleColor.Gray; } #region Subfunctions diff --git a/MinecraftClient/Protocol/Handlers/ChatParser.cs b/MinecraftClient/Protocol/Handlers/ChatParser.cs index df86f8a487..7526ea4467 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -169,25 +169,43 @@ private static string TranslateString(string rulename, List using_data) if (!init) { InitRules(); init = true; } if (TranslationRules.ContainsKey(rulename)) { - if ((TranslationRules[rulename].IndexOf("%1$s") >= 0 && TranslationRules[rulename].IndexOf("%2$s") >= 0) - && (TranslationRules[rulename].IndexOf("%1$s") > TranslationRules[rulename].IndexOf("%2$s"))) + 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; - } - 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]; + 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]); } - translated += syntax[syntax.Length - 1]; - return translated; + return result.ToString(); } else return "[" + rulename + "] " + String.Join(" ", using_data); } From 5b662e2d07530938336a7fb67f2619913a4ebb9e Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 18 May 2015 16:15:58 +0200 Subject: [PATCH 022/102] Fix HeroChat public messages treated as private See issue #63 - Also includes minor fixes and optimizations --- MinecraftClient/ChatBot.cs | 96 +++++++++++++++++------------------- MinecraftClient/ConsoleIO.cs | 2 +- 2 files changed, 46 insertions(+), 52 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index d591c419a7..a3ae5ebbb1 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -14,12 +14,12 @@ namespace MinecraftClient /// Once your bot is created, read the explanations below to start using it in the MinecraftClient app. /// /// Pieces of code to add in other parts of the program for your bot. Line numbers are approximative. - /// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); } /// Settings.cs:73 | public static bool YourBot_Enabled = false; /// Settings.cs:74 | private enum ParseMode { /* [...] */, YourBot }; /// Settings.cs:106 | case "yourbot": pMode = ParseMode.YourBot; break; /// Settings.cs:197 | case ParseMode.YourBot: switch (argName.ToLower()) { case "enabled": YourBot_Enabled = str2bool(argValue); break; } break; /// Settings.cs:267 | + "[YourBot]\r\n" + "enabled=false\r\n" + /// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); } /// Here your are. Now you will have a setting in MinecraftClient.ini for enabling your brand new bot. /// Delete MinecraftClient.ini to re-generate it or add the lines [YourBot] and enabled=true to the existing one. /// @@ -213,17 +213,6 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri return isValidName(sender); } - //Detect HeroChat Messages - //[Channel] [Rank] User: Message - else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2) - { - int name_end = text.IndexOf(':'); - int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; - sender = text.Substring(name_start, name_end - name_start); - message = text.Substring(name_end + 2); - return isValidName(sender); - } - else return false; } catch (IndexOutOfRangeException) { return false; } @@ -239,31 +228,46 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri protected static bool isChatMessage(string text, ref string message, ref string sender) { - //Detect chat messages - // message - //<*Faction Someone> message - //<*Faction Someone>: message - //<*Faction ~Nicknamed>: message + text = getVerbatim(text); - if (text == "") { return false; } - if (text[0] == '<') + string[] tmp = text.Split(' '); + if (text.Length > 0) { - try + //Detect vanilla/factions Messages + // message + //<*Faction Someone> message + //<*Faction Someone>: message + //<*Faction ~Nicknamed>: message + if (text[0] == '<') { - text = text.Substring(1); - string[] tmp = text.Split('>'); - sender = tmp[0]; - message = text.Substring(sender.Length + 2); - if (message.Length > 1 && message[0] == ' ') - { message = message.Substring(1); } - tmp = sender.Split(' '); - sender = tmp[tmp.Length - 1]; - if (sender[0] == '~') { sender = sender.Substring(1); } + try + { + text = text.Substring(1); + string[] tmp2 = text.Split('>'); + sender = tmp2[0]; + message = text.Substring(sender.Length + 2); + if (message.Length > 1 && message[0] == ' ') + { message = message.Substring(1); } + tmp2 = sender.Split(' '); + sender = tmp2[tmp2.Length - 1]; + if (sender[0] == '~') { sender = sender.Substring(1); } + return isValidName(sender); + } + catch (IndexOutOfRangeException) { return false; } + } + + //Detect HeroChat Messages + //[Channel] [Rank] User: Message + else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2) + { + int name_end = text.IndexOf(':'); + int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; + sender = text.Substring(name_start, name_end - name_start); + message = text.Substring(name_end + 2); return isValidName(sender); } - catch (IndexOutOfRangeException) { return false; } } - else return false; + return false; } /// @@ -311,18 +315,11 @@ public static void LogToConsole(string text) /// /// Disconnect from the server and restart the program - /// It will unload & reload all the bots and then reconnect to the server - /// - - protected void ReconnectToTheServer() { ReconnectToTheServer(3); } - - /// - /// Disconnect from the server and restart the program - /// It will unload & reload all the bots and then reconnect to the server + /// It will unload and reload all the bots and then reconnect to the server /// /// If connection fails, the client will make X extra attempts - protected void ReconnectToTheServer(int ExtraAttempts) + protected void ReconnectToTheServer(int ExtraAttempts = 3) { McTcpClient.AttemptsLeft = ExtraAttempts; Program.Restart(); @@ -369,22 +366,19 @@ protected void RunScript(string filename, string playername = "") } /// - /// Get a D-M-Y h:m:s timestamp representing the current system date and time + /// Get a Y-M-D h:m:s timestamp representing the current system date and time /// protected static string getTimestamp() { DateTime time = DateTime.Now; - - string D = time.Day.ToString("00"); - string M = time.Month.ToString("00"); - string Y = time.Year.ToString("0000"); - - string h = time.Hour.ToString("00"); - string m = time.Minute.ToString("00"); - string s = time.Second.ToString("00"); - - return "" + D + '-' + M + '-' + Y + ' ' + h + ':' + m + ':' + s; + return String.Format("{0}-{1}-{2} {3}:{4}:{5}", + time.Year.ToString("0000"), + time.Month.ToString("00"), + time.Day.ToString("00"), + time.Hour.ToString("00"), + time.Minute.ToString("00"), + time.Second.ToString("00")); } } } diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 90c714b866..dd488d52d3 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -284,7 +284,7 @@ public static void WriteLineFormatted(string str, bool acceptnewlines = true) 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; } From 43fa3fb4b47206696416163b984585b170577b5a Mon Sep 17 00:00:00 2001 From: Bancey Date: Tue, 19 May 2015 15:36:20 +0100 Subject: [PATCH 023/102] Auto Respond Bot This bot allows users to add a bot that can detect and respond to certain text. The bot can be enabled/disabled via the ini file. (disabled by default) The bot uses 2 files to let the user set what to pickup and what to respond. --- MinecraftClient/ChatBots/Auto Respond.cs | 59 ++++++++++++++++++++++++ MinecraftClient/McTcpClient.cs | 7 +-- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Settings.cs | 36 ++++++++++++--- 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 MinecraftClient/ChatBots/Auto Respond.cs diff --git a/MinecraftClient/ChatBots/Auto Respond.cs b/MinecraftClient/ChatBots/Auto Respond.cs new file mode 100644 index 0000000000..c861241e11 --- /dev/null +++ b/MinecraftClient/ChatBots/Auto Respond.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace MinecraftClient.ChatBots +{ + class Auto_Respond : ChatBot + { + private String[] respondon = new String[0]; + private String[] torespond = new String[0]; + + private static string[] FromFile(string file) + { + if (File.Exists(file)) + { + //Read all lines from file, remove lines with no text, convert to lowercase, + //remove duplicate entries, convert to a string array, and return the result. + return File.ReadAllLines(file) + .Where(line => !String.IsNullOrWhiteSpace(line)) + .Select(line => line.ToLower()) + .Distinct().ToArray(); + } + else + { + LogToConsole("File not found: " + file); + return new string[0]; + } + } + + //Initalize the bot + public override void Initialize() + { + respondon = FromFile(Settings.Respond_MatchesFile); + torespond = FromFile(Settings.Respond_RespondFile); + ConsoleIO.WriteLine("Auto Respond Bot Sucessfully loaded!"); + } + + public override void GetText(string text) + { + //Remove colour codes + text = getVerbatim(text).ToLower(); + //Check text to see if bot should respond + foreach (string alert in respondon.Where(alert => text.Contains(alert))) + { + //Find what to respond with + for (int x = 0; x < respondon.Length; x++) + { + if (respondon[x].ToString().Contains(alert)) + { + //Respond + SendText(torespond[x].ToString()); + } + } + } + } + } +} diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index cf14046f69..e3f792e17d 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -20,7 +20,7 @@ public class McTcpClient : IMinecraftComHandler private static List cmd_names = new List(); private static Dictionary cmds = new Dictionary(); private List bots = new List(); - private readonly Dictionary onlinePlayers = new Dictionary(); + private readonly 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)); } @@ -39,7 +39,7 @@ public class McTcpClient : IMinecraftComHandler public string getUsername() { return username; } public string getUserUUID() { return uuid; } public string getSessionID() { return sessionid; } - + TcpClient client; IMinecraftCom handler; Thread cmdprompt; @@ -106,6 +106,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv 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.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } + if (Settings.Respond_Enabled) { BotLoad(new ChatBots.Auto_Respond()); } } try @@ -454,7 +455,7 @@ public void OnPlayerJoin(Guid uuid, string name) onlinePlayers[uuid] = name; } } - + /// /// Triggered when a player has left the game /// diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index afbcb28b95..9ae1ccdc12 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -74,6 +74,7 @@ + diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index e074da88b3..d66bb17e00 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -21,7 +21,7 @@ public static class Settings 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 = ""; @@ -88,12 +88,17 @@ public static class Settings public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; + //Auto Respond + public static bool Respond_Enabled = false; + public static string Respond_MatchesFile = "detect.txt"; + public static string Respond_RespondFile = "respond.txt"; + //Custom app variables and Minecraft accounts private static Dictionary AppVars = new Dictionary(); private static Dictionary> Accounts = new Dictionary>(); private static Dictionary> Servers = new Dictionary>(); - private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl }; + private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, Auto_Respond }; /// /// Load settings from the give INI file @@ -127,6 +132,7 @@ public static void LoadSettings(string settingsfile) case "remotecontrol": pMode = ParseMode.RemoteControl; break; case "proxy": pMode = ParseMode.Proxy; break; case "appvars": pMode = ParseMode.AppVars; break; + case "auto respond": pMode = ParseMode.Auto_Respond; break; default: pMode = ParseMode.Default; break; } } @@ -201,7 +207,7 @@ public static void LoadSettings(string settingsfile) Servers[server_data[0]] = new KeyValuePair(ServerIP, ServerPort); } - + //Restore current server info ServerIP = server_host_temp; ServerPort = server_port_temp; @@ -284,7 +290,7 @@ public static void LoadSettings(string settingsfile) 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": @@ -308,6 +314,15 @@ public static void LoadSettings(string settingsfile) case ParseMode.AppVars: setVar(argName, argValue); break; + + case ParseMode.Auto_Respond: + switch (argName.ToLower()) + { + case "enabled": Respond_Enabled = str2bool(argValue); break; + case "matchfile": Respond_MatchesFile = argValue; break; + case "respondfile": Respond_RespondFile = argValue; break; + } + break; } } } @@ -402,7 +417,14 @@ public static void WriteDefaultSettings(string settingsfile) + "[RemoteControl]\r\n" + "enabled=false\r\n" + "autotpaccept=true\r\n" - + "tpaccepteveryone=false\r\n", Encoding.UTF8); + + "tpaccepteveryone=false\r\n" + + "\r\n" + + "[Auto Respond]\r\n" + + "enabled=false\r\n" + + "matchfile=detect.txt\r\n" + + "respondfile=respond.txt\r\n" + + "#To use the bot, place the text to detect in the matchfile file and the text to respond with in the respondfile\r\n" + + "#Each line in each file is relevant to the same line in the other document, for example if the bot detects the text in line 1 of the first file, it will respond with line 1 of the second file.\r\n", Encoding.UTF8); } public static int str2int(string str) { try { return Convert.ToInt32(str); } catch { return 0; } } @@ -436,7 +458,7 @@ public static bool setServerIP(string server) string[] sip = server.Split(':'); string host = sip[0]; ushort port = 25565; - + if (sip.Length > 1) { try @@ -458,7 +480,7 @@ public static bool setServerIP(string server) ServerPort = Servers[server].Value; return true; } - + return false; } From f7c729835ab45d67dd6bb863a44bd5bf0ce2bab5 Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 26 May 2015 19:00:37 +0200 Subject: [PATCH 024/102] Add delay between sends of long messages When a very long message is typed, a delay of 2 seconds is by default used before sending parts of the long messages. The delay can be modified or set back to 0 in configuration file, if necessary. --- MinecraftClient/McTcpClient.cs | 2 ++ MinecraftClient/Settings.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index cf14046f69..9db2dc2b56 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -420,6 +420,8 @@ public bool SendText(string text) { 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); } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index e074da88b3..72fbc1b796 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -37,6 +37,7 @@ public static class 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_Website_Download = "http://resources.download.minecraft.net"; + public static TimeSpan splitMessageDelay = TimeSpan.FromSeconds(2); public static List Bots_Owners = new List(); public static string Language = "en_GB"; public static bool chatTimeStamps = false; @@ -152,6 +153,7 @@ public static void LoadSettings(string settingsfile) 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 "botowners": Bots_Owners.Clear(); @@ -344,6 +346,7 @@ public static void WriteDefaultSettings(string settingsfile) + "botowners=Player1,Player2,Player3\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" + "mcversion=auto #use 'auto' or '1.X.X' values\r\n" + "chatbotlogfile= #leave empty for no logfile\r\n" + "accountlist=accounts.txt\r\n" From 53156bdf984a9d5d25f699232d5452b9d517dfed Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 26 May 2015 19:16:50 +0200 Subject: [PATCH 025/102] Normalize AutoRespond bot Move FromFile method from bots to ChatBot class Rename file and class, removing space and underscore. --- MinecraftClient/ChatBot.cs | 24 ++++++++++++++++ MinecraftClient/ChatBots/Alerts.cs | 27 ++---------------- .../{Auto Respond.cs => AutoRespond.cs} | 28 ++++--------------- MinecraftClient/McTcpClient.cs | 2 +- MinecraftClient/MinecraftClient.csproj | 2 +- 5 files changed, 33 insertions(+), 50 deletions(-) rename MinecraftClient/ChatBots/{Auto Respond.cs => AutoRespond.cs} (50%) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index a3ae5ebbb1..579430cc7e 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -380,5 +380,29 @@ protected static string getTimestamp() time.Minute.ToString("00"), time.Second.ToString("00")); } + + /// + /// Load entries from a file as a string array, removing duplicates and empty lines + /// + /// File to load + /// The string array or an empty array if failed to load the file + + protected static string[] LoadDistinctEntriesFromFile(string file) + { + if (File.Exists(file)) + { + //Read all lines from file, remove lines with no text, convert to lowercase, + //remove duplicate entries, convert to a string array, and return the result. + return File.ReadAllLines(file) + .Where(line => !String.IsNullOrWhiteSpace(line)) + .Select(line => line.ToLower()) + .Distinct().ToArray(); + } + else + { + LogToConsole("File not found: " + Settings.Alerts_MatchesFile); + return new string[0]; + } + } } } diff --git a/MinecraftClient/ChatBots/Alerts.cs b/MinecraftClient/ChatBots/Alerts.cs index 5cdff08841..cd21bae4cb 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -14,36 +14,13 @@ public class Alerts : ChatBot private string[] dictionary = new string[0]; private string[] excludelist = new string[0]; - /// - /// Import alerts from the specified file - /// - /// - /// - private static string[] FromFile(string file) - { - if (File.Exists(file)) - { - //Read all lines from file, remove lines with no text, convert to lowercase, - //remove duplicate entries, convert to a string array, and return the result. - return File.ReadAllLines(file) - .Where(line => !String.IsNullOrWhiteSpace(line)) - .Select(line => line.ToLower()) - .Distinct().ToArray(); - } - else - { - LogToConsole("File not found: " + Settings.Alerts_MatchesFile); - return new string[0]; - } - } - /// /// Intitialize the Alerts bot /// public override void Initialize() { - dictionary = FromFile(Settings.Alerts_MatchesFile); - excludelist = FromFile(Settings.Alerts_ExcludesFile); + dictionary = LoadDistinctEntriesFromFile(Settings.Alerts_MatchesFile); + excludelist = LoadDistinctEntriesFromFile(Settings.Alerts_ExcludesFile); } /// diff --git a/MinecraftClient/ChatBots/Auto Respond.cs b/MinecraftClient/ChatBots/AutoRespond.cs similarity index 50% rename from MinecraftClient/ChatBots/Auto Respond.cs rename to MinecraftClient/ChatBots/AutoRespond.cs index c861241e11..3a0d6575ac 100644 --- a/MinecraftClient/ChatBots/Auto Respond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -6,34 +6,16 @@ namespace MinecraftClient.ChatBots { - class Auto_Respond : ChatBot + class AutoRespond : ChatBot { - private String[] respondon = new String[0]; - private String[] torespond = new String[0]; - - private static string[] FromFile(string file) - { - if (File.Exists(file)) - { - //Read all lines from file, remove lines with no text, convert to lowercase, - //remove duplicate entries, convert to a string array, and return the result. - return File.ReadAllLines(file) - .Where(line => !String.IsNullOrWhiteSpace(line)) - .Select(line => line.ToLower()) - .Distinct().ToArray(); - } - else - { - LogToConsole("File not found: " + file); - return new string[0]; - } - } + private string[] respondon = new string[0]; + private string[] torespond = new string[0]; //Initalize the bot public override void Initialize() { - respondon = FromFile(Settings.Respond_MatchesFile); - torespond = FromFile(Settings.Respond_RespondFile); + respondon = LoadDistinctEntriesFromFile(Settings.Respond_MatchesFile); + torespond = LoadDistinctEntriesFromFile(Settings.Respond_RespondFile); ConsoleIO.WriteLine("Auto Respond Bot Sucessfully loaded!"); } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 6a991d9671..3f152002e0 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -106,7 +106,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv 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.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } - if (Settings.Respond_Enabled) { BotLoad(new ChatBots.Auto_Respond()); } + if (Settings.Respond_Enabled) { BotLoad(new ChatBots.AutoRespond()); } } try diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 9ae1ccdc12..8129d2ec62 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -74,7 +74,7 @@ - + From 80b468b301e384f5163da02ee5baf838f76afacc Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 1 Jun 2015 08:33:53 +0200 Subject: [PATCH 026/102] Add Mention to Mozroots in Readme file See issue #77 --- MinecraftClient/config/README.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/MinecraftClient/config/README.txt b/MinecraftClient/config/README.txt index 9811ca2801..496dcea7c8 100644 --- a/MinecraftClient/config/README.txt +++ b/MinecraftClient/config/README.txt @@ -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 From 840ac01dc5482d7d5e5f3593969973f91ce008cb Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 3 Jun 2015 12:00:25 +0200 Subject: [PATCH 027/102] Fix crash on empty player list updates Player list updates on MC 1.8 handler did not take into account the amount of items in the list and were only processing the first item, including when there wasn't any item to process. Unfortunately some weird servers were sending useless empty tab-list updates, causing a crash. Should fix issue #78 and forum posts 1267, 1269, 1284. Thanks dbear20, link3321, gerik43, Darkaegis, k3ldon and Ryan6578 for their bug reports! :) --- .../Protocol/Handlers/Protocol18.cs | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 760f398af4..362bbd41d0 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -146,19 +146,22 @@ private bool handlePacket(int packetID, byte[] packetData) case 0x38: //Player List update int action = readNextVarInt(ref packetData); int numActions = readNextVarInt(ref packetData); - Guid uuid = readNextUUID(ref packetData); - switch (action) + for (int i = 0; i < numActions; 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; + Guid uuid = readNextUUID(ref packetData); + switch (action) + { + 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; + } } break; case 0x3A: //Tab-Complete Result @@ -282,14 +285,7 @@ private string readNextString(ref byte[] cache) private Guid readNextUUID(ref byte[] cache) { - try - { - return new Guid(readData(16, ref cache)); - } - catch (ArgumentException) - { - return Guid.Empty; - } + return new Guid(readData(16, ref cache)); } /// From 00295611359e1f2773817d61770d76e380181842 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 11 Jun 2015 23:36:35 +0200 Subject: [PATCH 028/102] AutoRespond improvements - Add improvements from pull request #76 - Add support for regexes instead of simple matches - Add support for internal MCC commands eg script - Add support for flexible INI file containing matches TODO: Testing, sample INI file, proper documentation --- MinecraftClient/ChatBots/AutoRespond.cs | 203 ++++++++++++++++++++++-- MinecraftClient/McTcpClient.cs | 2 +- MinecraftClient/Settings.cs | 23 ++- 3 files changed, 197 insertions(+), 31 deletions(-) diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 3a0d6575ac..52a149443e 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -1,38 +1,209 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; namespace MinecraftClient.ChatBots { + /// + /// This bot automatically runs actions when a user sends a message matching a specified rule + /// class AutoRespond : ChatBot { - private string[] respondon = new string[0]; - private string[] torespond = new string[0]; + private string matchesFile; + private List respondRules; + private static string header = "[AutoRespond] "; + + /// + /// Create a new AutoRespond bot + /// + /// INI File to load matches from + public AutoRespond(string matchesFile) + { + this.matchesFile = matchesFile; + } + + /// + /// Describe a respond rule based on a simple match or a regex + /// + private class RespondRule + { + private Regex regex; + private string match; + private string actionPublic; + private string actionPrivate; + + /// + /// Create a respond rule from a regex and a reponse message or command + /// + /// Regex + /// Internal command to run for public messages + /// Internal command to run for private messages + public RespondRule(Regex regex, string actionPublic, string actionPrivate) + { + this.regex = regex; + this.match = null; + this.actionPublic = actionPublic; + this.actionPrivate = actionPrivate; + } + + /// + /// Create a respond rule from a match string and a reponse message or command + /// + /// Match string + /// Internal command to run for public messages + /// Internal command to run for private messages + public RespondRule(string match, string actionPublic, string actionPrivate) + { + this.regex = null; + this.match = match; + this.actionPublic = actionPublic; + this.actionPrivate = actionPrivate; + } + + /// + /// Match the respond rule to the specified string and return a message or command to send if a match is detected + /// + /// Player who have sent the message + /// Message to match against the regex or match string + /// True if the provided message was sent privately eg with /tell + /// Internal command to run as a response to this user, or null if no match has been detected + public string Match(string username, string message, bool privateMsg) + { + if (regex != null) + { + if (regex.IsMatch(message)) + { + Match regexMatch = regex.Match(message); + string toSend = privateMsg ? actionPrivate : actionPublic; + for (int i = regexMatch.Groups.Count - 1; i >= 1; i--) + toSend = toSend.Replace("$" + i, regexMatch.Groups[i].Value); + toSend = toSend.Replace("$u", username); + return toSend; + } + } + else if (!String.IsNullOrEmpty(match)) + { + if (message.Contains(match)) + { + return (privateMsg + ? actionPrivate + : actionPublic).Replace("$u", username); + } + } + return null; + } + } - //Initalize the bot + /// + /// Initialize the AutoRespond bot from the matches file + /// public override void Initialize() { - respondon = LoadDistinctEntriesFromFile(Settings.Respond_MatchesFile); - torespond = LoadDistinctEntriesFromFile(Settings.Respond_RespondFile); - ConsoleIO.WriteLine("Auto Respond Bot Sucessfully loaded!"); + if (File.Exists(matchesFile)) + { + Regex matchRegex = null; + string matchString = null; + string matchAction = null; + string matchActionPrivate = null; + respondRules = new List(); + + foreach (string lineRAW in File.ReadAllLines(matchesFile)) + { + string line = lineRAW.Split('#')[0].Trim(); + if (line.Length > 0) + { + if (line[0] == '[' && line[line.Length - 1] == ']') + { + switch (line.Substring(1, line.Length - 2).ToLower()) + { + case "match": + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate); + matchRegex = null; + matchString = null; + matchAction = null; + matchActionPrivate = null; + break; + } + } + else + { + string argName = line.Split('=')[0]; + if (line.Length > (argName.Length + 1)) + { + string argValue = line.Substring(argName.Length + 1); + switch (argName.ToLower()) + { + case "regex": matchRegex = new Regex(argValue); break; + case "match": matchString = argValue; break; + case "action": matchAction = argValue; break; + case "actionprivate": matchAction = argValue; break; + } + } + } + } + } + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate); + } + else + { + LogToConsole("File not found: '" + matchesFile + "'"); + UnloadBot(); //No need to keep the bot active + } + } + + /// + /// Create a new respond rule from the provided arguments, only if they are valid: at least one match and one action + /// + /// Matching regex + /// Matching string + /// Action if the matching message is public + /// Action if the matching message is private + private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate) + { + if (matchAction != null || matchActionPrivate != null) + { + if (matchActionPrivate == null) + { + matchActionPrivate = matchAction; + } + + if (matchRegex != null) + { + respondRules.Add(new RespondRule(matchRegex, matchAction, matchActionPrivate)); + } + else if (matchString != null) + { + respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate)); + } + } } public override void GetText(string text) { //Remove colour codes text = getVerbatim(text).ToLower(); - //Check text to see if bot should respond - foreach (string alert in respondon.Where(alert => text.Contains(alert))) + + //Check if this is a valid message + string sender = "", message = ""; + bool chatMessage = isChatMessage(text, ref message, ref sender); + bool privateMessage = false; + if (!chatMessage) + privateMessage = isPrivateMessage(text, ref message, ref sender); + + //Process only chat messages sent by another user + if ((chatMessage || privateMessage) && sender != Settings.Username) { - //Find what to respond with - for (int x = 0; x < respondon.Length; x++) + foreach (RespondRule rule in respondRules) { - if (respondon[x].ToString().Contains(alert)) + string toPerform = rule.Match(sender, message, privateMessage); + if (toPerform != null) { - //Respond - SendText(torespond[x].ToString()); + string response = null; + LogToConsole(header + toPerform); + performInternalCommand(toPerform, ref response); + if (!String.IsNullOrEmpty(response)) + LogToConsole(header + response); } } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 3f152002e0..46b40c5934 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -106,7 +106,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv 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.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } - if (Settings.Respond_Enabled) { BotLoad(new ChatBots.AutoRespond()); } + if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); } } try diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 4c4779a70b..b7daa52413 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -90,16 +90,15 @@ public static class Settings public static bool RemoteCtrl_AutoTpaccept_Everyone = false; //Auto Respond - public static bool Respond_Enabled = false; - public static string Respond_MatchesFile = "detect.txt"; - public static string Respond_RespondFile = "respond.txt"; + public static bool AutoRespond_Enabled = false; + public static string AutoRespond_Matches = "matches.ini"; //Custom app variables and Minecraft accounts private static Dictionary AppVars = new Dictionary(); private static Dictionary> Accounts = new Dictionary>(); private static Dictionary> Servers = new Dictionary>(); - private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, Auto_Respond }; + private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, AutoRespond }; /// /// Load settings from the give INI file @@ -133,7 +132,7 @@ public static void LoadSettings(string settingsfile) case "remotecontrol": pMode = ParseMode.RemoteControl; break; case "proxy": pMode = ParseMode.Proxy; break; case "appvars": pMode = ParseMode.AppVars; break; - case "auto respond": pMode = ParseMode.Auto_Respond; break; + case "autorespond": pMode = ParseMode.AutoRespond; break; default: pMode = ParseMode.Default; break; } } @@ -317,12 +316,11 @@ public static void LoadSettings(string settingsfile) setVar(argName, argValue); break; - case ParseMode.Auto_Respond: + case ParseMode.AutoRespond: switch (argName.ToLower()) { - case "enabled": Respond_Enabled = str2bool(argValue); break; - case "matchfile": Respond_MatchesFile = argValue; break; - case "respondfile": Respond_RespondFile = argValue; break; + case "enabled": AutoRespond_Enabled = str2bool(argValue); break; + case "matchesfile": AutoRespond_Matches = argValue; break; } break; } @@ -422,12 +420,9 @@ public static void WriteDefaultSettings(string settingsfile) + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + "\r\n" - + "[Auto Respond]\r\n" + + "[AutoRespond]\r\n" + "enabled=false\r\n" - + "matchfile=detect.txt\r\n" - + "respondfile=respond.txt\r\n" - + "#To use the bot, place the text to detect in the matchfile file and the text to respond with in the respondfile\r\n" - + "#Each line in each file is relevant to the same line in the other document, for example if the bot detects the text in line 1 of the first file, it will respond with line 1 of the second file.\r\n", Encoding.UTF8); + + "matchesfile=matches.ini\r\n", Encoding.UTF8); } public static int str2int(string str) { try { return Convert.ToInt32(str); } catch { return 0; } } From 365af032adbeebc9bdc4b16da0594aba0db93b97 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 14 Jun 2015 21:43:24 +0200 Subject: [PATCH 029/102] Remove invalid disconnect packet ... use TCP connection closing instead. See #45 --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 362bbd41d0..432425f2e1 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -615,16 +615,12 @@ public bool SendRespawnPacket() /// /// 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) { } From c957ed0efd09cf27a69445a7baa92e7434021a3e Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 16 Jun 2015 10:59:18 +0200 Subject: [PATCH 030/102] Remove invalid disconnect packet (2) Forgot to apply the same change to Protocol17, see #45 --- MinecraftClient/Protocol/Handlers/Protocol17.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index a514ffc5cb..b8950cc727 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -508,18 +508,12 @@ public bool SendRespawnPacket() /// /// 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); + c.Close(); } catch (SocketException) { } catch (System.IO.IOException) { } From a7f0897f0986934352366c4c69b52b05b6053d9a Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 19 Jun 2015 18:42:24 +0200 Subject: [PATCH 031/102] Add 1.8.5 - 1.8.7 as supported versions + Improve wording: answer -> respon[d|se] --- MinecraftClient/Program.cs | 2 +- MinecraftClient/Protocol/Handlers/Protocol16.cs | 2 +- MinecraftClient/Protocol/ProtocolHandler.cs | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index f33790f49e..6fc954dec6 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -28,7 +28,7 @@ static class Program static void Main(string[] args) { - Console.WriteLine("Console Client for MC 1.4.6 to 1.8.4 - v" + Version + " - By ORelio & Contributors"); + Console.WriteLine("Console Client for MC 1.4.6 to 1.8.7 - v" + Version + " - By ORelio & Contributors"); //Basic Input/Output ? if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 740342f564..935ad3cd92 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -670,7 +670,7 @@ public static bool doPing(string host, int port, ref int protocolversion) { 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/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 9c4f3a2d82..7262da169d 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -36,7 +36,7 @@ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int pro { success = true; } - else ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)"); + else ConsoleIO.WriteLineFormatted("§8Unexpected response from the server (is that a Minecraft server?)"); } catch (Exception e) { @@ -118,6 +118,9 @@ public static int MCVer2ProtocolVersion(string MCVersion) case "1.8.2": case "1.8.3": case "1.8.4": + case "1.8.5": + case "1.8.6": + case "1.8.7": return 47; default: return 0; From 67affc62702a6e3f6bc37c8abaf148b0ad32c4d8 Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 19 Jun 2015 19:29:23 +0200 Subject: [PATCH 032/102] Fix 1.7+ server list ping by properly parsing Json Separate Json and ChatParser classes Use Json parser for retrieving Json fields Will avoid wrong "name" field from being used --- MinecraftClient/MinecraftClient.csproj | 1 + .../Protocol/Handlers/ChatParser.cs | 160 +--------------- MinecraftClient/Protocol/Handlers/Json.cs | 179 ++++++++++++++++++ .../Protocol/Handlers/Protocol17.cs | 21 +- 4 files changed, 202 insertions(+), 159 deletions(-) create mode 100644 MinecraftClient/Protocol/Handlers/Json.cs diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 8129d2ec62..a530522ca1 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -123,6 +123,7 @@ + diff --git a/MinecraftClient/Protocol/Handlers/ChatParser.cs b/MinecraftClient/Protocol/Handlers/ChatParser.cs index 7526ea4467..937cb72b3f 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -19,31 +19,7 @@ static class ChatParser 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), ""); } /// @@ -210,135 +186,27 @@ private static string TranslateString(string rulename, List using_data) 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")) @@ -352,7 +220,7 @@ private static string JSONData2String(JSONData data, string colorcode) 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)); @@ -362,29 +230,21 @@ private static string JSONData2String(JSONData data, string colorcode) } 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/Json.cs b/MinecraftClient/Protocol/Handlers/Json.cs new file mode 100644 index 0000000000..c95fdc890e --- /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/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index b8950cc727..d32150685f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -580,18 +580,21 @@ public static bool doPing(string host, int port, ref int protocolversion) if (ComTmp.readNextVarInt() == 0x00) //Read Packet ID { string result = ComTmp.readNextString(); //Get the Json data - if (result[0] == '{' && result.Contains("protocol\":") && result.Contains("name\":\"")) + if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) { - 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) + Json.JSONData jsonData = Json.ParseJson(result); + if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) { - protocolversion = atoi(tmp_ver[1]); + jsonData = jsonData.Properties["version"]; + + //Retrieve display name of the Minecraft version + if (jsonData.Properties.ContainsKey("name")) + version = jsonData.Properties["name"].StringValue; + + //Retrieve protocol version number for handling this server + if (jsonData.Properties.ContainsKey("protocol")) + protocolversion = atoi(jsonData.Properties["protocol"].StringValue); - //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"); From 3224c59eab79e1121e2e1724157a1bb32ff40697 Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 19 Jun 2015 19:40:18 +0200 Subject: [PATCH 033/102] Remove padding mechanism Not needed anymore since proper encryption is now used under Mono --- MinecraftClient/Crypto/CryptoHandler.cs | 5 ++-- MinecraftClient/Crypto/IPaddingProvider.cs | 17 ----------- .../Crypto/Streams/MonoAesStream.cs | 4 +-- MinecraftClient/MinecraftClient.csproj | 1 - .../Protocol/Handlers/Protocol16.cs | 22 +------------- .../Protocol/Handlers/Protocol17.cs | 28 +----------------- .../Protocol/Handlers/Protocol18.cs | 29 +------------------ MinecraftClient/Protocol/IMinecraftCom.cs | 2 +- 8 files changed, 7 insertions(+), 101 deletions(-) delete mode 100644 MinecraftClient/Crypto/IPaddingProvider.cs diff --git a/MinecraftClient/Crypto/CryptoHandler.cs b/MinecraftClient/Crypto/CryptoHandler.cs index 7d31e16279..ae46ccf581 100644 --- a/MinecraftClient/Crypto/CryptoHandler.cs +++ b/MinecraftClient/Crypto/CryptoHandler.cs @@ -197,14 +197,13 @@ private static byte[] TwosComplementLittleEndian(byte[] p) /// /// 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 4829049464..0000000000 --- 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/MonoAesStream.cs b/MinecraftClient/Crypto/Streams/MonoAesStream.cs index 430483b6ab..57d5598ea4 100644 --- a/MinecraftClient/Crypto/Streams/MonoAesStream.cs +++ b/MinecraftClient/Crypto/Streams/MonoAesStream.cs @@ -19,15 +19,13 @@ namespace MinecraftClient.Crypto.Streams public class MonoAesStream : Stream, IAesStream { - IPaddingProvider pad; CipherStream cstream; - public MonoAesStream(System.IO.Stream stream, byte[] key, IPaddingProvider provider) + public MonoAesStream(System.IO.Stream stream, byte[] key) { BaseStream = stream; BufferedBlockCipher enc = GenerateAES(key, true); BufferedBlockCipher dec = GenerateAES(key, false); cstream = new CipherStream(stream, dec, enc); - pad = provider; } public System.IO.Stream BaseStream { get; set; } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index a530522ca1..0ef5d9ec57 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -127,7 +127,6 @@ - diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 935ad3cd92..38686d174b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -42,19 +42,11 @@ private Protocol16Handler(TcpClient Client) 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()); } @@ -504,7 +496,7 @@ private bool StartEncryption(string uuid, string username, string sessionID, byt if (pid[0] == 0xFC) { readData(4); - s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this); + s = CryptoHandler.getAesStream(c.GetStream(), secretKey); encrypted = true; return true; } @@ -652,18 +644,6 @@ private static byte[] concatBytes(params byte[][] bytes) 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 diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index d32150685f..b7c08e37e0 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -45,19 +45,11 @@ private Protocol17Handler(TcpClient Client) 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()); } @@ -431,7 +423,7 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string Send(encryption_response_tosend); //Start client-side encryption - s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this); + s = CryptoHandler.getAesStream(c.GetStream(), secretKey); encrypted = true; //Read and skip the next packet @@ -444,24 +436,6 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string 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 /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 432425f2e1..67fb96ca9d 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -46,19 +46,11 @@ private Protocol18Handler(TcpClient Client) 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()); } @@ -531,7 +523,7 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string SendPacket(0x01, concatBytes(key_len, key_enc, token_len, 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 @@ -555,25 +547,6 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string } } - /// - /// 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 /// diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index d35a1fd737..2ae82c1404 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -13,7 +13,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 From 3ce91188c7bb16e3882315f832f69f936b18dec7 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 20 Jun 2015 22:58:18 +0200 Subject: [PATCH 034/102] Add support for C# scripts in scripting bot - Now scripts can also be written in C# - C# scripts can access ChatBot API - Add more methods in ChatBot API - Add an example of C# script file - Coding style fixes: method names ucfirst --- MinecraftClient/ChatBot.cs | 118 ++++++++++--- MinecraftClient/ChatBots/Alerts.cs | 2 +- MinecraftClient/ChatBots/AutoRelog.cs | 2 +- MinecraftClient/ChatBots/AutoRespond.cs | 8 +- MinecraftClient/ChatBots/ChatLog.cs | 8 +- MinecraftClient/ChatBots/HangmanGame.cs | 6 +- MinecraftClient/ChatBots/PlayerListLogger.cs | 2 +- MinecraftClient/ChatBots/RemoteControl.cs | 8 +- MinecraftClient/ChatBots/Script.cs | 167 ++++++++++++++---- MinecraftClient/ChatBots/TestBot.cs | 6 +- MinecraftClient/Commands/Connect.cs | 4 +- MinecraftClient/Commands/List.cs | 2 +- MinecraftClient/Commands/Reco.cs | 2 +- MinecraftClient/Commands/Set.cs | 2 +- MinecraftClient/McTcpClient.cs | 25 +-- MinecraftClient/Program.cs | 16 +- .../Protocol/Handlers/Protocol16.cs | 2 +- .../Protocol/Handlers/Protocol17.cs | 8 +- .../Protocol/Handlers/Protocol18.cs | 8 +- .../Protocol/IMinecraftComHandler.cs | 12 +- MinecraftClient/Settings.cs | 27 ++- MinecraftClient/config/sample-script.cs | 15 ++ 22 files changed, 318 insertions(+), 132 deletions(-) create mode 100644 MinecraftClient/config/sample-script.cs diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 579430cc7e..59fff3cd56 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.IO; +using System.Threading; namespace MinecraftClient { @@ -33,8 +34,11 @@ public abstract class ChatBot public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost }; //Will be automatically set on bot loading, don't worry about this - public void SetHandler(McTcpClient handler) { this.handler = handler; } - private McTcpClient handler; + public void SetHandler(McTcpClient handler) { this._handler = handler; } + public void SetMaster(ChatBot master) { this.master = master; } + private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } } + private McTcpClient _handler = null; + private ChatBot master = null; /* ================================================== */ /* Main methods to override for creating your bot */ @@ -80,10 +84,12 @@ public virtual void GetText(string text) { } /// Text to send to the server /// True if the text was sent with no error - protected bool SendText(string text) + protected bool SendText(object text) { LogToConsole("Sending '" + text + "'"); - return handler.SendText(text); + bool result = Handler.SendText(text is string ? (string)text : text.ToString()); + Thread.Sleep(1000); + return result; } /// @@ -92,10 +98,10 @@ protected bool SendText(string text) /// The command to process /// TRUE if the command was indeed an internal MCC command - protected bool performInternalCommand(string command) + protected bool PerformInternalCommand(string command) { string temp = ""; - return handler.performInternalCommand(command, ref temp); + return Handler.PerformInternalCommand(command, ref temp); } /// @@ -105,16 +111,16 @@ protected bool performInternalCommand(string command) /// May contain a confirmation or error message after processing the command, or "" otherwise. /// TRUE if the command was indeed an internal MCC command - protected bool performInternalCommand(string command, ref string response_msg) + protected bool PerformInternalCommand(string command, ref string response_msg) { - return handler.performInternalCommand(command, ref response_msg); + return Handler.PerformInternalCommand(command, ref response_msg); } /// /// Remove color codes ("§c") from a text message received from the server /// - protected static string getVerbatim(string text) + protected static string GetVerbatim(string text) { if ( String.IsNullOrEmpty(text) ) return String.Empty; @@ -135,7 +141,7 @@ protected static string getVerbatim(string text) /// Verify that a string contains only a-z A-Z 0-9 and _ characters. /// - protected static bool isValidName(string username) + protected static bool IsValidName(string username) { if ( String.IsNullOrEmpty(username) ) return false; @@ -158,9 +164,9 @@ protected static bool isValidName(string username) /// if it's a private message, this will contain the player name that sends the message /// Returns true if the text is a private message - protected static bool isPrivateMessage(string text, ref string message, ref string sender) + protected static bool IsPrivateMessage(string text, ref string message, ref string sender) { - text = getVerbatim(text); + text = GetVerbatim(text); if (text == "") { return false; } string[] tmp = text.Split(' '); @@ -177,7 +183,7 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri } else message = text.Substring(tmp[0].Length + 10); //MC 1.5 sender = tmp[0]; - return isValidName(sender); + return IsValidName(sender); } //Detect Essentials (Bukkit) /m messages @@ -189,7 +195,7 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1); sender = tmp[0].Substring(1); if (sender[0] == '~') { sender = sender.Substring(1); } - return isValidName(sender); + return IsValidName(sender); } //Detect Essentials (Bukkit) /me messages with some custom rank @@ -201,7 +207,7 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); sender = tmp[0].Substring(1); if (sender[0] == '~') { sender = sender.Substring(1); } - return isValidName(sender); + return IsValidName(sender); } //Detect HeroChat PMsend @@ -210,7 +216,7 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri { sender = text.Substring(5).Split(':')[0]; message = text.Substring(text.IndexOf(':') + 2); - return isValidName(sender); + return IsValidName(sender); } else return false; @@ -226,10 +232,10 @@ protected static bool isPrivateMessage(string text, ref string message, ref stri /// if it's message, this will contain the player name that sends the message /// Returns true if the text is a chat message - protected static bool isChatMessage(string text, ref string message, ref string sender) + protected static bool IsChatMessage(string text, ref string message, ref string sender) { - text = getVerbatim(text); + text = GetVerbatim(text); string[] tmp = text.Split(' '); if (text.Length > 0) { @@ -251,7 +257,7 @@ protected static bool isChatMessage(string text, ref string message, ref string tmp2 = sender.Split(' '); sender = tmp2[tmp2.Length - 1]; if (sender[0] == '~') { sender = sender.Substring(1); } - return isValidName(sender); + return IsValidName(sender); } catch (IndexOutOfRangeException) { return false; } } @@ -264,7 +270,7 @@ protected static bool isChatMessage(string text, ref string message, ref string int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; sender = text.Substring(name_start, name_end - name_start); message = text.Substring(name_end + 2); - return isValidName(sender); + return IsValidName(sender); } } return false; @@ -277,14 +283,14 @@ protected static bool isChatMessage(string text, ref string message, ref string /// Will contain the sender's username, if it's a teleport request /// Returns true if the text is a teleport request - protected static bool isTeleportRequest(string text, ref string sender) + protected static bool IsTeleportRequest(string text, ref string sender) { - text = getVerbatim(text); + text = GetVerbatim(text); sender = text.Split(' ')[0]; if (text.EndsWith("has requested to teleport to you.") || text.EndsWith("has requested that you teleport to them.")) { - return isValidName(sender); + return IsValidName(sender); } else return false; } @@ -294,10 +300,10 @@ protected static bool isTeleportRequest(string text, ref string sender) /// /// Log text to write - public static void LogToConsole(string text) + public static void LogToConsole(object text) { ConsoleIO.WriteLineFormatted("§8[BOT] " + text); - string logfile = Settings.expandVars(Settings.chatbotLogFile); + string logfile = Settings.ExpandVars(Settings.chatbotLogFile); if (!String.IsNullOrEmpty(logfile)) { @@ -309,7 +315,7 @@ public static void LogToConsole(string text) catch { return; /* Invalid file name or access denied */ } } - File.AppendAllLines(logfile, new string[] { getTimestamp() + ' ' + text }); + File.AppendAllLines(logfile, new string[] { GetTimestamp() + ' ' + text }); } } @@ -340,7 +346,7 @@ protected void DisconnectAndExit() protected void UnloadBot() { - handler.BotUnLoad(this); + Handler.BotUnLoad(this); } /// @@ -362,14 +368,14 @@ protected void SendPrivateMessage(string player, string message) protected void RunScript(string filename, string playername = "") { - handler.BotLoad(new ChatBots.Script(filename, playername)); + Handler.BotLoad(new ChatBots.Script(filename, playername)); } /// /// Get a Y-M-D h:m:s timestamp representing the current system date and time /// - protected static string getTimestamp() + protected static string GetTimestamp() { DateTime time = DateTime.Now; return String.Format("{0}-{1}-{2} {3}:{4}:{5}", @@ -404,5 +410,59 @@ protected static string[] LoadDistinctEntriesFromFile(string file) return new string[0]; } } + + /// + /// Set a custom %variable% which will be available through expandVars() + /// + /// Name of the variable + /// Value of the variable + /// True if the parameters were valid + + protected static bool SetVar(string varName, object varData) + { + return Settings.SetVar(varName, varData.ToString()); + } + + /// + /// Get a custom %variable% or null if the variable does not exist + /// + /// Variable name + /// The value or null if the variable does not exists + + protected static string GetVar(string varName) + { + return Settings.GetVar(varName); + } + + /// + /// Get a custom %variable% as an Integer or null if the variable does not exist + /// + /// Variable name + /// The value or null if the variable does not exists + + protected static int GetVarAsInt(string varName) + { + return Settings.str2int(Settings.GetVar(varName)); + } + + /// + /// Load login/password using an account alias + /// + /// True if the account was found and loaded + + protected static bool SetAccount(string accountAlias) + { + return Settings.SetAccount(accountAlias); + } + + /// + /// Load server information in ServerIP and ServerPort variables from a "serverip:port" couple or server alias + /// + /// True if the server IP was valid and loaded, false otherwise + + protected static bool SetServerIP(string server) + { + return Settings.SetServerIP(server); + } } } diff --git a/MinecraftClient/ChatBots/Alerts.cs b/MinecraftClient/ChatBots/Alerts.cs index cd21bae4cb..9ad12b2bd3 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -30,7 +30,7 @@ public override void Initialize() public override void GetText(string text) { //Remove color codes and convert to lowercase - text = getVerbatim(text).ToLower(); + text = GetVerbatim(text).ToLower(); //Proceed only if no exclusions are found in text if (!excludelist.Any(exclusion => text.Contains(exclusion))) diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs index 19d831f50c..d98e30d5c3 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -47,7 +47,7 @@ public override void Initialize() public override bool OnDisconnect(DisconnectReason reason, string message) { - message = getVerbatim(message); + message = GetVerbatim(message); string comp = message.ToLower(); foreach (string msg in dictionary) { diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 52a149443e..567b38b0e8 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -182,14 +182,14 @@ private void CheckAddMatch(Regex matchRegex, string matchString, string matchAct public override void GetText(string text) { //Remove colour codes - text = getVerbatim(text).ToLower(); + text = GetVerbatim(text).ToLower(); //Check if this is a valid message string sender = "", message = ""; - bool chatMessage = isChatMessage(text, ref message, ref sender); + bool chatMessage = IsChatMessage(text, ref message, ref sender); bool privateMessage = false; if (!chatMessage) - privateMessage = isPrivateMessage(text, ref message, ref sender); + privateMessage = IsPrivateMessage(text, ref message, ref sender); //Process only chat messages sent by another user if ((chatMessage || privateMessage) && sender != Settings.Username) @@ -201,7 +201,7 @@ public override void GetText(string text) { string response = null; LogToConsole(header + toPerform); - performInternalCommand(toPerform, ref response); + PerformInternalCommand(toPerform, ref response); if (!String.IsNullOrEmpty(response)) LogToConsole(header + response); } diff --git a/MinecraftClient/ChatBots/ChatLog.cs b/MinecraftClient/ChatBots/ChatLog.cs index 7d4acc078b..5446825337 100644 --- a/MinecraftClient/ChatBots/ChatLog.cs +++ b/MinecraftClient/ChatBots/ChatLog.cs @@ -69,15 +69,15 @@ public static MessageFilter str2filter(string filtername) public override void GetText(string text) { - text = getVerbatim(text); + text = GetVerbatim(text); string sender = ""; string message = ""; - if (saveChat && isChatMessage(text, ref message, ref sender)) + if (saveChat && IsChatMessage(text, ref message, ref sender)) { save("Chat " + sender + ": " + message); } - else if (savePrivate && isPrivateMessage(text, ref message, ref sender)) + else if (savePrivate && IsPrivateMessage(text, ref message, ref sender)) { save("Private " + sender + ": " + message); } @@ -90,7 +90,7 @@ public override void GetText(string text) private void save(string tosave) { if (dateandtime) - tosave = getTimestamp() + ' ' + tosave; + tosave = GetTimestamp() + ' ' + tosave; string directory = Path.GetDirectoryName(logfile); if (!String.IsNullOrEmpty(directory) && !Directory.Exists(directory)) diff --git a/MinecraftClient/ChatBots/HangmanGame.cs b/MinecraftClient/ChatBots/HangmanGame.cs index 9b11cc2c74..0ea3c2ea7c 100644 --- a/MinecraftClient/ChatBots/HangmanGame.cs +++ b/MinecraftClient/ChatBots/HangmanGame.cs @@ -52,9 +52,9 @@ public override void GetText(string text) { string message = ""; string username = ""; - text = getVerbatim(text); + text = GetVerbatim(text); - if (isPrivateMessage(text, ref message, ref username)) + if (IsPrivateMessage(text, ref message, ref username)) { if (Settings.Bots_Owners.Contains(username.ToLower())) { @@ -73,7 +73,7 @@ public override void GetText(string text) } else { - if (running && isChatMessage(text, ref message, ref username)) + if (running && IsChatMessage(text, ref message, ref username)) { if (message.Length == 1) { diff --git a/MinecraftClient/ChatBots/PlayerListLogger.cs b/MinecraftClient/ChatBots/PlayerListLogger.cs index 9124b66478..1d1c5cb98f 100644 --- a/MinecraftClient/ChatBots/PlayerListLogger.cs +++ b/MinecraftClient/ChatBots/PlayerListLogger.cs @@ -46,7 +46,7 @@ public override void GetText(string text) LogToConsole("Saving Player List"); DateTime now = DateTime.Now; string TimeStamp = "[" + now.Year + '/' + now.Month + '/' + now.Day + ' ' + now.Hour + ':' + now.Minute + ']'; - System.IO.File.AppendAllText(file, TimeStamp + "\n" + getVerbatim(text) + "\n\n"); + System.IO.File.AppendAllText(file, TimeStamp + "\n" + GetVerbatim(text) + "\n\n"); } } } diff --git a/MinecraftClient/ChatBots/RemoteControl.cs b/MinecraftClient/ChatBots/RemoteControl.cs index dfdf5d2a35..300beb02ff 100644 --- a/MinecraftClient/ChatBots/RemoteControl.cs +++ b/MinecraftClient/ChatBots/RemoteControl.cs @@ -13,19 +13,19 @@ public class RemoteControl : ChatBot { public override void GetText(string text) { - text = getVerbatim(text); + text = GetVerbatim(text); string command = "", sender = ""; - if (isPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower().Trim())) + if (IsPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower().Trim())) { string response = ""; - performInternalCommand(command, ref response); + PerformInternalCommand(command, ref response); if (response.Length > 0) { SendPrivateMessage(sender, response); } } else if (Settings.RemoteCtrl_AutoTpaccept - && isTeleportRequest(text, ref sender) + && IsTeleportRequest(text, ref sender) && (Settings.RemoteCtrl_AutoTpaccept_Everyone || Settings.Bots_Owners.Contains(sender.ToLower().Trim()))) { SendText("/tpaccept"); diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs index cb96ffa8af..44ae5891fe 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; +using Microsoft.CSharp; +using System.CodeDom.Compiler; namespace MinecraftClient.ChatBots { @@ -14,9 +17,11 @@ public class Script : ChatBot private string file; private string[] lines = new string[0]; private int sleepticks = 10; - private int sleepticks_interval = 10; private int nextline = 0; private string owner; + private bool csharp; + private Thread thread; + private ManualResetEvent tpause; public Script(string filename) { @@ -38,10 +43,13 @@ public static bool lookForScript(ref string filename) { filename, filename + ".txt", + filename + ".cs", "scripts" + dir_slash + filename, "scripts" + dir_slash + filename + ".txt", + "scripts" + dir_slash + filename + ".cs", "config" + dir_slash + filename, "config" + dir_slash + filename + ".txt", + "config" + dir_slash + filename + ".cs", }; foreach (string possible_file in files) @@ -62,63 +70,152 @@ public override void Initialize() if (lookForScript(ref file)) { lines = System.IO.File.ReadAllLines(file); - if (owner != null) { SendPrivateMessage(owner, "Script '" + file + "' loaded."); } + csharp = file.EndsWith(".cs"); + thread = null; + + if (owner != null) + SendPrivateMessage(owner, "Script '" + file + "' loaded."); } else { LogToConsole("File not found: '" + file + "'"); + if (owner != null) SendPrivateMessage(owner, "File not found: '" + file + "'"); + UnloadBot(); //No need to keep the bot active } } public override void Update() { - if (sleepticks > 0) { sleepticks--; } - else + if (csharp) //C# compiled script { - if (nextline < lines.Length) //Is there an instruction left to interpret? + //Initialize thread on first update + if (thread == null) { - string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line - nextline++; //Move the cursor so that the next time the following line will be interpreted - sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming + tpause = new ManualResetEvent(false); + thread = new Thread(() => + { + if (!RunCSharpScript(String.Join("\n", lines), file, tpause) && owner != null) + SendPrivateMessage(owner, "Script '" + file + "' failed to run."); + }); + thread.Start(); + } - if (instruction_line.Length > 1) + //Let the thread run for a short span of time + if (thread != null) + { + tpause.Set(); + tpause.Reset(); + if (thread.Join(100)) + UnloadBot(); + } + } + else //Classic MCC script interpreter + { + if (sleepticks > 0) { sleepticks--; } + else + { + if (nextline < lines.Length) //Is there an instruction left to interpret? { - if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/') + string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line + nextline++; //Move the cursor so that the next time the following line will be interpreted + + if (instruction_line.Length > 1) { - instruction_line = Settings.expandVars(instruction_line); - string instruction_name = instruction_line.Split(' ')[0]; - switch (instruction_name.ToLower()) + if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/') { - case "wait": - int ticks = 10; - try - { - ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5)); - } - catch { } - sleepticks = ticks; - break; - default: - if (!performInternalCommand(instruction_line)) - { - sleepticks = 0; Update(); //Unknown command : process next line immediately - } - else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); } - break; + instruction_line = Settings.ExpandVars(instruction_line); + string instruction_name = instruction_line.Split(' ')[0]; + switch (instruction_name.ToLower()) + { + case "wait": + int ticks = 10; + try + { + ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5)); + } + catch { } + sleepticks = ticks; + break; + default: + if (!PerformInternalCommand(instruction_line)) + { + Update(); //Unknown command : process next line immediately + } + else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); } + break; + } } + else { Update(); } //Comment: process next line immediately } - else { sleepticks = 0; Update(); } //Comment: process next line immediately + } + else + { + //No more instructions to interpret + UnloadBot(); } } - else - { - //No more instructions to interpret - UnloadBot(); - } } } + + private bool RunCSharpScript(string script, string filename = "C# Script", ManualResetEvent tpause = null) + { + //Script compatibility check for handling future versions differently + if (!script.ToLower().StartsWith("//mccscript 1.0")) + { + ConsoleIO.WriteLineFormatted("§8Script file '" + filename + "' does not start with a valid //MCCScript comment."); + return false; + } + + //Create a simple ChatBot class from the given script, allowing access to ChatBot API + string code = String.Join("\n", new string[] + { + "using System;", + "using System.IO;", + "using System.Threading;", + "using MinecraftClient;", + "namespace ScriptLoader {", + "public class Script : ChatBot {", + "public void Run(ChatBot master, ManualResetEvent tpause) {", + "SetMaster(master);", + tpause != null + ? script.Replace(";\n", ";\ntpause.WaitOne();\n") + : script, + "}}}", + }); + + //Compile the C# class in memory using all the currently loaded assemblies + CSharpCodeProvider compiler = new CSharpCodeProvider(); + CompilerParameters parameters = new CompilerParameters(); + parameters.ReferencedAssemblies + .AddRange(AppDomain.CurrentDomain + .GetAssemblies() + .Where(a => !a.IsDynamic) + .Select(a => a.Location).ToArray()); + parameters.CompilerOptions = "/t:library"; + parameters.GenerateInMemory = true; + CompilerResults result + = compiler.CompileAssemblyFromSource(parameters, code); + + //Process compile warnings and errors + if (result.Errors.Count > 0) + { + ConsoleIO.WriteLineFormatted("§8Error loading '" + filename + "':\n" + result.Errors[0].ErrorText); + return false; + } + + //Run the compiled script with exception handling + object compiledScript = result.CompiledAssembly.CreateInstance("ScriptLoader.Script"); + try { compiledScript.GetType().GetMethod("Run").Invoke(compiledScript, new object[] { this, tpause }); } + catch (Exception e) + { + ConsoleIO.WriteLineFormatted("§8Runtime error for '" + filename + "':\n" + e); + return false; + } + + return true; + } } } diff --git a/MinecraftClient/ChatBots/TestBot.cs b/MinecraftClient/ChatBots/TestBot.cs index ffd921f8b5..ce6ef8f781 100644 --- a/MinecraftClient/ChatBots/TestBot.cs +++ b/MinecraftClient/ChatBots/TestBot.cs @@ -15,13 +15,13 @@ public override void GetText(string text) { string message = ""; string username = ""; - text = getVerbatim(text); + text = GetVerbatim(text); - if (isPrivateMessage(text, ref message, ref username)) + if (IsPrivateMessage(text, ref message, ref username)) { ConsoleIO.WriteLine("Bot: " + username + " told me : " + message); } - else if (isChatMessage(text, ref message, ref username)) + else if (IsChatMessage(text, ref message, ref username)) { ConsoleIO.WriteLine("Bot: " + username + " said : " + message); } diff --git a/MinecraftClient/Commands/Connect.cs b/MinecraftClient/Commands/Connect.cs index 3d57dbd395..4e92ecc928 100644 --- a/MinecraftClient/Commands/Connect.cs +++ b/MinecraftClient/Commands/Connect.cs @@ -17,13 +17,13 @@ public override string Run(McTcpClient handler, string command) string[] args = getArgs(command); if (args.Length > 1) { - if (!Settings.setAccount(args[1])) + if (!Settings.SetAccount(args[1])) { return "Unknown account '" + args[1] + "'."; } } - if (Settings.setServerIP(args[0])) + if (Settings.SetServerIP(args[0])) { Program.Restart(); return ""; diff --git a/MinecraftClient/Commands/List.cs b/MinecraftClient/Commands/List.cs index 116085d037..da9368e0de 100644 --- a/MinecraftClient/Commands/List.cs +++ b/MinecraftClient/Commands/List.cs @@ -12,7 +12,7 @@ public class List : Command public override string Run(McTcpClient handler, string command) { - return "PlayerList: " + String.Join(", ", handler.getOnlinePlayers()); + return "PlayerList: " + String.Join(", ", handler.GetOnlinePlayers()); } } } diff --git a/MinecraftClient/Commands/Reco.cs b/MinecraftClient/Commands/Reco.cs index 520f4fea20..49e18e76fb 100644 --- a/MinecraftClient/Commands/Reco.cs +++ b/MinecraftClient/Commands/Reco.cs @@ -15,7 +15,7 @@ public override string Run(McTcpClient handler, string command) string[] args = getArgs(command); if (args.Length > 0) { - if (!Settings.setAccount(args[0])) + if (!Settings.SetAccount(args[0])) { return "Unknown account '" + args[0] + "'."; } diff --git a/MinecraftClient/Commands/Set.cs b/MinecraftClient/Commands/Set.cs index 9316077a5e..5fc2adabf7 100644 --- a/MinecraftClient/Commands/Set.cs +++ b/MinecraftClient/Commands/Set.cs @@ -17,7 +17,7 @@ public override string Run(McTcpClient handler, string command) string[] temp = getArg(command).Split('='); if (temp.Length > 1) { - if (Settings.setVar(temp[0], getArg(command).Substring(temp[0].Length + 1))) + if (Settings.SetVar(temp[0], getArg(command).Substring(temp[0].Length + 1))) { return ""; //Success } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 46b40c5934..769c2d0295 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -34,11 +34,11 @@ public class McTcpClient : IMinecraftComHandler 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; } TcpClient client; IMinecraftCom handler; @@ -101,10 +101,10 @@ private void StartClient(string user, string uuid, string sessionID, string serv 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)); } } @@ -209,7 +209,7 @@ private void CommandPrompt() { 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); } @@ -234,7 +234,7 @@ private void CommandPrompt() /// 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 */ @@ -314,7 +314,8 @@ public void Disconnect() Thread.Sleep(1000); - if (client != null) { client.Close(); } + if (client != null) + client.Close(); } /// @@ -476,7 +477,7 @@ public void OnPlayerLeave(Guid uuid) /// /// Online player names - public string[] getOnlinePlayers() + public string[] GetOnlinePlayers() { lock (onlinePlayers) { diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 6fc954dec6..2beabf47d2 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -63,7 +63,7 @@ static void Main(string[] args) Settings.Password = args[1]; if (args.Length >= 3) { - Settings.setServerIP(args[2]); + Settings.SetServerIP(args[2]); //Single command? if (args.Length >= 4) @@ -77,7 +77,7 @@ static void Main(string[] args) if (Settings.ConsoleTitle != "") { Settings.Username = "New Window"; - Console.Title = Settings.expandVars(Settings.ConsoleTitle); + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); } //Asking the user to type in missing data such as Username and Password @@ -130,7 +130,7 @@ private static void InitializeClient() if (result == ProtocolHandler.LoginResult.Success) { if (Settings.ConsoleTitle != "") - Console.Title = Settings.expandVars(Settings.ConsoleTitle); + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); if (Settings.playerHeadAsIcon) ConsoleIcon.setPlayerIconAsync(Settings.Username); @@ -140,7 +140,7 @@ private static void InitializeClient() if (Settings.ServerIP == "") { Console.Write("Server IP : "); - Settings.setServerIP(Console.ReadLine()); + Settings.SetServerIP(Console.ReadLine()); } //Get server version @@ -186,7 +186,7 @@ private static void InitializeClient() //Update console title if (Settings.ConsoleTitle != "") - Console.Title = Settings.expandVars(Settings.ConsoleTitle); + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); } catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); } } @@ -306,15 +306,15 @@ public static void HandleFailure(string errorMessage = null, bool versionError = if (command.StartsWith("reco")) { - message = new Commands.Reco().Run(null, Settings.expandVars(command)); + message = new Commands.Reco().Run(null, Settings.ExpandVars(command)); } else if (command.StartsWith("connect")) { - message = new Commands.Connect().Run(null, Settings.expandVars(command)); + 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)); + message = new Commands.Exit().Run(null, Settings.ExpandVars(command)); } else if (command.StartsWith("help")) { diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 38686d174b..4acf444785 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -505,7 +505,7 @@ private bool StartEncryption(string uuid, string username, string sessionID, byt 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 diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index b7c08e37e0..7d74e80bd2 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -349,16 +349,16 @@ 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_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[] 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_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); @@ -377,7 +377,7 @@ public bool Login() string serverID = readNextString(); byte[] Serverkey = readNextByteArray(); byte[] token = readNextByteArray(); - return StartEncryption(handler.getUserUUID(), handler.getSessionID(), token, serverID, Serverkey); + return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); } else if (pid == 0x02) //Login successful { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 67fb96ca9d..509bfb925a 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -449,15 +449,15 @@ private void SendRAW(byte[] buffer) 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()); 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); 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); @@ -478,7 +478,7 @@ public bool Login() string serverID = readNextString(ref packetData); byte[] Serverkey = readNextByteArray(ref packetData); byte[] token = readNextByteArray(ref packetData); - return StartEncryption(handler.getUserUUID(), handler.getSessionID(), token, serverID, Serverkey); + return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); } else if (packetID == 0x02) //Login successful { diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index f74d22a4b6..79f8659e9f 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -16,12 +16,12 @@ public interface IMinecraftComHandler /* The MinecraftCom Hanler 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(); /// /// This method is called when the protocol handler receives a chat message diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index b7daa52413..735de5cd61 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -149,7 +149,7 @@ public static void LoadSettings(string settingsfile) { 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; @@ -204,7 +204,7 @@ public static void LoadSettings(string settingsfile) 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); } @@ -313,7 +313,7 @@ public static void LoadSettings(string settingsfile) break; case ParseMode.AppVars: - setVar(argName, argValue); + SetVar(argName, argValue); break; case ParseMode.AutoRespond: @@ -433,7 +433,7 @@ public static void WriteDefaultSettings(string settingsfile) /// /// 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)) @@ -450,7 +450,7 @@ public static bool setAccount(string accountAlias) /// /// 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(':'); @@ -489,7 +489,7 @@ public static bool setServerIP(string server) /// Value of the variable /// True if the parameters were valid - public static bool setVar(string varName, string varData) + public static bool SetVar(string varName, string varData) { varName = new string(varName.TakeWhile(char.IsLetterOrDigit).ToArray()).ToLower(); if (varName.Length > 0) @@ -500,13 +500,26 @@ public static bool setVar(string varName, string varData) 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 string GetVar(string varName) + { + if (AppVars.ContainsKey(varName)) + return AppVars[varName]; + return null; + } + /// /// Replace %variables% with their value /// /// 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++) diff --git a/MinecraftClient/config/sample-script.cs b/MinecraftClient/config/sample-script.cs new file mode 100644 index 0000000000..6240c46a71 --- /dev/null +++ b/MinecraftClient/config/sample-script.cs @@ -0,0 +1,15 @@ +//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 all methods provided by the bot API */ + +for (int i = 0; i < 5; i++) +{ + int count = GetVarAsInt("test"); + count++; + SetVar("test", count); + SendText("Hello World no. " + count); + PerformInternalCommand("log Sleeping for 5 seconds..."); + Thread.Sleep(5000); +} \ No newline at end of file From a6b3bf0481d86c87aa99577858df9649179f0028 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 21 Jun 2015 16:40:13 +0200 Subject: [PATCH 035/102] AutoRespond tests and fixes - Automatically add [BotName] tags to log lines - Fix case handling and actionPrivate used for public messages - Add a sample file for basic and regex matches --- MinecraftClient/ChatBot.cs | 8 +++--- MinecraftClient/ChatBots/AutoRespond.cs | 11 ++++---- MinecraftClient/Commands/Log.cs | 2 +- MinecraftClient/ConsoleIO.cs | 10 +++++++ MinecraftClient/McTcpClient.cs | 2 +- MinecraftClient/config/sample-matches.ini | 32 +++++++++++++++++++++++ 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 MinecraftClient/config/sample-matches.ini diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 59fff3cd56..a3d57840ad 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -296,13 +296,13 @@ protected static bool IsTeleportRequest(string text, ref string sender) } /// - /// Writes some text in the console. Nothing will be sent to the server. + /// Write some text in the console. Nothing will be sent to the server. /// /// Log text to write - public static void LogToConsole(object text) + public void LogToConsole(object text) { - ConsoleIO.WriteLineFormatted("§8[BOT] " + text); + ConsoleIO.WriteLogLine(String.Format("[{0}] {1}", this.GetType().Name, text)); string logfile = Settings.ExpandVars(Settings.chatbotLogFile); if (!String.IsNullOrEmpty(logfile)) @@ -393,7 +393,7 @@ protected static string GetTimestamp() /// File to load /// The string array or an empty array if failed to load the file - protected static string[] LoadDistinctEntriesFromFile(string file) + protected string[] LoadDistinctEntriesFromFile(string file) { if (File.Exists(file)) { diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 567b38b0e8..19146854ae 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -12,7 +12,6 @@ class AutoRespond : ChatBot { private string matchesFile; private List respondRules; - private static string header = "[AutoRespond] "; /// /// Create a new AutoRespond bot @@ -84,7 +83,7 @@ public string Match(string username, string message, bool privateMsg) } else if (!String.IsNullOrEmpty(match)) { - if (message.Contains(match)) + if (message.ToLower().Contains(match.ToLower())) { return (privateMsg ? actionPrivate @@ -137,7 +136,7 @@ public override void Initialize() case "regex": matchRegex = new Regex(argValue); break; case "match": matchString = argValue; break; case "action": matchAction = argValue; break; - case "actionprivate": matchAction = argValue; break; + case "actionprivate": matchActionPrivate = argValue; break; } } } @@ -182,7 +181,7 @@ private void CheckAddMatch(Regex matchRegex, string matchString, string matchAct public override void GetText(string text) { //Remove colour codes - text = GetVerbatim(text).ToLower(); + text = GetVerbatim(text); //Check if this is a valid message string sender = "", message = ""; @@ -200,10 +199,10 @@ public override void GetText(string text) if (toPerform != null) { string response = null; - LogToConsole(header + toPerform); + LogToConsole(toPerform); PerformInternalCommand(toPerform, ref response); if (!String.IsNullOrEmpty(response)) - LogToConsole(header + response); + LogToConsole(response); } } } diff --git a/MinecraftClient/Commands/Log.cs b/MinecraftClient/Commands/Log.cs index afc2dcef3d..2e4355532c 100644 --- a/MinecraftClient/Commands/Log.cs +++ b/MinecraftClient/Commands/Log.cs @@ -14,7 +14,7 @@ public override string Run(McTcpClient handler, string command) { if (hasArg(command)) { - ChatBot.LogToConsole(getArg(command)); + ConsoleIO.WriteLogLine(getArg(command)); return ""; } else return CMDDesc; diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index dd488d52d3..9daeadc9fb 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -326,6 +326,16 @@ public static void WriteLineFormatted(string str, bool acceptnewlines = true) } } + /// + /// Write a Minecraft Console Client Log line + /// + /// Text of the log line + + public static void WriteLogLine(string text) + { + WriteLineFormatted("§8[MCC] " + text); + } + #region Subfunctions private static void ClearLineAndBuffer() { diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 769c2d0295..db8b66faf9 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -163,7 +163,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv { if (AttemptsLeft > 0) { - ChatBot.LogToConsole("Waiting 5 seconds (" + AttemptsLeft + " attempts left)..."); + ConsoleIO.WriteLogLine("Waiting 5 seconds (" + AttemptsLeft + " attempts left)..."); Thread.Sleep(5000); AttemptsLeft--; Program.Restart(); } else if (!singlecommand && Settings.interactiveMode) diff --git a/MinecraftClient/config/sample-matches.ini b/MinecraftClient/config/sample-matches.ini new file mode 100644 index 0000000000..fcf228a011 --- /dev/null +++ b/MinecraftClient/config/sample-matches.ini @@ -0,0 +1,32 @@ +# Minecraft Console Client +# AutoRespond matches +# Example config file + +# Structure of a match: [Match] Followed by the match and action +# The match can be a simple match or an advanced regular expression +# You can define a different action if the match was in a private message +# You can use $u for username of the player triggering the match +# Regex matches are also supported eg $1, $2, $3.. in actions + +# Simple example: Respond to a message containing a keyword + +[Match] +match=hi +action=send hi, $u! +actionprivate=send /tell $u Hello! + +# Advanced example: Use a regular expression + +[Match] +regex=^.*hello ([a-zA-Z0-9_]+).*$ +action=send hello too, $1! + +# You can also use any other internal command +# Private action is optional + +[Match] +match=dotest +action=script test + +# Enjoy! +# - ORelio \ No newline at end of file From e29b4ee545648f7548f2042ea3927033d280d18a Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 21 Jun 2015 18:45:43 +0200 Subject: [PATCH 036/102] Add support for C# script extensions - Allow defining function for use into the script - Allow defining a ChatBot for loading it into MCC - Improve sample script and add more examples - Todo add new documentation into the readme file --- MinecraftClient/ChatBot.cs | 5 +- MinecraftClient/ChatBots/Script.cs | 47 ++++++++++++++----- MinecraftClient/Settings.cs | 19 ++++---- .../config/sample-script-extended.cs | 30 ++++++++++++ .../config/sample-script-with-chatbot.cs | 35 ++++++++++++++ MinecraftClient/config/sample-script.cs | 5 +- 6 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 MinecraftClient/config/sample-script-extended.cs create mode 100644 MinecraftClient/config/sample-script-with-chatbot.cs diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index a3d57840ad..f1080d8ddf 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -33,9 +33,10 @@ public abstract class ChatBot { public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost }; - //Will be automatically set on bot loading, don't worry about this + //Handler will be automatically set on bot loading, don't worry about this public void SetHandler(McTcpClient handler) { this._handler = handler; } - public void SetMaster(ChatBot master) { this.master = master; } + protected void SetMaster(ChatBot master) { this.master = master; } + protected void LoadBot(ChatBot bot) { Handler.BotUnLoad(bot); Handler.BotLoad(bot); } private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } } private McTcpClient _handler = null; private ChatBot master = null; diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs index 44ae5891fe..6fc8573dc6 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -97,7 +97,7 @@ public override void Update() tpause = new ManualResetEvent(false); thread = new Thread(() => { - if (!RunCSharpScript(String.Join("\n", lines), file, tpause) && owner != null) + if (!RunCSharpScript() && owner != null) SendPrivateMessage(owner, "Script '" + file + "' failed to run."); }); thread.Start(); @@ -160,16 +160,37 @@ public override void Update() } } - private bool RunCSharpScript(string script, string filename = "C# Script", ManualResetEvent tpause = null) + private bool RunCSharpScript() { //Script compatibility check for handling future versions differently - if (!script.ToLower().StartsWith("//mccscript 1.0")) + if (lines.Length < 1 || lines[0] != "//MCCScript 1.0") { - ConsoleIO.WriteLineFormatted("§8Script file '" + filename + "' does not start with a valid //MCCScript comment."); + LogToConsole("Script file '" + file + "' does not start with a valid //MCCScript identifier."); return false; } - //Create a simple ChatBot class from the given script, allowing access to ChatBot API + //Process different sections of the script file + bool scriptMain = true; + List script = new List(); + List extensions = new List(); + foreach (string line in lines) + { + if (line.StartsWith("//MCCScript")) + { + if (line.EndsWith("Extensions")) + scriptMain = false; + } + else if (scriptMain) + { + script.Add(line); + //Add breakpoints for step-by-step execution of the script + if (tpause != null && line.Trim().EndsWith(";")) + script.Add("tpause.WaitOne();"); + } + else extensions.Add(line); + } + + //Generate a ChatBot class, allowing access to the ChatBot API string code = String.Join("\n", new string[] { "using System;", @@ -178,12 +199,12 @@ private bool RunCSharpScript(string script, string filename = "C# Script", Manua "using MinecraftClient;", "namespace ScriptLoader {", "public class Script : ChatBot {", - "public void Run(ChatBot master, ManualResetEvent tpause) {", + "public void __run(ChatBot master, ManualResetEvent tpause) {", "SetMaster(master);", - tpause != null - ? script.Replace(";\n", ";\ntpause.WaitOne();\n") - : script, - "}}}", + String.Join("\n", script), + "}", + String.Join("\n", extensions), + "}}", }); //Compile the C# class in memory using all the currently loaded assemblies @@ -202,16 +223,16 @@ CompilerResults result //Process compile warnings and errors if (result.Errors.Count > 0) { - ConsoleIO.WriteLineFormatted("§8Error loading '" + filename + "':\n" + result.Errors[0].ErrorText); + LogToConsole("Error loading '" + file + "':\n" + result.Errors[0].ErrorText); return false; } //Run the compiled script with exception handling object compiledScript = result.CompiledAssembly.CreateInstance("ScriptLoader.Script"); - try { compiledScript.GetType().GetMethod("Run").Invoke(compiledScript, new object[] { this, tpause }); } + try { compiledScript.GetType().GetMethod("__run").Invoke(compiledScript, new object[] { this, tpause }); } catch (Exception e) { - ConsoleIO.WriteLineFormatted("§8Runtime error for '" + filename + "':\n" + e); + LogToConsole("Runtime error for '" + file + "':\n" + e); return false; } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 735de5cd61..f6fc9b3a52 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -94,9 +94,9 @@ public static class Settings public static string AutoRespond_Matches = "matches.ini"; //Custom app variables and Minecraft accounts - private static Dictionary AppVars = new Dictionary(); - private static Dictionary> Accounts = new Dictionary>(); - private static Dictionary> Servers = new Dictionary>(); + 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, AutoRespond }; @@ -491,13 +491,16 @@ public static bool SetServerIP(string server) public static bool SetVar(string varName, string 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; } /// diff --git a/MinecraftClient/config/sample-script-extended.cs b/MinecraftClient/config/sample-script-extended.cs new file mode 100644 index 0000000000..be6694da15 --- /dev/null +++ b/MinecraftClient/config/sample-script-extended.cs @@ -0,0 +1,30 @@ +//MCCScript 1.0 + +/* This script demonstrates how to add fields and methods */ + +for (int i = 0; i < 5; i++) +{ + int count = GetVarAsInt("test") + 1; + SetVar("test", count); + SendHelloWorld(count); + SleepBetweenSends(); +} + +//MCCScript Extensions + +/* Here you can define methods for use into your script */ + +void SendHelloWorld(int count) +{ + /* Warning: Do not make more than one server-related call into a method + * defined as a script extension eg SendText or switching servers, + * as execution flow is not managed in the Extensions section */ + + SendText("Hello World no. " + count); +} + +void SleepBetweenSends() +{ + 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 0000000000..264711fac9 --- /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 */ + +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.cs b/MinecraftClient/config/sample-script.cs index 6240c46a71..3d61b25cb1 100644 --- a/MinecraftClient/config/sample-script.cs +++ b/MinecraftClient/config/sample-script.cs @@ -6,10 +6,9 @@ for (int i = 0; i < 5; i++) { - int count = GetVarAsInt("test"); - count++; + int count = GetVarAsInt("test") + 1; SetVar("test", count); SendText("Hello World no. " + count); - PerformInternalCommand("log Sleeping for 5 seconds..."); + LogToConsole("Sleeping for 5 seconds..."); Thread.Sleep(5000); } \ No newline at end of file From f076e1f51226895f0fbd6ae82ed0261beb9959f0 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 25 Jun 2015 12:12:59 +0200 Subject: [PATCH 037/102] Add argument passing for C# scripts script files with spaces in filename will need double quotes when calling them eg /script "my script.txt" instead of /script my script.txt --- MinecraftClient/ChatBots/Script.cs | 56 +++++++++++++++++-- MinecraftClient/ChatBots/ScriptScheduler.cs | 2 +- .../config/sample-script-extended.cs | 13 +++-- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs index 6fc8573dc6..0e1894d933 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -16,6 +16,7 @@ public class Script : ChatBot { private string file; private string[] lines = new string[0]; + private string[] args = new string[0]; private int sleepticks = 10; private int nextline = 0; private string owner; @@ -25,7 +26,7 @@ public class Script : ChatBot public Script(string filename) { - file = filename; + ParseArguments(filename); } public Script(string filename, string ownername) @@ -35,7 +36,52 @@ public Script(string filename, string ownername) owner = ownername; } - public static bool lookForScript(ref string filename) + private void ParseArguments(string argstr) + { + List args = new List(); + StringBuilder str = new StringBuilder(); + + bool escape = false; + bool quotes = false; + + foreach (char c in argstr) + { + if (escape) + { + if (c != '"') + str.Append('\\'); + str.Append(c); + escape = false; + } + else + { + if (c == '\\') + escape = true; + else if (c == '"') + quotes = !quotes; + else if (c == ' ' && !quotes) + { + if (str.Length > 0) + args.Add(str.ToString()); + str.Clear(); + } + else str.Append(c); + } + } + + if (str.Length > 0) + args.Add(str.ToString()); + + if (args.Count > 0) + { + file = args[0]; + args.RemoveAt(0); + this.args = args.ToArray(); + } + else file = ""; + } + + public static bool LookForScript(ref string filename) { //Automatically look in subfolders and try to add ".txt" file extension char dir_slash = Program.isUsingMono ? '/' : '\\'; @@ -67,7 +113,7 @@ public static bool lookForScript(ref string filename) public override void Initialize() { //Load the given file from the startup parameters - if (lookForScript(ref file)) + if (LookForScript(ref file)) { lines = System.IO.File.ReadAllLines(file); csharp = file.EndsWith(".cs"); @@ -199,7 +245,7 @@ private bool RunCSharpScript() "using MinecraftClient;", "namespace ScriptLoader {", "public class Script : ChatBot {", - "public void __run(ChatBot master, ManualResetEvent tpause) {", + "public void __run(ChatBot master, ManualResetEvent tpause, string[] args) {", "SetMaster(master);", String.Join("\n", script), "}", @@ -229,7 +275,7 @@ CompilerResults result //Run the compiled script with exception handling object compiledScript = result.CompiledAssembly.CreateInstance("ScriptLoader.Script"); - try { compiledScript.GetType().GetMethod("__run").Invoke(compiledScript, new object[] { this, tpause }); } + try { compiledScript.GetType().GetMethod("__run").Invoke(compiledScript, new object[] { this, tpause, args }); } catch (Exception e) { LogToConsole("Runtime error for '" + file + "':\n" + e); diff --git a/MinecraftClient/ChatBots/ScriptScheduler.cs b/MinecraftClient/ChatBots/ScriptScheduler.cs index d1783cb707..7605fc40f0 100644 --- a/MinecraftClient/ChatBots/ScriptScheduler.cs +++ b/MinecraftClient/ChatBots/ScriptScheduler.cs @@ -95,7 +95,7 @@ private void checkAddTask(TaskDesc current_task) if (current_task != null) { //Check if we built a valid task before adding it - if (current_task.script_file != null && Script.lookForScript(ref current_task.script_file) //Check if file exists + if (current_task.script_file != null && Script.LookForScript(ref current_task.script_file) //Check if file exists && (current_task.triggerOnLogin || (current_task.triggerOnTime && current_task.triggerOnTime_Times.Count > 0)) || (current_task.triggerOnInterval && current_task.triggerOnInterval_Interval > 0)) //Look for a valid trigger diff --git a/MinecraftClient/config/sample-script-extended.cs b/MinecraftClient/config/sample-script-extended.cs index be6694da15..68ff8cd174 100644 --- a/MinecraftClient/config/sample-script-extended.cs +++ b/MinecraftClient/config/sample-script-extended.cs @@ -1,12 +1,17 @@ //MCCScript 1.0 -/* This script demonstrates how to add fields and methods */ +/* This script demonstrates how to use methods and arguments */ +string text = "hello"; + +if (args.Length > 0) + text = args[0]; + for (int i = 0; i < 5; i++) { int count = GetVarAsInt("test") + 1; SetVar("test", count); - SendHelloWorld(count); + SendHelloWorld(count, text); SleepBetweenSends(); } @@ -14,13 +19,13 @@ /* Here you can define methods for use into your script */ -void SendHelloWorld(int count) +void SendHelloWorld(int count, string text) { /* Warning: Do not make more than one server-related call into a method * defined as a script extension eg SendText or switching servers, * as execution flow is not managed in the Extensions section */ - SendText("Hello World no. " + count); + SendText("Hello World no. " + count + ": " + text); } void SleepBetweenSends() From f5a67090c255b56343d4e1dd9a1413ebaace3d7d Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 5 Jul 2015 20:53:35 +0200 Subject: [PATCH 038/102] Add comments for server IP parsing Dot is used for determining of the given IP is a server alias or a direct ip address or domain name. See #83 --- MinecraftClient/Settings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index f6fc9b3a52..0985287e6e 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -468,12 +468,14 @@ public static bool SetServerIP(string server) 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; From a6a9814163dd8c5131552bd1321c41a6287978fd Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 7 Jul 2015 22:43:27 +0200 Subject: [PATCH 039/102] MC 1.7: Skip potential extra data in tab-list items See issue #84 for more info --- MinecraftClient/Protocol/Handlers/Protocol17.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index 7d74e80bd2..6358007459 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -90,9 +90,11 @@ private bool Update() handler.OnTextReceived(ChatParser.ParseText(readNextString())); break; case 0x38: - string name = readNextString(); + int name_len = readNextVarInt(); + string name = readNextString(name_len); bool online = readNextBool(); short ping = readNextShort(); + readData(size - getVarInt(id).Length - getVarInt(name.Length).Length - name_len - 3); //Skip extradata Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); if (online) { @@ -176,11 +178,13 @@ private void readData(int offset) /// /// Read a string from the network /// + /// String length /// The string - private string readNextString() + private string readNextString(int length = -1) { - int length = readNextVarInt(); + if (length < 0) + length = readNextVarInt(); if (length > 0) { byte[] cache = new byte[length]; From 546119a33457b84cb16e1e48b003e8a2aef19f88 Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 17 Jul 2015 23:00:45 +0200 Subject: [PATCH 040/102] Fix ] password typing on QWERTY keyboards Oem6 key was incorrectly ignored. Fix #47 --- MinecraftClient/ConsoleIO.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 9daeadc9fb..7e81590afa 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -70,7 +70,6 @@ public static string ReadPassword() case ConsoleKey.Home: case ConsoleKey.End: case ConsoleKey.Delete: - case ConsoleKey.Oem6: case ConsoleKey.DownArrow: case ConsoleKey.UpArrow: case ConsoleKey.Tab: From 1e801ad415e9518d213292054dbc0884416e5afb Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 21 Jul 2015 16:46:41 +0200 Subject: [PATCH 041/102] Fix tab autocompletion when no result is found When no result is found, tab-complete result should be ignored. Bug report by c0dei. --- MinecraftClient/ConsoleIO.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 7e81590afa..1272de2010 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -175,9 +175,8 @@ public static string ReadLine() string[] tmp = buffer.Split(' '); if (tmp.Length > 0) { - string word_tocomplete = tmp[tmp.Length - 1]; string word_autocomplete = autocomplete_engine.AutoComplete(buffer); - if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete) + if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != buffer) { while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); } foreach (char c in word_autocomplete) { AddChar(c); } From c88d1509765c1e34f3c47d9361ac8e29dd8cf1ff Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 23 Jul 2015 21:38:58 +0200 Subject: [PATCH 042/102] Fix Offline BungeeCord 1.5.2 requiring encryption Vanilla minecraft encryption can be unofficially bypassed on pre-1.7 minecraft when connecting to offline-mode servers (now it IS officially bypassed in offline mode in 1.7+), but BungeeCord 1.5.2 requires encryption even in offline-mode, so enable encryption even in offline-mode. Bug report by xp9kus. --- MinecraftClient/Protocol/Handlers/Protocol16.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 4acf444785..cf648fc832 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -441,15 +441,11 @@ private bool Handshake(string uuid, string username, string sessionID, string ho byte[] token = readNextByteArray(); if (serverID == "-") - { ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); - return true; //No need to check session or start encryption - } else - { 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; } From 80b44228f8dce06d636f18c9b40f539cfc85995f Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 23 Jul 2015 21:39:41 +0200 Subject: [PATCH 043/102] Further autocompletion fixes Refactor code as splitting is now useless --- MinecraftClient/ConsoleIO.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 1272de2010..34bb7a35d0 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -172,15 +172,11 @@ public static string ReadLine() case ConsoleKey.Tab: if (autocomplete_engine != null && buffer.Length > 0) { - string[] tmp = buffer.Split(' '); - if (tmp.Length > 0) + string word_autocomplete = autocomplete_engine.AutoComplete(buffer); + if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != buffer) { - string word_autocomplete = autocomplete_engine.AutoComplete(buffer); - if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != buffer) - { - while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); } - foreach (char c in word_autocomplete) { AddChar(c); } - } + while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); } + foreach (char c in word_autocomplete) { AddChar(c); } } } break; From 729960d4a3bb7df638add690dff19e776cb9eeef Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 30 Jul 2015 12:37:29 +0200 Subject: [PATCH 044/102] Add 1.8.8 as supported version --- MinecraftClient/Program.cs | 2 +- MinecraftClient/Protocol/ProtocolHandler.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 2beabf47d2..a9e4575d7f 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -28,7 +28,7 @@ static class Program static void Main(string[] args) { - Console.WriteLine("Console Client for MC 1.4.6 to 1.8.7 - v" + Version + " - By ORelio & Contributors"); + Console.WriteLine("Console Client for MC 1.4.6 to 1.8.8 - v" + Version + " - By ORelio & Contributors"); //Basic Input/Output ? if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 7262da169d..a6c2e98142 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -121,6 +121,7 @@ public static int MCVer2ProtocolVersion(string MCVersion) case "1.8.5": case "1.8.6": case "1.8.7": + case "1.8.8": return 47; default: return 0; From 3a760240e493f77b84a6fa37dfe2cda1077b9931 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 30 Jul 2015 16:47:55 +0200 Subject: [PATCH 045/102] Move 1.7 handling into 1.8 handler Minecraft 1.7 handler was pretty similar to 1.8 handler and lacking some features such as packet prefetching. --- MinecraftClient/MinecraftClient.csproj | 1 - .../Protocol/Handlers/Protocol17.cs | 589 ------------------ .../Protocol/Handlers/Protocol18.cs | 215 +++++-- MinecraftClient/Protocol/ProtocolHandler.cs | 7 +- 4 files changed, 172 insertions(+), 640 deletions(-) delete mode 100644 MinecraftClient/Protocol/Handlers/Protocol17.cs diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 0ef5d9ec57..bc276c9ead 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -135,7 +135,6 @@ - diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs deleted file mode 100644 index 6358007459..0000000000 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ /dev/null @@ -1,589 +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() - { - try - { - do - { - Thread.Sleep(100); - } - 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: - int name_len = readNextVarInt(); - string name = readNextString(name_len); - bool online = readNextBool(); - short ping = readNextShort(); - readData(size - getVarInt(id).Length - getVarInt(name.Length).Length - name_len - 3); //Skip extradata - 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 - /// - /// String length - /// The string - - private string readNextString(int length = -1) - { - if (length < 0) - 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); - 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; - } - - /// - /// 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 - /// - - public void Disconnect() - { - try - { - c.Close(); - } - 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 (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) - { - Json.JSONData jsonData = Json.ParseJson(result); - if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) - { - jsonData = jsonData.Properties["version"]; - - //Retrieve display name of the Minecraft version - if (jsonData.Properties.ContainsKey("name")) - version = jsonData.Properties["name"].StringValue; - - //Retrieve protocol version number for handling this server - if (jsonData.Properties.ContainsKey("protocol")) - protocolversion = atoi(jsonData.Properties["protocol"].StringValue); - - //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 509bfb925a..4e663ae24d 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -6,24 +6,28 @@ using System.Threading; using MinecraftClient.Crypto; using MinecraftClient.Proxy; +using System.Security.Cryptography; namespace MinecraftClient.Protocol.Handlers { /// - /// Implementation for Minecraft 1.8.X Protocol + /// Implementation for Minecraft 1.7.X and 1.8.X Protocols /// class Protocol18Handler : IMinecraftCom { - IMinecraftComHandler handler; + private const int MC18Version = 47; + 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; + + IMinecraftComHandler handler; + Thread netRead; + IAesStream s; TcpClient c; public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler) @@ -95,7 +99,9 @@ private void readNextPacket(ref int packetID, ref byte[] packetData) int size = readNextVarIntRAW(); //Packet size packetData = 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 @@ -114,12 +120,20 @@ private void readNextPacket(ref int packetID, ref byte[] packetData) private bool handlePacket(int packetID, byte[] packetData) { + if (new[] { 0x00, 0x02, 0x38, 0x3A, 0x40 }.Contains(packetID)) + ConsoleIO.WriteLineFormatted("§aP 0x" + packetID.ToString("x2")); //Process + else if (packetID <= 64) + ConsoleIO.WriteLineFormatted("§fS 0x" + packetID.ToString("x2")); //Skip + else + ConsoleIO.WriteLineFormatted("§c? 0x" + packetID.ToString("x2")); //Invalid + 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(ref packetData); break; default: return false; //Ignored packet @@ -130,31 +144,44 @@ private bool handlePacket(int packetID, byte[] packetData) switch (packetID) { case 0x00: //Keep-Alive - SendPacket(0x00, getVarInt(readNextVarInt(ref packetData))); + SendPacket(0x00, 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); - for (int i = 0; i < numActions; i++) + if (protocolversion >= MC18Version) { - Guid uuid = readNextUUID(ref packetData); - switch (action) + int action = readNextVarInt(ref packetData); + int numActions = readNextVarInt(ref packetData); + for (int i = 0; i < numActions; 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; + Guid uuid = readNextUUID(ref packetData); + switch (action) + { + 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; + } } } + else //MC 1.7.X does not provide UUID in tab-list updates + { + string name = readNextString(ref packetData); + bool online = readNextBool(ref packetData); + short ping = readNextShort(ref 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 0x3A: //Tab-Complete Result int autocomplete_count = readNextVarInt(ref packetData); @@ -174,7 +201,8 @@ private bool handlePacket(int packetID, byte[] packetData) handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); return false; case 0x46: //Network Compression Treshold Info - compression_treshold = readNextVarInt(ref packetData); + if (protocolversion >= MC18Version) + compression_treshold = readNextVarInt(ref packetData); break; default: return false; //Ignored packet @@ -239,18 +267,11 @@ private byte[] readDataRAW(int offset) /// 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, ref byte[] 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 = cache.Skip(offset).ToArray(); + return result; } /// @@ -259,7 +280,7 @@ private byte[] readData(int offset, ref byte[] cache) /// Cache of bytes to read from /// The string - private string readNextString(ref byte[] cache) + private static string readNextString(ref byte[] cache) { int length = readNextVarInt(ref cache); if (length > 0) @@ -269,13 +290,35 @@ private string readNextString(ref byte[] cache) else return ""; } + /// + /// Read a boolean from a cache of bytes and remove it from the cache + /// + /// The boolean value + + private static bool readNextBool(ref byte[] cache) + { + return readData(1, ref cache)[0] != 0x00; + } + + /// + /// Read a short integer from a cache of bytes and remove it from the cache + /// + /// The short integer value + + private static short readNextShort(ref byte[] cache) + { + byte[] rawValue = readData(2, ref cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToInt16(rawValue, 0); + } + /// /// 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(ref byte[] cache) { return new Guid(readData(16, ref cache)); } @@ -288,7 +331,9 @@ private Guid readNextUUID(ref byte[] cache) private byte[] readNextByteArray(ref byte[] cache) { - int len = readNextVarInt(ref cache); + int len = protocolversion >= MC18Version + ? readNextVarInt(ref cache) + : readNextShort(ref cache); return readData(len, ref cache); } @@ -320,7 +365,7 @@ private int readNextVarIntRAW() /// Cache of bytes to read from /// The integer - private int readNextVarInt(ref byte[] cache) + private static int readNextVarInt(ref byte[] cache) { int i = 0; int j = 0; @@ -355,6 +400,23 @@ private static byte[] getVarInt(int paramInt) return bytes.ToArray(); } + /// + /// 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); + } + /// /// Easily append several byte arrays /// @@ -514,13 +576,11 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string } //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); @@ -614,8 +674,10 @@ public string AutoComplete(string BehindCursor) 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[] has_position = new byte[] { 0x00 }; + byte[] tabcomplete_packet = protocolversion >= MC18Version + ? concatBytes(tocomplete_len, tocomplete_val, has_position) + : concatBytes(tocomplete_len, tocomplete_val); autocomplete_received = false; autocomplete_result = BehindCursor; @@ -625,5 +687,68 @@ public string AutoComplete(string BehindCursor) 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); + + Protocol18Handler ComTmp = new Protocol18Handler(tcp); + int packetLength = ComTmp.readNextVarIntRAW(); + if (packetLength > 0) //Read Response length + { + byte[] packetData = ComTmp.readDataRAW(packetLength); + if (readNextVarInt(ref packetData) == 0x00) //Read Packet ID + { + string result = readNextString(ref 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")) + { + jsonData = jsonData.Properties["version"]; + + //Retrieve display name of the Minecraft version + if (jsonData.Properties.ContainsKey("name")) + version = jsonData.Properties["name"].StringValue; + + //Retrieve protocol version number for handling this server + if (jsonData.Properties.ContainsKey("protocol")) + protocolversion = atoi(jsonData.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"); + + ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ")."); + return true; + } + } + } + } + return false; + } } } diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index a6c2e98142..11a0873f05 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -32,7 +32,7 @@ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int pro try { if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp) - || Protocol17Handler.doPing(serverIP, serverPort, ref protocolversionTmp)) + || Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp)) { success = true; } @@ -67,10 +67,7 @@ public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVer 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 }; + int[] supportedVersions_Protocol18 = { 4, 5, 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."); From 67f17cbb3e02b061a3a1716eae9bf328625a53fb Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 30 Jul 2015 17:32:42 +0200 Subject: [PATCH 046/102] Remove packet debugging code --- MinecraftClient/McTcpClient.cs | 1 + MinecraftClient/Protocol/Handlers/Protocol18.cs | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index db8b66faf9..0817755c53 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -224,6 +224,7 @@ private void CommandPrompt() } } catch (IOException) { } + catch (NullReferenceException) { } } /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 4e663ae24d..6cfbf89b8a 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -120,13 +120,6 @@ private void readNextPacket(ref int packetID, ref byte[] packetData) private bool handlePacket(int packetID, byte[] packetData) { - if (new[] { 0x00, 0x02, 0x38, 0x3A, 0x40 }.Contains(packetID)) - ConsoleIO.WriteLineFormatted("§aP 0x" + packetID.ToString("x2")); //Process - else if (packetID <= 64) - ConsoleIO.WriteLineFormatted("§fS 0x" + packetID.ToString("x2")); //Skip - else - ConsoleIO.WriteLineFormatted("§c? 0x" + packetID.ToString("x2")); //Invalid - if (login_phase) { switch (packetID) //Packet IDs are different while logging in From 12b94996c74cbab1d3594666d08ad2afd559bd3e Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 31 Jul 2015 12:23:13 +0200 Subject: [PATCH 047/102] Add joshbean39's chat formats --- MinecraftClient/ChatBot.cs | 51 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index f1080d8ddf..b8c71706b7 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -199,11 +199,24 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } + //Detect Essentials (Bukkit) /me messages with some custom prefix + //[Prefix] [Someone -> me] message + //[Prefix] [~Someone -> me] message + else if (text[0] == '[' && tmp[0][tmp[0].Length - 1] == ']' + && tmp[1][0] == '[' && tmp.Length > 4 && tmp[2] == "->" + && (tmp[3] == "me]" || tmp[3] == "moi]")) + { + message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1); + sender = tmp[1].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } + //Detect Essentials (Bukkit) /me messages with some custom rank //[Someone [rank] -> me] message //[~Someone [rank] -> me] message else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->" - && (tmp[3] == "me]" || tmp[3] == "moi]")) //'me' is replaced by 'moi' in french servers + && (tmp[3] == "me]" || tmp[3] == "moi]")) { message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); sender = tmp[0].Substring(1); @@ -273,6 +286,29 @@ protected static bool IsChatMessage(string text, ref string message, ref string message = text.Substring(name_end + 2); return IsValidName(sender); } + + //Detect (Unknown Plugin) Messages + //**Faction User : Message + else if (text[0] == '*' + && text.Length > 1 + && text[1] != ' ' + && text.Contains('<') && text.Contains('>') + && text.Contains(' ') && text.Contains(':') + && text.IndexOf('*') < text.IndexOf('<') + && text.IndexOf('<') < text.IndexOf('>') + && text.IndexOf('>') < text.IndexOf(' ') + && text.IndexOf(' ') < text.IndexOf(':')) + { + string prefix = tmp[0]; + string user = tmp[1]; + string semicolon = tmp[2]; + if (prefix.All(c => char.IsLetterOrDigit(c) || new char[] { '*', '<', '>', '_' }.Contains(c)) + && semicolon == ":") + { + message = text.Substring(prefix.Length + user.Length + 4); + return IsValidName(user); + } + } } return false; } @@ -287,10 +323,21 @@ protected static bool IsChatMessage(string text, ref string message, ref string protected static bool IsTeleportRequest(string text, ref string sender) { text = GetVerbatim(text); - sender = text.Split(' ')[0]; + string[] tmp = text.Split(' '); if (text.EndsWith("has requested to teleport to you.") || text.EndsWith("has requested that you teleport to them.")) { + // Username has requested... + //[Rank] Username has requested... + if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">")) + || (tmp[0].StartsWith("[") && tmp[0].EndsWith("]"))) + && tmp.Length > 1) + sender = tmp[1]; + + //Username has requested... + else sender = tmp[0]; + + //Final check on username validity return IsValidName(sender); } else return false; From de4322458a01c6caa3e35b81fd7811625f29473e Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 2 Aug 2015 12:20:51 +0200 Subject: [PATCH 048/102] Add 'other' messages support in AutoRespond --- MinecraftClient/ChatBots/AutoRespond.cs | 70 ++++++++++++++--------- MinecraftClient/config/sample-matches.ini | 18 +++++- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 19146854ae..974ac8e6e7 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -12,6 +12,7 @@ class AutoRespond : ChatBot { private string matchesFile; private List respondRules; + private enum MessageType { Public, Private, Other }; /// /// Create a new AutoRespond bot @@ -31,6 +32,7 @@ private class RespondRule private string match; private string actionPublic; private string actionPrivate; + private string actionOther; /// /// Create a respond rule from a regex and a reponse message or command @@ -38,12 +40,14 @@ private class RespondRule /// Regex /// Internal command to run for public messages /// Internal command to run for private messages - public RespondRule(Regex regex, string actionPublic, string actionPrivate) + /// Internal command to run for any other messages + public RespondRule(Regex regex, string actionPublic, string actionPrivate, string actionOther) { this.regex = regex; this.match = null; this.actionPublic = actionPublic; this.actionPrivate = actionPrivate; + this.actionOther = actionOther; } /// @@ -52,12 +56,13 @@ public RespondRule(Regex regex, string actionPublic, string actionPrivate) /// Match string /// Internal command to run for public messages /// Internal command to run for private messages - public RespondRule(string match, string actionPublic, string actionPrivate) + public RespondRule(string match, string actionPublic, string actionPrivate, string actionOther) { this.regex = null; this.match = match; this.actionPublic = actionPublic; this.actionPrivate = actionPrivate; + this.actionOther = actionOther; } /// @@ -65,16 +70,27 @@ public RespondRule(string match, string actionPublic, string actionPrivate) /// /// Player who have sent the message /// Message to match against the regex or match string - /// True if the provided message was sent privately eg with /tell + /// Type of the message public/private message, or other message /// Internal command to run as a response to this user, or null if no match has been detected - public string Match(string username, string message, bool privateMsg) + public string Match(string username, string message, MessageType msgType) { + string toSend = null; + + switch (msgType) + { + case MessageType.Public: toSend = actionPublic; break; + case MessageType.Private: toSend = actionPrivate; break; + case MessageType.Other: toSend = actionOther; break; + } + + if (String.IsNullOrEmpty(toSend)) + return null; + if (regex != null) { if (regex.IsMatch(message)) { Match regexMatch = regex.Match(message); - string toSend = privateMsg ? actionPrivate : actionPublic; for (int i = regexMatch.Groups.Count - 1; i >= 1; i--) toSend = toSend.Replace("$" + i, regexMatch.Groups[i].Value); toSend = toSend.Replace("$u", username); @@ -85,11 +101,10 @@ public string Match(string username, string message, bool privateMsg) { if (message.ToLower().Contains(match.ToLower())) { - return (privateMsg - ? actionPrivate - : actionPublic).Replace("$u", username); + return toSend.Replace("$u", username); } } + return null; } } @@ -105,6 +120,7 @@ public override void Initialize() string matchString = null; string matchAction = null; string matchActionPrivate = null; + string matchActionOther = null; respondRules = new List(); foreach (string lineRAW in File.ReadAllLines(matchesFile)) @@ -117,11 +133,12 @@ public override void Initialize() switch (line.Substring(1, line.Length - 2).ToLower()) { case "match": - CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate); + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther); matchRegex = null; matchString = null; matchAction = null; matchActionPrivate = null; + matchActionOther = null; break; } } @@ -137,12 +154,13 @@ public override void Initialize() case "match": matchString = argValue; break; case "action": matchAction = argValue; break; case "actionprivate": matchActionPrivate = argValue; break; + case "actionother": matchActionOther = argValue; break; } } } } } - CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate); + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther); } else { @@ -158,22 +176,17 @@ public override void Initialize() /// Matching string /// Action if the matching message is public /// Action if the matching message is private - private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate) + private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate, string matchActionOther) { - if (matchAction != null || matchActionPrivate != null) + if (matchAction != null || matchActionPrivate != null || matchActionOther != null) { - if (matchActionPrivate == null) - { - matchActionPrivate = matchAction; - } - if (matchRegex != null) { - respondRules.Add(new RespondRule(matchRegex, matchAction, matchActionPrivate)); + respondRules.Add(new RespondRule(matchRegex, matchAction, matchActionPrivate, matchActionOther)); } else if (matchString != null) { - respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate)); + respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate, matchActionOther)); } } } @@ -183,20 +196,21 @@ public override void GetText(string text) //Remove colour codes text = GetVerbatim(text); - //Check if this is a valid message + //Get Message type string sender = "", message = ""; - bool chatMessage = IsChatMessage(text, ref message, ref sender); - bool privateMessage = false; - if (!chatMessage) - privateMessage = IsPrivateMessage(text, ref message, ref sender); + MessageType msgType = MessageType.Other; + if (IsChatMessage(text, ref message, ref sender)) + msgType = MessageType.Public; + else if (IsPrivateMessage(text, ref message, ref sender)) + msgType = MessageType.Private; - //Process only chat messages sent by another user - if ((chatMessage || privateMessage) && sender != Settings.Username) + //Do not process messages sent by the bot itself + if (msgType == MessageType.Other || sender != Settings.Username) { foreach (RespondRule rule in respondRules) { - string toPerform = rule.Match(sender, message, privateMessage); - if (toPerform != null) + string toPerform = rule.Match(sender, message, msgType); + if (!String.IsNullOrEmpty(toPerform)) { string response = null; LogToConsole(toPerform); diff --git a/MinecraftClient/config/sample-matches.ini b/MinecraftClient/config/sample-matches.ini index fcf228a011..ec85b87c6d 100644 --- a/MinecraftClient/config/sample-matches.ini +++ b/MinecraftClient/config/sample-matches.ini @@ -4,8 +4,9 @@ # Structure of a match: [Match] Followed by the match and action # The match can be a simple match or an advanced regular expression -# You can define a different action if the match was in a private message # You can use $u for username of the player triggering the match +# You can define an action if the match was in a private message +# You can define an action if the match was not sent by a player # Regex matches are also supported eg $1, $2, $3.. in actions # Simple example: Respond to a message containing a keyword @@ -14,19 +15,30 @@ match=hi action=send hi, $u! actionprivate=send /tell $u Hello! +actionother=log detected "hi" message + +# You do not need to specify all the "action" fields +# Only one of them is required for each match # Advanced example: Use a regular expression +# Here a "regex" field is used instead of "match" field +# Do not use both "regex" and "match" fields... [Match] regex=^.*hello ([a-zA-Z0-9_]+).*$ action=send hello too, $1! -# You can also use any other internal command -# Private action is optional +# Example of using a script [Match] match=dotest action=script test +# Example of matching a server announcement + +[Match] +match=server is restarting +actionother=script restart + # Enjoy! # - ORelio \ No newline at end of file From 295dfe717e4ca7a753bb3199d7f8a0341731d4b1 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Mon, 17 Aug 2015 11:01:28 -0700 Subject: [PATCH 049/102] Fixed issues with passwords containing unicode special characters. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main fix is the change to ProtocolHandler's jsonEncode method. Previously, it used 'char.IsLetterOrDigit' to see if it needed to be escaped, but some chars, such as "Ð", count as a letter but still need to be escaped. The fix is to check if it's in the right range, rather than using that method. There's also some changes to those methods for performance and clarity reasons. Most of this is using a StringBuilder rather than appending to the string. Not too important, but it makes things clearer. --- MinecraftClient/ConsoleIO.cs | 20 +++++++++----------- MinecraftClient/Protocol/ProtocolHandler.cs | 8 +++++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 34bb7a35d0..07c6771954 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -45,22 +45,18 @@ public static void Reset() public static string ReadPassword() { - string password = ""; - ConsoleKeyInfo k = new ConsoleKeyInfo(); - while (k.Key != ConsoleKey.Enter) + StringBuilder password = new StringBuilder(); + + ConsoleKeyInfo k; + while ((k = Console.ReadKey(true)).Key != ConsoleKey.Enter) { - k = Console.ReadKey(true); switch (k.Key) { - case ConsoleKey.Enter: - Console.Write('\n'); - return password; - case ConsoleKey.Backspace: if (password.Length > 0) { Console.Write("\b \b"); - password = password.Substring(0, password.Length - 1); + password.Remove(password.Length - 1, 1); } break; @@ -79,12 +75,14 @@ public static string ReadPassword() if (k.KeyChar != 0) { Console.Write('*'); - password += k.KeyChar; + password.Append(k.KeyChar); } break; } } - return password; + + Console.WriteLine(); + return password.ToString(); } /// diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 11a0873f05..624d6a7ff4 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -285,16 +285,18 @@ private static string jsonEncode(string text) StringBuilder result = new StringBuilder(); foreach (char c in text) { - if (char.IsLetterOrDigit(c)) + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z')) { result.Append(c); } else { - result.Append("\\u"); - result.Append(((int)c).ToString("x4")); + result.AppendFormat(@"\u{0:x4}", (int)c); } } + return result.ToString(); } } From 86711adba8df44766890495f39ce67da0ed58a63 Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 21 Aug 2015 16:54:18 +0200 Subject: [PATCH 050/102] Fake resource pack acceptance Some server requires that players install a resource pack, and will kick them if they doesn't. With this new feature MCC will automatically respond "successfully loaded" for every "resource pack send" packet it receives. Suggested by Yoann166 in issue #91 --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 6cfbf89b8a..6188accff3 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -197,6 +197,12 @@ private bool handlePacket(int packetID, byte[] packetData) if (protocolversion >= MC18Version) compression_treshold = readNextVarInt(ref packetData); break; + case 0x48: //Resource Pack Send + string url = readNextString(ref packetData); + string hash = readNextString(ref packetData); + //Send back a "successfully loaded" response for plugins making use of resource pack mandatory + SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); + break; default: return false; //Ignored packet } From a0683e1c466d60d9025b423ecff25ee2850b20e0 Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 21 Aug 2015 17:16:27 +0200 Subject: [PATCH 051/102] Add '/' as valid separator for detecting 1.8 proxies Should work with "Requires 1.7/1.8 (protocol v4)" server info, reported by Yoann166. --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 6188accff3..502390d5bf 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -738,7 +738,7 @@ public static bool doPing(string host, int port, ref int protocolversion) protocolversion = atoi(jsonData.Properties["protocol"].StringValue); //Automatic fix for BungeeCord 1.8 reporting itself as 1.7... - if (protocolversion < 47 && version.Split(' ').Contains("1.8")) + if (protocolversion < 47 && version.Split(' ', '/').Contains("1.8")) protocolversion = ProtocolHandler.MCVer2ProtocolVersion("1.8.0"); ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ")."); From 344749ead2f070ba0c4b761a2e614d4030dcf9bd Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 21 Aug 2015 17:22:06 +0200 Subject: [PATCH 052/102] Add 'accepted' response pour resource packs An 'accepted' response is sent by vanilla minecraft before sending 'successfully loaded', so let's do the same thing here. See #91 --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 502390d5bf..398d8c589f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -200,7 +200,8 @@ private bool handlePacket(int packetID, byte[] packetData) case 0x48: //Resource Pack Send string url = readNextString(ref packetData); string hash = readNextString(ref packetData); - //Send back a "successfully loaded" response for plugins making use of resource pack mandatory + //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory + SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); break; default: From 9fefcb40ef41134131314f7624a6c64428708f90 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 22 Aug 2015 14:26:56 -0700 Subject: [PATCH 053/102] Send MC|Brand information upon joining the game --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 398d8c589f..c6357f73e5 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -139,6 +139,9 @@ private bool handlePacket(int packetID, byte[] packetData) case 0x00: //Keep-Alive SendPacket(0x00, packetData); break; + case 0x01: //Join game + SendBrandInfo(); + break; case 0x02: //Chat message handler.OnTextReceived(ChatParser.ParseText(readNextString(ref packetData))); break; @@ -607,6 +610,20 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string } } + /// + /// Sends information about the client version. + /// + + private void SendBrandInfo() + { + byte[] channel = Encoding.UTF8.GetBytes("MC|Brand"); + byte[] channelLen = getVarInt(channel.Length); + byte[] brand = Encoding.UTF8.GetBytes("Minecraft Console Client v" + Program.Version); + byte[] brandLen = getVarInt(brand.Length); + + SendPacket(0x17, concatBytes(channelLen, channel, brandLen, brand)); + } + /// /// Send a chat message to the server /// From 3e2622fbb781b40b95afc493db3a36ee3d5e37d8 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 23 Aug 2015 18:51:24 +0200 Subject: [PATCH 054/102] Various C# Script improvements Move handling code in a separate file Add caching ability for low-power devices (rpi..) Use a distinct API with MCC.MethodName() Stop script execution only on specific API calls --- MinecraftClient/CSharpRunner.cs | 372 ++++++++++++++++++ MinecraftClient/ChatBot.cs | 62 +-- MinecraftClient/ChatBots/Script.cs | 96 +---- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Settings.cs | 11 +- .../config/sample-script-extended.cs | 12 +- .../config/sample-script-with-chatbot.cs | 2 +- MinecraftClient/config/sample-script.cs | 10 +- 8 files changed, 407 insertions(+), 159 deletions(-) create mode 100644 MinecraftClient/CSharpRunner.cs diff --git a/MinecraftClient/CSharpRunner.cs b/MinecraftClient/CSharpRunner.cs new file mode 100644 index 0000000000..46add1122a --- /dev/null +++ b/MinecraftClient/CSharpRunner.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Microsoft.CSharp; +using System.CodeDom.Compiler; +using System.Reflection; +using System.Threading; + +namespace MinecraftClient +{ + /// + /// C# Script runner - Compile on-the-fly and run C# scripts + /// + class CSharpRunner + { + private static readonly Dictionary CompileCache = new Dictionary(); + + /// + /// Run the specified C# script file + /// + /// ChatBot handler for accessing ChatBot API + /// Tick handler for waiting after some API calls + /// Lines of the script file to run + /// Arguments to pass to the script + /// Set to false to compile and cache the script without launching it + /// Thrown if an error occured + /// Result of the execution, returned by the script + public static object Run(ChatBot apiHandler, ManualResetEvent tickHandler, string[] lines, string[] args, bool run = true) + { + //Script compatibility check for handling future versions differently + if (lines.Length < 1 || lines[0] != "//MCCScript 1.0") + throw new CSharpException(CSErrorType.InvalidScript, + new InvalidDataException("The provided script does not have a valid MCCScript header")); + + //Script hash for determining if it was previously compiled + ulong scriptHash = QuickHash(lines); + Assembly assembly = null; + + //No need to compile two scripts at the same time + lock (CompileCache) + { + ///Process and compile script only if not already compiled + if (!Settings.CacheScripts || !CompileCache.ContainsKey(scriptHash)) + { + //Process different sections of the script file + bool scriptMain = true; + List script = new List(); + List extensions = new List(); + foreach (string line in lines) + { + if (line.StartsWith("//MCCScript")) + { + if (line.EndsWith("Extensions")) + scriptMain = false; + } + else if (scriptMain) + script.Add(line); + else extensions.Add(line); + } + + //Add return statement if missing + if (script.All(line => !line.StartsWith("return ") && !line.Contains(" return "))) + script.Add("return null;"); + + //Generate a class from the given script + string code = String.Join("\n", new string[] + { + "using System;", + "using System.Collections.Generic;", + "using System.Linq;", + "using System.Text;", + "using System.IO;", + "using System.Threading;", + "using MinecraftClient;", + "namespace ScriptLoader {", + "public class Script {", + "public CSharpAPI MCC;", + "public object __run(CSharpAPI __apiHandler, string[] args) {", + "this.MCC = __apiHandler;", + String.Join("\n", script), + "}", + String.Join("\n", extensions), + "}}", + }); + + //Compile the C# class in memory using all the currently loaded assemblies + CSharpCodeProvider compiler = new CSharpCodeProvider(); + CompilerParameters parameters = new CompilerParameters(); + parameters.ReferencedAssemblies + .AddRange(AppDomain.CurrentDomain + .GetAssemblies() + .Where(a => !a.IsDynamic) + .Select(a => a.Location).ToArray()); + parameters.CompilerOptions = "/t:library"; + parameters.GenerateInMemory = true; + CompilerResults result = compiler.CompileAssemblyFromSource(parameters, code); + + //Process compile warnings and errors + if (result.Errors.Count > 0) + throw new CSharpException(CSErrorType.LoadError, + new InvalidOperationException(result.Errors[0].ErrorText)); + + //Retrieve compiled assembly + assembly = result.CompiledAssembly; + if (Settings.CacheScripts) + CompileCache[scriptHash] = result.CompiledAssembly; + } + else if (Settings.CacheScripts) + assembly = CompileCache[scriptHash]; + } + + //Run the compiled assembly with exception handling + if (run) + { + try + { + object compiledScript + = CompileCache[scriptHash].CreateInstance("ScriptLoader.Script"); + return + compiledScript + .GetType() + .GetMethod("__run") + .Invoke(compiledScript, + new object[] { new CSharpAPI(apiHandler, tickHandler), args }); + } + catch (Exception e) { throw new CSharpException(CSErrorType.RuntimeError, e); } + } + else return null; + } + + /// + /// Quickly calculate a hash for the given script + /// + /// script lines + /// Quick hash as unsigned long + private static ulong QuickHash(string[] lines) + { + ulong hashedValue = 3074457345618258791ul; + for (int i = 0; i < lines.Length; i++) + { + for (int j = 0; j < lines[i].Length; j++) + { + hashedValue += lines[i][j]; + hashedValue *= 3074457345618258799ul; + } + hashedValue += '\n'; + hashedValue *= 3074457345618258799ul; + } + return hashedValue; + } + } + + /// + /// Describe a C# script error type + /// + public enum CSErrorType { FileReadError, InvalidScript, LoadError, RuntimeError }; + + /// + /// Describe a C# script error with associated error type + /// + public class CSharpException : Exception + { + private CSErrorType _type; + public CSErrorType ExceptionType { get { return _type; } } + public override string Message { get { return InnerException.Message; } } + public override string ToString() { return InnerException.ToString(); } + public CSharpException(CSErrorType type, Exception inner) + : base(inner != null ? inner.Message : "", inner) + { + _type = type; + } + } + + /// + /// Represents the C# API object accessible from C# Scripts + /// + public class CSharpAPI : ChatBot + { + /// + /// Thread blocking utility for stopping execution when making a ChatBot API call + /// + private ManualResetEvent tickHandler; + + /// + /// Create a new C# API Wrapper + /// + /// ChatBot API Handler + /// ChatBot tick handler + public CSharpAPI(ChatBot apiHandler, ManualResetEvent tickHandler) + { + SetMaster(apiHandler); + this.tickHandler = tickHandler; + } + + /* == Wrappers for ChatBot API with public visibility and call limit to one per tick for safety == */ + + /// + /// Write some text in the console. Nothing will be sent to the server. + /// + /// Log text to write + new public void LogToConsole(object text) + { + base.LogToConsole(text); + } + + /// + /// Send text to the server. Can be anything such as chat messages or commands + /// + /// Text to send to the server + /// True if the text was sent with no error + new public bool SendText(object text) + { + bool result = base.SendText(text is string ? (string)text : text.ToString()); + tickHandler.WaitOne(); + Thread.Sleep(1000); + return result; + } + + /// + /// Perform an internal MCC command (not a server command, use SendText() instead for that!) + /// + /// The command to process + /// TRUE if the command was indeed an internal MCC command + new public bool PerformInternalCommand(string command) + { + bool result = base.PerformInternalCommand(command); + tickHandler.WaitOne(); + return result; + } + + /// + /// Disconnect from the server and restart the program + /// It will unload and reload all the bots and then reconnect to the server + /// + /// If connection fails, the client will make X extra attempts + new public void ReconnectToTheServer(int extraAttempts = -999999) + { + if (extraAttempts == -999999) + base.ReconnectToTheServer(); + else base.ReconnectToTheServer(extraAttempts); + tickHandler.WaitOne(); + } + + /// + /// Disconnect from the server and exit the program + /// + new public void DisconnectAndExit() + { + base.DisconnectAndExit(); + tickHandler.WaitOne(); + } + + /// + /// Load the provided ChatBot object + /// + /// Bot to load + new public void LoadBot(ChatBot bot) + { + base.LoadBot(bot); + tickHandler.WaitOne(); + } + + /* == Additional Methods useful for Script API == */ + + /// + /// Get a global variable by name + /// + /// Name of the variable + /// Value of the variable or null if no variable + public object GetVar(string varName) + { + return Settings.GetVar(varName); + } + + /// + /// Get a global variable by name, as a string + /// + /// Name of the variable + /// Value of the variable as string, or null if no variable + public string GetVarAsString(string varName) + { + object val = GetVar(varName); + if (val != null) + return val.ToString(); + return null; + } + + /// + /// Get a global variable by name, as an integer + /// + /// Name of the variable + /// Value of the variable as int, or 0 if no variable or not a number + public int GetVarAsInt(string varName) + { + if (GetVar(varName) is int) + return (int)GetVar(varName); + int result; + if (int.TryParse(GetVarAsString(varName), out result)) + return result; + return 0; + } + + /// + /// Get a global variable by name, as a boolean + /// + /// Name of the variable + /// Value of the variable as bool, or false if no variable or not a boolean + public bool GetVarAsBool(string varName) + { + if (GetVar(varName) is bool) + return (bool)GetVar(varName); + bool result; + if (bool.TryParse(GetVarAsString(varName), out result)) + return result; + return false; + } + + /// + /// Set a global variable for further use in any other script + /// + /// Name of the variable + /// Value of the variable + public bool SetVar(string varName, object varValue) + { + return Settings.SetVar(varName, varValue); + } + + /// + /// Load login/password using an account alias and optionally reconnect to the server + /// + /// Account alias + /// Set to true to reconnecto to the server afterwards + /// True if the account was found and loaded + public bool SetAccount(string accountAlias, bool andReconnect = false) + { + bool result = Settings.SetAccount(accountAlias); + if (result && andReconnect) + ReconnectToTheServer(); + return result; + } + + /// + /// Load new server information and optionally reconnect to the server + /// + /// "serverip:port" couple or server alias + /// True if the server IP was valid and loaded, false otherwise + public bool SetServer(string server, bool andReconnect = false) + { + bool result = Settings.SetServerIP(server); + if (result && andReconnect) + ReconnectToTheServer(); + return result; + } + + /// + /// Synchronously call another script and retrieve the result + /// + /// Script to call + /// Arguments to pass to the script + /// An object returned by the script, or null + public object CallScript(string script, string[] args) + { + string[] lines = null; + ChatBots.Script.LookForScript(ref script); + try { lines = File.ReadAllLines(script); } + catch (Exception e) { throw new CSharpException(CSErrorType.FileReadError, e); } + return CSharpRunner.Run(this, tickHandler, lines, args); + } + } +} diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index b8c71706b7..4c0a5c0923 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -85,12 +85,10 @@ public virtual void GetText(string text) { } /// Text to send to the server /// True if the text was sent with no error - protected bool SendText(object text) + protected bool SendText(string text) { LogToConsole("Sending '" + text + "'"); - bool result = Handler.SendText(text is string ? (string)text : text.ToString()); - Thread.Sleep(1000); - return result; + return Handler.SendText(text); } /// @@ -348,7 +346,7 @@ protected static bool IsTeleportRequest(string text, ref string sender) /// /// Log text to write - public void LogToConsole(object text) + protected void LogToConsole(object text) { ConsoleIO.WriteLogLine(String.Format("[{0}] {1}", this.GetType().Name, text)); string logfile = Settings.ExpandVars(Settings.chatbotLogFile); @@ -458,59 +456,5 @@ protected string[] LoadDistinctEntriesFromFile(string file) return new string[0]; } } - - /// - /// Set a custom %variable% which will be available through expandVars() - /// - /// Name of the variable - /// Value of the variable - /// True if the parameters were valid - - protected static bool SetVar(string varName, object varData) - { - return Settings.SetVar(varName, varData.ToString()); - } - - /// - /// Get a custom %variable% or null if the variable does not exist - /// - /// Variable name - /// The value or null if the variable does not exists - - protected static string GetVar(string varName) - { - return Settings.GetVar(varName); - } - - /// - /// Get a custom %variable% as an Integer or null if the variable does not exist - /// - /// Variable name - /// The value or null if the variable does not exists - - protected static int GetVarAsInt(string varName) - { - return Settings.str2int(Settings.GetVar(varName)); - } - - /// - /// Load login/password using an account alias - /// - /// True if the account was found and loaded - - protected static bool SetAccount(string accountAlias) - { - return Settings.SetAccount(accountAlias); - } - - /// - /// Load server information in ServerIP and ServerPort variables from a "serverip:port" couple or server alias - /// - /// True if the server IP was valid and loaded, false otherwise - - protected static bool SetServerIP(string server) - { - return Settings.SetServerIP(server); - } } } diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs index 0e1894d933..4e464f1a8e 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -5,6 +5,7 @@ using System.Threading; using Microsoft.CSharp; using System.CodeDom.Compiler; +using System.Reflection; namespace MinecraftClient.ChatBots { @@ -143,8 +144,18 @@ public override void Update() tpause = new ManualResetEvent(false); thread = new Thread(() => { - if (!RunCSharpScript() && owner != null) - SendPrivateMessage(owner, "Script '" + file + "' failed to run."); + try + { + CSharpRunner.Run(this, tpause, lines, args); + } + catch (CSharpException e) + { + string errorMessage = "Script '" + file + "' failed to run (" + e.ExceptionType + ")."; + LogToConsole(errorMessage); + if (owner != null) + SendPrivateMessage(owner, errorMessage); + LogToConsole(e.InnerException); + } }); thread.Start(); } @@ -154,7 +165,7 @@ public override void Update() { tpause.Set(); tpause.Reset(); - if (thread.Join(100)) + if (!thread.IsAlive) UnloadBot(); } } @@ -205,84 +216,5 @@ public override void Update() } } } - - private bool RunCSharpScript() - { - //Script compatibility check for handling future versions differently - if (lines.Length < 1 || lines[0] != "//MCCScript 1.0") - { - LogToConsole("Script file '" + file + "' does not start with a valid //MCCScript identifier."); - return false; - } - - //Process different sections of the script file - bool scriptMain = true; - List script = new List(); - List extensions = new List(); - foreach (string line in lines) - { - if (line.StartsWith("//MCCScript")) - { - if (line.EndsWith("Extensions")) - scriptMain = false; - } - else if (scriptMain) - { - script.Add(line); - //Add breakpoints for step-by-step execution of the script - if (tpause != null && line.Trim().EndsWith(";")) - script.Add("tpause.WaitOne();"); - } - else extensions.Add(line); - } - - //Generate a ChatBot class, allowing access to the ChatBot API - string code = String.Join("\n", new string[] - { - "using System;", - "using System.IO;", - "using System.Threading;", - "using MinecraftClient;", - "namespace ScriptLoader {", - "public class Script : ChatBot {", - "public void __run(ChatBot master, ManualResetEvent tpause, string[] args) {", - "SetMaster(master);", - String.Join("\n", script), - "}", - String.Join("\n", extensions), - "}}", - }); - - //Compile the C# class in memory using all the currently loaded assemblies - CSharpCodeProvider compiler = new CSharpCodeProvider(); - CompilerParameters parameters = new CompilerParameters(); - parameters.ReferencedAssemblies - .AddRange(AppDomain.CurrentDomain - .GetAssemblies() - .Where(a => !a.IsDynamic) - .Select(a => a.Location).ToArray()); - parameters.CompilerOptions = "/t:library"; - parameters.GenerateInMemory = true; - CompilerResults result - = compiler.CompileAssemblyFromSource(parameters, code); - - //Process compile warnings and errors - if (result.Errors.Count > 0) - { - LogToConsole("Error loading '" + file + "':\n" + result.Errors[0].ErrorText); - return false; - } - - //Run the compiled script with exception handling - object compiledScript = result.CompiledAssembly.CreateInstance("ScriptLoader.Script"); - try { compiledScript.GetType().GetMethod("__run").Invoke(compiledScript, new object[] { this, tpause, args }); } - catch (Exception e) - { - LogToConsole("Runtime error for '" + file + "':\n" + e); - return false; - } - - return true; - } } } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index bc276c9ead..9e1fc0c71a 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -113,6 +113,7 @@ + diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 0985287e6e..40a51cb14a 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -45,6 +45,7 @@ public static class Settings public static char internalCmdChar = '/'; public static bool playerHeadAsIcon = false; public static string chatbotLogFile = ""; + public static bool CacheScripts = true; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -94,7 +95,7 @@ public static class Settings public static string AutoRespond_Matches = "matches.ini"; //Custom app variables and Minecraft accounts - private static readonly Dictionary AppVars = new Dictionary(); + private static readonly Dictionary AppVars = new Dictionary(); private static readonly Dictionary> Accounts = new Dictionary>(); private static readonly Dictionary> Servers = new Dictionary>(); @@ -159,6 +160,7 @@ public static void LoadSettings(string settingsfile) 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 "botowners": Bots_Owners.Clear(); @@ -366,6 +368,7 @@ public static void WriteDefaultSettings(string settingsfile) + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" + "exitonfailure=false\r\n" + + "scriptcache=true\r\n" + "timestamps=false\r\n" + "\r\n" + "[AppVars]\r\n" @@ -491,7 +494,7 @@ public static bool SetServerIP(string server) /// 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) { lock (AppVars) { @@ -511,7 +514,7 @@ public static bool SetVar(string varName, string varData) /// Variable name /// The value or null if the variable does not exists - public static string GetVar(string varName) + public static object GetVar(string varName) { if (AppVars.ContainsKey(varName)) return AppVars[varName]; @@ -559,7 +562,7 @@ public static string ExpandVars(string str) 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/sample-script-extended.cs b/MinecraftClient/config/sample-script-extended.cs index 68ff8cd174..b8751f0b1d 100644 --- a/MinecraftClient/config/sample-script-extended.cs +++ b/MinecraftClient/config/sample-script-extended.cs @@ -9,8 +9,8 @@ for (int i = 0; i < 5; i++) { - int count = GetVarAsInt("test") + 1; - SetVar("test", count); + int count = MCC.GetVarAsInt("test") + 1; + MCC.SetVar("test", count); SendHelloWorld(count, text); SleepBetweenSends(); } @@ -21,15 +21,11 @@ void SendHelloWorld(int count, string text) { - /* Warning: Do not make more than one server-related call into a method - * defined as a script extension eg SendText or switching servers, - * as execution flow is not managed in the Extensions section */ - - SendText("Hello World no. " + count + ": " + text); + MCC.SendText("Hello World no. " + count + ": " + text); } void SleepBetweenSends() { - LogToConsole("Sleeping for 5 seconds..."); + 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 index 264711fac9..25cb13ebc4 100644 --- a/MinecraftClient/config/sample-script-with-chatbot.cs +++ b/MinecraftClient/config/sample-script-with-chatbot.cs @@ -3,7 +3,7 @@ /* 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 */ -LoadBot(new ExampleBot()); +MCC.LoadBot(new ExampleBot()); //MCCScript Extensions diff --git a/MinecraftClient/config/sample-script.cs b/MinecraftClient/config/sample-script.cs index 3d61b25cb1..34d3354a46 100644 --- a/MinecraftClient/config/sample-script.cs +++ b/MinecraftClient/config/sample-script.cs @@ -2,13 +2,13 @@ /* 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 all methods provided by the bot API */ + * Allowed instructions: Any C# code AND methods provided by the MCC API */ for (int i = 0; i < 5; i++) { - int count = GetVarAsInt("test") + 1; - SetVar("test", count); - SendText("Hello World no. " + count); - LogToConsole("Sleeping for 5 seconds..."); + 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 From 88c9605e940b0c5d1e548f7e57cb869ee429f221 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 2 Sep 2015 23:01:01 -0400 Subject: [PATCH 055/102] Added a new PM regex Added [someone @ me] message so that remote control could be used on more servers. Added back Hero Chat Messages with a default config option of; herochatmessagesenabled=false which safely disables it for everyone and has to be explicitly enabled for it to goof anything up. How this happened was me downloading the "source" from minecraft forums and modifying that source then trying to merge it back to the main github fork of mine. --- .vs/MinecraftClient/v14/.suo | Bin 0 -> 3584 bytes MinecraftClient/ChatBot.cs | 22 ++++++++++++++++++++++ MinecraftClient/Settings.cs | 3 +++ 3 files changed, 25 insertions(+) create mode 100644 .vs/MinecraftClient/v14/.suo diff --git a/.vs/MinecraftClient/v14/.suo b/.vs/MinecraftClient/v14/.suo new file mode 100644 index 0000000000000000000000000000000000000000..9b02a86378d3ff782804856f32fdca328b07c6a2 GIT binary patch literal 3584 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx6O1pwz`z1zgT(&*|NkE(%nZap<|r6K zA@CRIR#5znf{_^lK@9moSi+#d;L4B(q>C6TL0Oq-AjZIolIMxh2rDm$QA>;(etC4yf%0YR`em9P)QxOY4A9;TaLMfibV&)&y~#i~86-q%&q+PEFx@fDg_$jR mA~1=gl-Hne0Ev+cUdWqU*~urSWXP>Q!M3qChg@?8N*@4)1zMs2 literal 0 HcmV?d00001 diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 4c0a5c0923..27459bf9fb 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -197,6 +197,17 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } + //Detect Modified server messages. /m + //[Someone @ me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" + && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers + { + message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } + //Detect Essentials (Bukkit) /me messages with some custom prefix //[Prefix] [Someone -> me] message //[Prefix] [~Someone -> me] message @@ -231,6 +242,17 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } + //Detect HeroChat Messages + //[Channel] [Rank] User: Message + else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) + { + int name_end = text.IndexOf(':'); + int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; + sender = text.Substring(name_start, name_end - name_start); + message = text.Substring(name_end + 2); + return IsValidName(sender); + } + else return false; } catch (IndexOutOfRangeException) { return false; } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 40a51cb14a..d09a591220 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,6 +89,7 @@ public static class Settings public static bool RemoteCtrl_Enabled = false; public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; + public static bool Hero_Chat_Messages_Enabled = false; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -282,6 +283,7 @@ public static void LoadSettings(string settingsfile) case "enabled": RemoteCtrl_Enabled = str2bool(argValue); break; case "autotpaccept": RemoteCtrl_AutoTpaccept = str2bool(argValue); break; case "tpaccepteveryone": RemoteCtrl_AutoTpaccept_Everyone = str2bool(argValue); break; + case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; } break; @@ -422,6 +424,7 @@ public static void WriteDefaultSettings(string settingsfile) + "enabled=false\r\n" + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + + "herochatmessagesenabled=false\r\n" + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" From 450cb4c6b98754bc6d1cf62a778c631dcce195d3 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 2 Sep 2015 23:01:30 -0400 Subject: [PATCH 056/102] Revert "Added a new PM regex" This reverts commit 88c9605e940b0c5d1e548f7e57cb869ee429f221. --- .vs/MinecraftClient/v14/.suo | Bin 3584 -> 0 bytes MinecraftClient/ChatBot.cs | 22 ---------------------- MinecraftClient/Settings.cs | 3 --- 3 files changed, 25 deletions(-) delete mode 100644 .vs/MinecraftClient/v14/.suo diff --git a/.vs/MinecraftClient/v14/.suo b/.vs/MinecraftClient/v14/.suo deleted file mode 100644 index 9b02a86378d3ff782804856f32fdca328b07c6a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3584 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx6O1pwz`z1zgT(&*|NkE(%nZap<|r6K zA@CRIR#5znf{_^lK@9moSi+#d;L4B(q>C6TL0Oq-AjZIolIMxh2rDm$QA>;(etC4yf%0YR`em9P)QxOY4A9;TaLMfibV&)&y~#i~86-q%&q+PEFx@fDg_$jR mA~1=gl-Hne0Ev+cUdWqU*~urSWXP>Q!M3qChg@?8N*@4)1zMs2 diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 27459bf9fb..4c0a5c0923 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -197,17 +197,6 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } - //Detect Modified server messages. /m - //[Someone @ me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" - && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers - { - message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } - //Detect Essentials (Bukkit) /me messages with some custom prefix //[Prefix] [Someone -> me] message //[Prefix] [~Someone -> me] message @@ -242,17 +231,6 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } - //Detect HeroChat Messages - //[Channel] [Rank] User: Message - else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) - { - int name_end = text.IndexOf(':'); - int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; - sender = text.Substring(name_start, name_end - name_start); - message = text.Substring(name_end + 2); - return IsValidName(sender); - } - else return false; } catch (IndexOutOfRangeException) { return false; } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index d09a591220..40a51cb14a 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,7 +89,6 @@ public static class Settings public static bool RemoteCtrl_Enabled = false; public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; - public static bool Hero_Chat_Messages_Enabled = false; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -283,7 +282,6 @@ public static void LoadSettings(string settingsfile) case "enabled": RemoteCtrl_Enabled = str2bool(argValue); break; case "autotpaccept": RemoteCtrl_AutoTpaccept = str2bool(argValue); break; case "tpaccepteveryone": RemoteCtrl_AutoTpaccept_Everyone = str2bool(argValue); break; - case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; } break; @@ -424,7 +422,6 @@ public static void WriteDefaultSettings(string settingsfile) + "enabled=false\r\n" + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" - + "herochatmessagesenabled=false\r\n" + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" From 35365a4b80774e2a4505f449c6e99c5842f88b1e Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 2 Sep 2015 23:01:46 -0400 Subject: [PATCH 057/102] Revert "Revert "Added a new PM regex"" This reverts commit 450cb4c6b98754bc6d1cf62a778c631dcce195d3. --- .vs/MinecraftClient/v14/.suo | Bin 0 -> 3584 bytes MinecraftClient/ChatBot.cs | 22 ++++++++++++++++++++++ MinecraftClient/Settings.cs | 3 +++ 3 files changed, 25 insertions(+) create mode 100644 .vs/MinecraftClient/v14/.suo diff --git a/.vs/MinecraftClient/v14/.suo b/.vs/MinecraftClient/v14/.suo new file mode 100644 index 0000000000000000000000000000000000000000..9b02a86378d3ff782804856f32fdca328b07c6a2 GIT binary patch literal 3584 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx6O1pwz`z1zgT(&*|NkE(%nZap<|r6K zA@CRIR#5znf{_^lK@9moSi+#d;L4B(q>C6TL0Oq-AjZIolIMxh2rDm$QA>;(etC4yf%0YR`em9P)QxOY4A9;TaLMfibV&)&y~#i~86-q%&q+PEFx@fDg_$jR mA~1=gl-Hne0Ev+cUdWqU*~urSWXP>Q!M3qChg@?8N*@4)1zMs2 literal 0 HcmV?d00001 diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 4c0a5c0923..27459bf9fb 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -197,6 +197,17 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } + //Detect Modified server messages. /m + //[Someone @ me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" + && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers + { + message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } + //Detect Essentials (Bukkit) /me messages with some custom prefix //[Prefix] [Someone -> me] message //[Prefix] [~Someone -> me] message @@ -231,6 +242,17 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri return IsValidName(sender); } + //Detect HeroChat Messages + //[Channel] [Rank] User: Message + else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) + { + int name_end = text.IndexOf(':'); + int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; + sender = text.Substring(name_start, name_end - name_start); + message = text.Substring(name_end + 2); + return IsValidName(sender); + } + else return false; } catch (IndexOutOfRangeException) { return false; } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 40a51cb14a..d09a591220 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,6 +89,7 @@ public static class Settings public static bool RemoteCtrl_Enabled = false; public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; + public static bool Hero_Chat_Messages_Enabled = false; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -282,6 +283,7 @@ public static void LoadSettings(string settingsfile) case "enabled": RemoteCtrl_Enabled = str2bool(argValue); break; case "autotpaccept": RemoteCtrl_AutoTpaccept = str2bool(argValue); break; case "tpaccepteveryone": RemoteCtrl_AutoTpaccept_Everyone = str2bool(argValue); break; + case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; } break; @@ -422,6 +424,7 @@ public static void WriteDefaultSettings(string settingsfile) + "enabled=false\r\n" + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + + "herochatmessagesenabled=false\r\n" + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" From 6ed17f5f9832cfef381e2803f3c6f86e0f3dc5b0 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Thu, 3 Sep 2015 23:58:38 -0400 Subject: [PATCH 058/102] Removal of .suo --- .vs/MinecraftClient/v14/.suo | Bin 3584 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .vs/MinecraftClient/v14/.suo diff --git a/.vs/MinecraftClient/v14/.suo b/.vs/MinecraftClient/v14/.suo deleted file mode 100644 index 9b02a86378d3ff782804856f32fdca328b07c6a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3584 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx6O1pwz`z1zgT(&*|NkE(%nZap<|r6K zA@CRIR#5znf{_^lK@9moSi+#d;L4B(q>C6TL0Oq-AjZIolIMxh2rDm$QA>;(etC4yf%0YR`em9P)QxOY4A9;TaLMfibV&)&y~#i~86-q%&q+PEFx@fDg_$jR mA~1=gl-Hne0Ev+cUdWqU*~urSWXP>Q!M3qChg@?8N*@4)1zMs2 From 60c95a66251f983e70c4c2ed9e9cdc71988905cb Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Thu, 3 Sep 2015 15:55:08 -0400 Subject: [PATCH 059/102] Added /.vs/ to git ignore. Adding /.vs/ to git ignore for Visual Studio 2015 Removed folder. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 307f491a00..163e9adaa6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /MinecraftClientGUI.v11.suo /MinecraftClientGUI.suo /MinecraftClient.userprefs +/.vs/ From cdec34d5ca5340c63deb240110facb8882088970 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Thu, 3 Sep 2015 23:42:01 -0400 Subject: [PATCH 060/102] I messed up and put the herochat *back* into a faulty position. Removed my mistake and updated the location of the Hero_Chat_Messages_Enabled clause that keeps it disabled unless needed. --- MinecraftClient/ChatBot.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 27459bf9fb..d7a7e412b5 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -241,18 +241,6 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri message = text.Substring(text.IndexOf(':') + 2); return IsValidName(sender); } - - //Detect HeroChat Messages - //[Channel] [Rank] User: Message - else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) - { - int name_end = text.IndexOf(':'); - int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; - sender = text.Substring(name_start, name_end - name_start); - message = text.Substring(name_end + 2); - return IsValidName(sender); - } - else return false; } catch (IndexOutOfRangeException) { return false; } @@ -297,8 +285,9 @@ protected static bool IsChatMessage(string text, ref string message, ref string } //Detect HeroChat Messages + //Public chat messages //[Channel] [Rank] User: Message - else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2) + else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) { int name_end = text.IndexOf(':'); int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; From b233b60abac95434b9475215c969c2f0b1c193ef Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Thu, 3 Sep 2015 23:44:07 -0400 Subject: [PATCH 061/102] Default acceptance of Hero-Chat public messages changed. Changed from default of disabled to enabled. *True* --- MinecraftClient/Settings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index d09a591220..272404404b 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,7 +89,7 @@ public static class Settings public static bool RemoteCtrl_Enabled = false; public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; - public static bool Hero_Chat_Messages_Enabled = false; + public static bool Hero_Chat_Messages_Enabled = true; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -424,7 +424,7 @@ public static void WriteDefaultSettings(string settingsfile) + "enabled=false\r\n" + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" - + "herochatmessagesenabled=false\r\n" + + "herochatmessagesenabled=true\r\n" + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" From 1abb46b8cabe8614da298409f644ea37859bae98 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Thu, 3 Sep 2015 23:59:15 -0400 Subject: [PATCH 062/102] Added / Cleaned Enable features of Chat Messages --- MinecraftClient/ChatBot.cs | 3 ++- MinecraftClient/Settings.cs | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index d7a7e412b5..ebdbcc08e2 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -306,7 +306,8 @@ protected static bool IsChatMessage(string text, ref string message, ref string && text.IndexOf('*') < text.IndexOf('<') && text.IndexOf('<') < text.IndexOf('>') && text.IndexOf('>') < text.IndexOf(' ') - && text.IndexOf(' ') < text.IndexOf(':')) + && text.IndexOf(' ') < text.IndexOf(':') + && Settings.Unknown_Chat_Plugin_Messages_One_Enabled.Equals(true)) { string prefix = tmp[0]; string user = tmp[1]; diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 272404404b..a22ec2963b 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,7 +89,10 @@ public static class Settings public static bool RemoteCtrl_Enabled = false; public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; + + //Chat Message Enabled / Disabled. public static bool Hero_Chat_Messages_Enabled = true; + public static bool Unknown_Chat_Plugin_Messages_One_Enabled = true; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -100,7 +103,7 @@ public static class Settings 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, AutoRespond }; + private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, ChatBotMessages, AutoRespond }; /// /// Load settings from the give INI file @@ -287,6 +290,15 @@ public static void LoadSettings(string settingsfile) } break; + case ParseMode.ChatBotMessages: + switch (argName.ToLower()) + { + case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; + case "unknownchatpluginmessagesone": Unknown_Chat_Plugin_Messages_One_Enabled = str2bool(argValue); break; + + } + break; + case ParseMode.Proxy: switch (argName.ToLower()) { @@ -424,7 +436,9 @@ public static void WriteDefaultSettings(string settingsfile) + "enabled=false\r\n" + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + + "[ChatBotMessages]\r\n" + "herochatmessagesenabled=true\r\n" + + "unknownchatpluginmessagesone=true\r\n" + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" From 93aae2d4679fe4edeb5db0cfd4c27c445c01551b Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Fri, 4 Sep 2015 00:15:02 -0400 Subject: [PATCH 063/102] Commented Auto-Generated MinecraftClient.ini Added example chat format in the ini comments. Further cleaning up my own mistakes as well. Note: I'm learning still. Getting better AND fast though. --- MinecraftClient/Settings.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index a22ec2963b..ee14b0e1fc 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -436,9 +436,10 @@ public static void WriteDefaultSettings(string settingsfile) + "enabled=false\r\n" + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + + "\r\n" + "[ChatBotMessages]\r\n" - + "herochatmessagesenabled=true\r\n" - + "unknownchatpluginmessagesone=true\r\n" + + "herochatmessagesenabled=true # Chat Format is \"[Channel][Rank] User: Message\"\r\n" + + "unknownchatpluginmessagesone=true # Chat Format is \"**Faction User : Message\"\r\n" + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" From 385a1f99b11d0ab3748d27ad934f1fe902030c52 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Fri, 4 Sep 2015 09:16:28 -0400 Subject: [PATCH 064/102] Added another setting. Added vanillaandfactionsmessages setting that enables / disables detection of vanilla / factions public chat messages. Setting has been added to the auto-generated MinecraftClient.ini and has been commented with respective chat format of " message" and "<*faction user>: message" Clause added to ChatBot.cs that makes use of the new setting. --- MinecraftClient/ChatBot.cs | 2 +- MinecraftClient/Settings.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index ebdbcc08e2..5d568289e6 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -266,7 +266,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string //<*Faction Someone> message //<*Faction Someone>: message //<*Faction ~Nicknamed>: message - if (text[0] == '<') + if (text[0] == '<' && Settings.Vanilla_And_Factions_Messages_Enabled.Equals(true)) { try { diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index ee14b0e1fc..ae231d63d5 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -93,6 +93,7 @@ public static class Settings //Chat Message Enabled / Disabled. public static bool Hero_Chat_Messages_Enabled = true; public static bool Unknown_Chat_Plugin_Messages_One_Enabled = true; + public static bool Vanilla_And_Factions_Messages_Enabled = true; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -295,7 +296,8 @@ public static void LoadSettings(string settingsfile) { case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; case "unknownchatpluginmessagesone": Unknown_Chat_Plugin_Messages_One_Enabled = str2bool(argValue); break; - + case "vanillaandfactionsmessages": Vanilla_And_Factions_Messages_Enabled = str2bool(argValue); break; + } break; @@ -438,6 +440,7 @@ public static void WriteDefaultSettings(string settingsfile) + "tpaccepteveryone=false\r\n" + "\r\n" + "[ChatBotMessages]\r\n" + + "vanillaandfactionsmessages=true # Chat Formats \" Message\" \"<*Faction User>: Message\" \r\n" + "herochatmessagesenabled=true # Chat Format is \"[Channel][Rank] User: Message\"\r\n" + "unknownchatpluginmessagesone=true # Chat Format is \"**Faction User : Message\"\r\n" + "\r\n" From 1223c91d79c67a144176a49c95484f947ebfec17 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Fri, 4 Sep 2015 09:54:38 -0400 Subject: [PATCH 065/102] Added setting to make sending brand info optional. sendbrandinfo=true|false was added so we can optionally send client info. Enabled by default. Added sendbrandinfo into auto-generated ini file. Edited Protocol18.cs to reflect this with an "if" statement before SendBrandInfo() is called upon. Fixed minor mistake of not adding chatbotmessages into Parsemode. Parsemode.Default was being used. --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 5 ++++- MinecraftClient/Settings.cs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index c6357f73e5..26054daf6b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -140,7 +140,10 @@ private bool handlePacket(int packetID, byte[] packetData) SendPacket(0x00, packetData); break; case 0x01: //Join game - SendBrandInfo(); + if (Settings.SendBrandInfoEnabled.Equals(true)) + { + SendBrandInfo(); + } break; case 0x02: //Chat message handler.OnTextReceived(ChatParser.ParseText(readNextString(ref packetData))); diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index ae231d63d5..93beb6a88c 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -46,6 +46,7 @@ public static class Settings public static bool playerHeadAsIcon = false; public static string chatbotLogFile = ""; public static bool CacheScripts = true; + public static bool SendBrandInfoEnabled = true; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -139,6 +140,7 @@ public static void LoadSettings(string settingsfile) case "proxy": pMode = ParseMode.Proxy; break; case "appvars": pMode = ParseMode.AppVars; break; case "autorespond": pMode = ParseMode.AutoRespond; break; + case "chatbotmessages": pMode = ParseMode.ChatBotMessages; break; default: pMode = ParseMode.Default; break; } } @@ -166,6 +168,7 @@ public static void LoadSettings(string settingsfile) case "mcversion": ServerVersion = argValue; break; case "splitmessagedelay": splitMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break; case "scriptcache": CacheScripts = str2bool(argValue); break; + case "sendbrandinfo": SendBrandInfoEnabled = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); @@ -386,6 +389,7 @@ public static void WriteDefaultSettings(string settingsfile) + "exitonfailure=false\r\n" + "scriptcache=true\r\n" + "timestamps=false\r\n" + + "sendbrandinfo=true\r\n" + "\r\n" + "[AppVars]\r\n" + "#yourvar=yourvalue\r\n" From 856075394967bc626642c39cf7c5a5ac1ebf20a9 Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 29 Sep 2015 14:00:44 +0200 Subject: [PATCH 066/102] Extend BrandInfo setting Brand Information tells the server what client is being used to connect to the server, possible values are the following: - none (do not tell anything) - vanilla (tells that you are using MC vanilla) - mcc (tell that you are using MCC + version) This will usually not do anything unless plugins developers use this information for developing some MCC interoperability eg more chat interactions instead of using GUIs. This could also be used to block third party clients, that's why brand information can be disabled or changed to vanilla. --- MinecraftClient/McTcpClient.cs | 3 ++ .../Protocol/Handlers/Protocol16.cs | 5 +++ .../Protocol/Handlers/Protocol18.cs | 43 ++++++++++--------- MinecraftClient/Protocol/IMinecraftCom.cs | 8 ++++ MinecraftClient/Settings.cs | 17 ++++++-- 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 0817755c53..ccb04f713d 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -120,6 +120,9 @@ private void StartClient(string user, string uuid, string sessionID, string serv { if (handler.Login()) { + if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) + handler.SendBrandInfo(Settings.BrandInfo.Trim()); + if (singlecommand) { handler.SendChatMessage(command); diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index cf648fc832..1813b9afd7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -610,6 +610,11 @@ public bool SendRespawnPacket() catch (SocketException) { return false; } } + public bool SendBrandInfo(string brandInfo) + { + return false; //Only supported since MC 1.7 + } + public string AutoComplete(string BehindCursor) { if (String.IsNullOrEmpty(BehindCursor)) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 26054daf6b..925121107c 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -139,12 +139,6 @@ private bool handlePacket(int packetID, byte[] packetData) case 0x00: //Keep-Alive SendPacket(0x00, packetData); break; - case 0x01: //Join game - if (Settings.SendBrandInfoEnabled.Equals(true)) - { - SendBrandInfo(); - } - break; case 0x02: //Chat message handler.OnTextReceived(ChatParser.ParseText(readNextString(ref packetData))); break; @@ -613,20 +607,6 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string } } - /// - /// Sends information about the client version. - /// - - private void SendBrandInfo() - { - byte[] channel = Encoding.UTF8.GetBytes("MC|Brand"); - byte[] channelLen = getVarInt(channel.Length); - byte[] brand = Encoding.UTF8.GetBytes("Minecraft Console Client v" + Program.Version); - byte[] brandLen = getVarInt(brand.Length); - - SendPacket(0x17, concatBytes(channelLen, channel, brandLen, brand)); - } - /// /// Send a chat message to the server /// @@ -665,6 +645,29 @@ public bool SendRespawnPacket() 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; + try + { + byte[] channel = Encoding.UTF8.GetBytes("MC|Brand"); + byte[] channelLen = getVarInt(channel.Length); + byte[] brand = Encoding.UTF8.GetBytes(brandInfo); + byte[] brandLen = getVarInt(brand.Length); + SendPacket(0x17, concatBytes(channelLen, channel, brandLen, brand)); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + } + /// /// Disconnect from the server /// diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 2ae82c1404..9c351ebc30 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -43,5 +43,13 @@ public interface IMinecraftCom : IDisposable, IAutoComplete /// True if packet successfully sent bool SendRespawnPacket(); + + /// + /// 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 + + bool SendBrandInfo(string brandInfo); } } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 93beb6a88c..7a4a4cb5b2 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -13,6 +13,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 @@ -46,7 +49,7 @@ public static class Settings public static bool playerHeadAsIcon = false; public static string chatbotLogFile = ""; public static bool CacheScripts = true; - public static bool SendBrandInfoEnabled = true; + public static string BrandInfo = MCCBrandInfo; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -168,7 +171,6 @@ public static void LoadSettings(string settingsfile) case "mcversion": ServerVersion = argValue; break; case "splitmessagedelay": splitMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break; case "scriptcache": CacheScripts = str2bool(argValue); break; - case "sendbrandinfo": SendBrandInfoEnabled = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); @@ -224,6 +226,15 @@ public static void LoadSettings(string settingsfile) 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; @@ -382,6 +393,7 @@ public static void WriteDefaultSettings(string settingsfile) + "internalcmdchar=slash #use 'none', 'slash' or 'backslash'\r\n" + "splitmessagedelay=2 #seconds between each part of a long message\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" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" @@ -389,7 +401,6 @@ public static void WriteDefaultSettings(string settingsfile) + "exitonfailure=false\r\n" + "scriptcache=true\r\n" + "timestamps=false\r\n" - + "sendbrandinfo=true\r\n" + "\r\n" + "[AppVars]\r\n" + "#yourvar=yourvalue\r\n" From 8bd130eb3a9edeeec84bd55ddc1fae4d9be6b215 Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 29 Sep 2015 14:07:11 +0200 Subject: [PATCH 067/102] Add setting for hiding system/xpbar messages Add settings for disabling: - System Messages - XP Bar Messages Fix #95 --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 12 +++++++++++- MinecraftClient/Settings.cs | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 925121107c..f728c470f7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -140,7 +140,17 @@ private bool handlePacket(int packetID, byte[] packetData) SendPacket(0x00, packetData); break; case 0x02: //Chat message - handler.OnTextReceived(ChatParser.ParseText(readNextString(ref packetData))); + string message = readNextString(ref packetData); + try + { + //Hide system messages or xp bar messages? + byte messageType = readData(1, ref packetData)[0]; + if ((messageType == 1 && !Settings.DisplaySystemMessages) + || (messageType == 2 && !Settings.DisplayXPBarMessages)) + break; + } + catch (IndexOutOfRangeException) { /* No message type */ } + handler.OnTextReceived(ChatParser.ParseText(message)); break; case 0x38: //Player List update if (protocolversion >= MC18Version) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 7a4a4cb5b2..129e8d6d62 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -50,6 +50,8 @@ public static class Settings public static string chatbotLogFile = ""; public static bool CacheScripts = true; public static string BrandInfo = MCCBrandInfo; + public static bool DisplaySystemMessages = true; + public static bool DisplayXPBarMessages = true; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -171,6 +173,8 @@ public static void LoadSettings(string settingsfile) 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": DisplaySystemMessages = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); @@ -395,6 +399,8 @@ public static void WriteDefaultSettings(string settingsfile) + "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" + + "showsystemmessages=true #system messages for server ops\r\n" + + "showxpbarmessages=true #messages displayed above xp bar\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" From b25a665c82fa035e1fe240af784ab8ba577af7b3 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 30 Sep 2015 20:01:57 +0200 Subject: [PATCH 068/102] Delay BrandInfo sending Implement Game Join event and send Brand Info only when server acknowledged game join, as ZizzyDizzyMC did before, else server may generate an invalid packet error because it was still in "login" mode and not in "playing" mode. Fix second issue in #95 --- MinecraftClient/McTcpClient.cs | 13 ++++++++++--- MinecraftClient/Protocol/Handlers/Protocol18.cs | 3 +++ MinecraftClient/Protocol/IMinecraftComHandler.cs | 6 ++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index ccb04f713d..fed884a1a6 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -120,9 +120,6 @@ private void StartClient(string user, string uuid, string sessionID, string serv { if (handler.Login()) { - if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) - handler.SendBrandInfo(Settings.BrandInfo.Trim()); - if (singlecommand) { handler.SendChatMessage(command); @@ -322,6 +319,16 @@ public void Disconnect() client.Close(); } + /// + /// Called when a server was successfully joined + /// + + public void OnGameJoined() + { + if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) + handler.SendBrandInfo(Settings.BrandInfo.Trim()); + } + /// /// Received some text from the server /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index f728c470f7..8fac698e2c 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -139,6 +139,9 @@ private bool handlePacket(int packetID, byte[] packetData) case 0x00: //Keep-Alive SendPacket(0x00, packetData); break; + case 0x01: //Join game + handler.OnGameJoined(); + break; case 0x02: //Chat message string message = readNextString(ref packetData); try diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 79f8659e9f..606e80eaf8 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -23,6 +23,12 @@ public interface IMinecraftComHandler string GetSessionID(); string[] GetOnlinePlayers(); + /// + /// Called when a server was successfully joined + /// + + void OnGameJoined(); + /// /// This method is called when the protocol handler receives a chat message /// From 4df5cb724adf1b3eeb1b73729fed38570af67472 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 30 Sep 2015 20:24:00 +0200 Subject: [PATCH 069/102] Fix copy and past mistake with XPBar setting #95 --- MinecraftClient/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 129e8d6d62..b247957569 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -174,7 +174,7 @@ public static void LoadSettings(string settingsfile) case "splitmessagedelay": splitMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break; case "scriptcache": CacheScripts = str2bool(argValue); break; case "showsystemmessages": DisplaySystemMessages = str2bool(argValue); break; - case "showxpbarmessages": DisplaySystemMessages = str2bool(argValue); break; + case "showxpbarmessages": DisplayXPBarMessages = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); From 8f6b59eaa085b1917dc3a561cb3637728659174c Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 11 Oct 2015 19:55:26 +0200 Subject: [PATCH 070/102] Add help section for AutoRelog --- MinecraftClient/config/README.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/config/README.txt b/MinecraftClient/config/README.txt index 496dcea7c8..fa71e99441 100644 --- a/MinecraftClient/config/README.txt +++ b/MinecraftClient/config/README.txt @@ -1,4 +1,4 @@ -================================================================== +================================================================== Minecraft Client v1.8.2 for Minecraft 1.4.6 to 1.8.3 - By ORelio ================================================================== @@ -166,6 +166,14 @@ You can remotely send chat messages or commands using /tell send Date: Tue, 13 Oct 2015 00:31:24 +0200 Subject: [PATCH 071/102] Fix AutoRespond not handling "other" message type Bug report by ibspa --- MinecraftClient/ChatBots/AutoRespond.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 974ac8e6e7..bb14c5b1db 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -203,6 +203,7 @@ public override void GetText(string text) msgType = MessageType.Public; else if (IsPrivateMessage(text, ref message, ref sender)) msgType = MessageType.Private; + else message = text; //Do not process messages sent by the bot itself if (msgType == MessageType.Other || sender != Settings.Username) From 0b870e2b4991f7cb07a959f934b30ab3c3bec69e Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Thu, 15 Oct 2015 21:20:29 -0400 Subject: [PATCH 072/102] Proxy Setting addition. Added 'loginonlyproxy' option with true / false boolean options. on 'true' only the minecraft login is redirected to the proxy. Otherwise both the login and the server connection are routed though the chosen proxy. Provides a semi-workaround to issues #89 and #80 on ORelio/Indev --- MinecraftClient/McTcpClient.cs | 9 ++++++++- MinecraftClient/Settings.cs | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 0817755c53..53e5965f47 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -111,7 +111,14 @@ private void StartClient(string user, string uuid, string sessionID, string serv try { - client = ProxyHandler.newTcpClient(host, port); + if (Settings.LoginOnlyProxy = true) + { + client = new TcpClient(host, port); + } + else + { + client = ProxyHandler.newTcpClient(host, port); + } client.ReceiveBufferSize = 1024 * 1024; handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this); Console.WriteLine("Version is supported.\nLogging in..."); diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 93beb6a88c..5defadef5c 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -32,6 +32,7 @@ public static class Settings public static Proxy.ProxyHandler.Type proxyType = Proxy.ProxyHandler.Type.HTTP; public static string ProxyUsername = ""; public static string ProxyPassword = ""; + public static bool LoginOnlyProxy = false; //Other Settings public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\9e\9e2fdc43fc1c7024ff5922b998fadb2971a64ee0"; //MC 1.7.4 en_GB.lang @@ -290,7 +291,6 @@ public static void LoadSettings(string settingsfile) case "enabled": RemoteCtrl_Enabled = str2bool(argValue); break; case "autotpaccept": RemoteCtrl_AutoTpaccept = str2bool(argValue); break; case "tpaccepteveryone": RemoteCtrl_AutoTpaccept_Everyone = str2bool(argValue); break; - case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; } break; @@ -308,6 +308,7 @@ public static void LoadSettings(string settingsfile) switch (argName.ToLower()) { case "enabled": ProxyEnabled = str2bool(argValue); break; + case "loginonlyproxy": LoginOnlyProxy = str2bool(argValue); break; case "type": argValue = argValue.ToLower(); if (argValue == "http") { proxyType = Proxy.ProxyHandler.Type.HTTP; } @@ -402,6 +403,7 @@ public static void WriteDefaultSettings(string settingsfile) + "server=0.0.0.0:0000\r\n" + "username=\r\n" + "password=\r\n" + + "loginonlyproxy=false # Change this to \"true\" if you only want to use a proxy for login." + "\r\n" + "#Bot Settings\r\n" + "\r\n" From a65e6325228149f2ee5e64051fdd60b0c2ea421c Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Fri, 16 Oct 2015 04:50:56 -0400 Subject: [PATCH 073/102] Fixed some things about pull request 99. Changed LoginOnlyProxy to OnlyForLogin in settings, changed McTcpClient so I was not using assignment operator. (Was a mistake anyway.) --- MinecraftClient/McTcpClient.cs | 2 +- MinecraftClient/Settings.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 53e5965f47..413121582e 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -111,7 +111,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv try { - if (Settings.LoginOnlyProxy = true) + if (Settings.OnlyForLogin) { client = new TcpClient(host, port); } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 5defadef5c..b005e90c85 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -32,7 +32,7 @@ public static class Settings public static Proxy.ProxyHandler.Type proxyType = Proxy.ProxyHandler.Type.HTTP; public static string ProxyUsername = ""; public static string ProxyPassword = ""; - public static bool LoginOnlyProxy = false; + public static bool OnlyForLogin = false; //Other Settings public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\9e\9e2fdc43fc1c7024ff5922b998fadb2971a64ee0"; //MC 1.7.4 en_GB.lang @@ -308,7 +308,7 @@ public static void LoadSettings(string settingsfile) switch (argName.ToLower()) { case "enabled": ProxyEnabled = str2bool(argValue); break; - case "loginonlyproxy": LoginOnlyProxy = str2bool(argValue); break; + case "onlyforlogin": OnlyForLogin = str2bool(argValue); break; case "type": argValue = argValue.ToLower(); if (argValue == "http") { proxyType = Proxy.ProxyHandler.Type.HTTP; } @@ -403,7 +403,7 @@ public static void WriteDefaultSettings(string settingsfile) + "server=0.0.0.0:0000\r\n" + "username=\r\n" + "password=\r\n" - + "loginonlyproxy=false # Change this to \"true\" if you only want to use a proxy for login." + + "onlyforlogin=false # Change this to \"true\" if you only want to use a proxy for login." + "\r\n" + "#Bot Settings\r\n" + "\r\n" From e8a8ca4e7aa8568973da4384005024de330eeef0 Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 20 Oct 2015 22:54:58 +0200 Subject: [PATCH 074/102] Catch exception while moving cursor in ConsoleIO - ConsoleIO bug report by ibspa. - Also: NullReferenceException when closing connection --- MinecraftClient/ConsoleIO.cs | 33 ++++++++++++------- .../Protocol/Handlers/Protocol18.cs | 1 + 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 07c6771954..6548838276 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -338,15 +338,21 @@ private static void RemoveOneChar() { 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) @@ -358,12 +364,17 @@ private static void RemoveOneChar() } 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() { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 8fac698e2c..f5fe4071b8 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -85,6 +85,7 @@ private bool Update() } } catch (SocketException) { return false; } + catch (NullReferenceException) { return false; } return true; } From 29975da627f7c33ab2707141c20a53f67f644c31 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 22 Oct 2015 20:56:08 +0200 Subject: [PATCH 075/102] Merge onlyforlogin and enabled in proxy settings The 'enabled' setting can now be set to 'login' for enabling proxy only for logging in to the Minecraft account, and then connect to the server directly without proxy. Useful when Minecraft login is blocked on some network, but not Minecraft servers (port 25565) (original idea and enhancement by ZizzyDizzyMC) --- MinecraftClient/McTcpClient.cs | 9 +-------- MinecraftClient/Protocol/ProtocolHandler.cs | 2 +- MinecraftClient/Proxy/ProxyHandler.cs | 7 +++++-- MinecraftClient/Settings.cs | 14 ++++++++------ 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index cdcf3f6d78..fed884a1a6 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -111,14 +111,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv try { - if (Settings.OnlyForLogin) - { - client = new TcpClient(host, port); - } - else - { - client = ProxyHandler.newTcpClient(host, port); - } + client = ProxyHandler.newTcpClient(host, port); client.ReceiveBufferSize = 1024 * 1024; handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this); Console.WriteLine("Version is supported.\nLogging in..."); diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 624d6a7ff4..dcd507d786 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -244,7 +244,7 @@ private static int doHTTPSPost(string host, string endpoint, string request, ref int statusCode = 520; AutoTimeout.Perform(() => { - TcpClient client = ProxyHandler.newTcpClient(host, 443); + TcpClient client = ProxyHandler.newTcpClient(host, 443, true); SslStream stream = new SslStream(client.GetStream()); stream.AuthenticateAsClient(host); diff --git a/MinecraftClient/Proxy/ProxyHandler.cs b/MinecraftClient/Proxy/ProxyHandler.cs index d18d4ea1c6..a017e3238c 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -24,12 +24,15 @@ public enum Type { HTTP, SOCKS4, SOCKS4a, SOCKS5 }; /// /// 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; diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index d429052046..407d730f4b 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -29,13 +29,13 @@ public static class Settings 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; public static string ProxyUsername = ""; public static string ProxyPassword = ""; - public static bool OnlyForLogin = false; //Other Settings public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\9e\9e2fdc43fc1c7024ff5922b998fadb2971a64ee0"; //MC 1.7.4 en_GB.lang @@ -322,8 +322,11 @@ public static void LoadSettings(string settingsfile) case ParseMode.Proxy: switch (argName.ToLower()) { - case "enabled": ProxyEnabled = str2bool(argValue); break; - case "onlyforlogin": OnlyForLogin = 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; } @@ -415,12 +418,11 @@ public static void WriteDefaultSettings(string settingsfile) + "#%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" - + "onlyforlogin=false # Change this to \"true\" if you only want to use a proxy for login." + "\r\n" + "#Bot Settings\r\n" + "\r\n" From 5038c3d475869310ae4f01d8229b4ae66d6a622e Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 22 Oct 2015 22:17:15 +0200 Subject: [PATCH 076/102] Add regex settings for parsing chat messages Allows user-defined regexes to be used instead of built-in chat detection routines for matching messages on server using a non-standard chat format. Built-in detection routines can be disabled using a single setting, based on a contribution by ZizzyDizzyMC. --- MinecraftClient/ChatBot.cs | 243 +++++++++++++++++++----------- MinecraftClient/Settings.cs | 65 +++++--- MinecraftClient/config/README.txt | 9 ++ 3 files changed, 210 insertions(+), 107 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 5d568289e6..6540b89240 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -4,6 +4,7 @@ using System.Text; using System.IO; using System.Threading; +using System.Text.RegularExpressions; namespace MinecraftClient { @@ -142,11 +143,11 @@ protected static string GetVerbatim(string text) protected static bool IsValidName(string username) { - if ( String.IsNullOrEmpty(username) ) + if (String.IsNullOrEmpty(username)) return false; - foreach ( char c in username ) - if ( !((c >= 'a' && c <= 'z') + foreach (char c in username) + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') ) @@ -165,85 +166,105 @@ protected static bool IsValidName(string username) protected static bool IsPrivateMessage(string text, ref string message, ref string sender) { + if (String.IsNullOrEmpty(text)) + return false; + text = GetVerbatim(text); - if (text == "") { return false; } - string[] tmp = text.Split(' '); - try + //Built-in detection routine for private messages + if (Settings.ChatFormat_Builtins) { - //Detect vanilla /tell messages - //Someone whispers message (MC 1.5) - //Someone whispers to you: message (MC 1.7) - if (tmp.Length > 2 && tmp[1] == "whispers") + string[] tmp = text.Split(' '); + try { - if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:") + //Detect vanilla /tell messages + //Someone whispers message (MC 1.5) + //Someone whispers to you: message (MC 1.7) + if (tmp.Length > 2 && tmp[1] == "whispers") { - message = text.Substring(tmp[0].Length + 18); //MC 1.7 + if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:") + { + message = text.Substring(tmp[0].Length + 18); //MC 1.7 + } + else message = text.Substring(tmp[0].Length + 10); //MC 1.5 + sender = tmp[0]; + return IsValidName(sender); } - else message = text.Substring(tmp[0].Length + 10); //MC 1.5 - sender = tmp[0]; - return IsValidName(sender); - } - //Detect Essentials (Bukkit) /m messages - //[Someone -> me] message - //[~Someone -> me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->" - && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers - { - message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Essentials (Bukkit) /m messages + //[Someone -> me] message + //[~Someone -> me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->" + && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers + { + message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect Modified server messages. /m - //[Someone @ me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" - && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers - { - message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Modified server messages. /m + //[Someone @ me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" + && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers + { + message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect Essentials (Bukkit) /me messages with some custom prefix - //[Prefix] [Someone -> me] message - //[Prefix] [~Someone -> me] message - else if (text[0] == '[' && tmp[0][tmp[0].Length - 1] == ']' - && tmp[1][0] == '[' && tmp.Length > 4 && tmp[2] == "->" - && (tmp[3] == "me]" || tmp[3] == "moi]")) - { - message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1); - sender = tmp[1].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Essentials (Bukkit) /me messages with some custom prefix + //[Prefix] [Someone -> me] message + //[Prefix] [~Someone -> me] message + else if (text[0] == '[' && tmp[0][tmp[0].Length - 1] == ']' + && tmp[1][0] == '[' && tmp.Length > 4 && tmp[2] == "->" + && (tmp[3] == "me]" || tmp[3] == "moi]")) + { + message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1); + sender = tmp[1].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect Essentials (Bukkit) /me messages with some custom rank - //[Someone [rank] -> me] message - //[~Someone [rank] -> me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->" - && (tmp[3] == "me]" || tmp[3] == "moi]")) - { - message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); + //Detect Essentials (Bukkit) /me messages with some custom rank + //[Someone [rank] -> me] message + //[~Someone [rank] -> me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->" + && (tmp[3] == "me]" || tmp[3] == "moi]")) + { + message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } + + //Detect HeroChat PMsend + //From Someone: message + else if (text.StartsWith("From ")) + { + sender = text.Substring(5).Split(':')[0]; + message = text.Substring(text.IndexOf(':') + 2); + return IsValidName(sender); + } + else return false; } + catch (IndexOutOfRangeException) { /* Not an expected chat format */ } + } - //Detect HeroChat PMsend - //From Someone: message - else if (text.StartsWith("From ")) + //User-defined regex for private chat messages + if (Settings.ChatFormat_Private != null) + { + Match regexMatch = Settings.ChatFormat_Private.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 3) { - sender = text.Substring(5).Split(':')[0]; - message = text.Substring(text.IndexOf(':') + 2); + sender = regexMatch.Groups[1].Value; + message = regexMatch.Groups[2].Value; return IsValidName(sender); } - else return false; } - catch (IndexOutOfRangeException) { return false; } + + return false; } /// @@ -256,17 +277,22 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri protected static bool IsChatMessage(string text, ref string message, ref string sender) { - + if (String.IsNullOrEmpty(text)) + return false; + text = GetVerbatim(text); - string[] tmp = text.Split(' '); - if (text.Length > 0) + + //Built-in detection routine for public messages + if (Settings.ChatFormat_Builtins) { + string[] tmp = text.Split(' '); + //Detect vanilla/factions Messages // message //<*Faction Someone> message //<*Faction Someone>: message //<*Faction ~Nicknamed>: message - if (text[0] == '<' && Settings.Vanilla_And_Factions_Messages_Enabled.Equals(true)) + if (text[0] == '<') { try { @@ -287,7 +313,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string //Detect HeroChat Messages //Public chat messages //[Channel] [Rank] User: Message - else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) + else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2) { int name_end = text.IndexOf(':'); int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; @@ -306,8 +332,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string && text.IndexOf('*') < text.IndexOf('<') && text.IndexOf('<') < text.IndexOf('>') && text.IndexOf('>') < text.IndexOf(' ') - && text.IndexOf(' ') < text.IndexOf(':') - && Settings.Unknown_Chat_Plugin_Messages_One_Enabled.Equals(true)) + && text.IndexOf(' ') < text.IndexOf(':')) { string prefix = tmp[0]; string user = tmp[1]; @@ -320,6 +345,19 @@ protected static bool IsChatMessage(string text, ref string message, ref string } } } + + //User-defined regex for public chat messages + if (Settings.ChatFormat_Public != null) + { + Match regexMatch = Settings.ChatFormat_Public.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 3) + { + sender = regexMatch.Groups[1].Value; + message = regexMatch.Groups[2].Value; + return IsValidName(sender); + } + } + return false; } @@ -332,25 +370,52 @@ protected static bool IsChatMessage(string text, ref string message, ref string protected static bool IsTeleportRequest(string text, ref string sender) { + if (String.IsNullOrEmpty(text)) + return false; + text = GetVerbatim(text); - string[] tmp = text.Split(' '); - if (text.EndsWith("has requested to teleport to you.") - || text.EndsWith("has requested that you teleport to them.")) + + //Built-in detection routine for teleport requests + if (Settings.ChatFormat_Builtins) { - // Username has requested... - //[Rank] Username has requested... - if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">")) - || (tmp[0].StartsWith("[") && tmp[0].EndsWith("]"))) - && tmp.Length > 1) - sender = tmp[1]; - - //Username has requested... - else sender = tmp[0]; - - //Final check on username validity - return IsValidName(sender); + string[] tmp = text.Split(' '); + + //Detect Essentials teleport requests, prossibly with + //nicknamed names or other modifications such as HeroChat + if (text.EndsWith("has requested to teleport to you.") + || text.EndsWith("has requested that you teleport to them.")) + { + // Username has requested... + //[Rank] Username has requested... + if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">")) + || (tmp[0].StartsWith("[") && tmp[0].EndsWith("]"))) + && tmp.Length > 1) + sender = tmp[1]; + + //Username has requested... + else sender = tmp[0]; + + //~Username has requested... + if (sender.Length > 1 && sender[0] == '~') + sender = sender.Substring(1); + + //Final check on username validity + return IsValidName(sender); + } } - else return false; + + //User-defined regex for teleport requests + if (Settings.ChatFormat_TeleportRequest != null) + { + Match regexMatch = Settings.ChatFormat_TeleportRequest.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 2) + { + sender = regexMatch.Groups[1].Value; + return IsValidName(sender); + } + } + + return false; } /// diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 407d730f4b..c0119ff16a 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.IO; +using System.Text.RegularExpressions; namespace MinecraftClient { @@ -97,10 +98,11 @@ public static class Settings public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; - //Chat Message Enabled / Disabled. - public static bool Hero_Chat_Messages_Enabled = true; - public static bool Unknown_Chat_Plugin_Messages_One_Enabled = true; - public static bool Vanilla_And_Factions_Messages_Enabled = true; + //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; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -111,7 +113,7 @@ public static class Settings 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, ChatBotMessages, AutoRespond }; + private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, ChatFormat, AutoRespond }; /// /// Load settings from the give INI file @@ -146,7 +148,7 @@ public static void LoadSettings(string settingsfile) case "proxy": pMode = ParseMode.Proxy; break; case "appvars": pMode = ParseMode.AppVars; break; case "autorespond": pMode = ParseMode.AutoRespond; break; - case "chatbotmessages": pMode = ParseMode.ChatBotMessages; break; + case "chatformat": pMode = ParseMode.ChatFormat; break; default: pMode = ParseMode.Default; break; } } @@ -309,13 +311,13 @@ public static void LoadSettings(string settingsfile) } break; - case ParseMode.ChatBotMessages: + case ParseMode.ChatFormat: switch (argName.ToLower()) { - case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; - case "unknownchatpluginmessagesone": Unknown_Chat_Plugin_Messages_One_Enabled = str2bool(argValue); break; - case "vanillaandfactionsmessages": Vanilla_And_Factions_Messages_Enabled = str2bool(argValue); break; - + 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; @@ -424,6 +426,12 @@ public static void WriteDefaultSettings(string settingsfile) + "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" @@ -464,18 +472,39 @@ public static void WriteDefaultSettings(string settingsfile) + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + "\r\n" - + "[ChatBotMessages]\r\n" - + "vanillaandfactionsmessages=true # Chat Formats \" Message\" \"<*Faction User>: Message\" \r\n" - + "herochatmessagesenabled=true # Chat Format is \"[Channel][Rank] User: Message\"\r\n" - + "unknownchatpluginmessagesone=true # Chat Format is \"**Faction User : Message\"\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 diff --git a/MinecraftClient/config/README.txt b/MinecraftClient/config/README.txt index fa71e99441..04e460ae0b 100644 --- a/MinecraftClient/config/README.txt +++ b/MinecraftClient/config/README.txt @@ -119,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 ====================== From f67a3e33842e5be4292bbfc59745cef0a9c92648 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Wed, 21 Oct 2015 22:40:50 -0700 Subject: [PATCH 077/102] Attempted to add basic forge support. This does not work, but it's a start. --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index f5fe4071b8..bb826930c0 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -525,7 +525,7 @@ private void SendRAW(byte[] buffer) 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() + "\0FML\0"); 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); @@ -760,6 +760,7 @@ public static bool doPing(string host, int port, ref int protocolversion) if (readNextVarInt(ref packetData) == 0x00) //Read Packet ID { string result = readNextString(ref packetData); //Get the Json data + if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) { Json.JSONData jsonData = Json.ParseJson(result); From 7cc87d8e71064a745071d58cc7285fdf64cbb09f Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 23 Oct 2015 16:54:36 -0700 Subject: [PATCH 078/102] Detect and store the list of forge mods. --- MinecraftClient/McTcpClient.cs | 13 ++-- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Program.cs | 8 ++- .../Protocol/Handlers/Forge/ForgeInfo.cs | 70 +++++++++++++++++++ .../Protocol/Handlers/Protocol18.cs | 35 ++++++++-- MinecraftClient/Protocol/ProtocolHandler.cs | 13 ++-- 6 files changed, 119 insertions(+), 21 deletions(-) create mode 100755 MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index fed884a1a6..e1e5c6bd94 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -8,6 +8,7 @@ using System.Net; using MinecraftClient.Protocol; using MinecraftClient.Proxy; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient { @@ -54,9 +55,9 @@ public class McTcpClient : IMinecraftComHandler /// 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 +71,9 @@ public McTcpClient(string username, string uuid, string sessionID, int protocolv /// 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,7 +88,7 @@ public McTcpClient(string username, string uuid, string sessionID, string server /// 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; @@ -113,7 +114,7 @@ private void StartClient(string user, string uuid, string sessionID, string serv { 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..."); try diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 9e1fc0c71a..2e2ad7271c 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -114,6 +114,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index a9e4575d7f..302e50bb3a 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -5,6 +5,7 @@ using MinecraftClient.Protocol; using System.Reflection; using System.Threading; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient { @@ -145,6 +146,7 @@ private static void InitializeClient() //Get server version int protocolversion = 0; + ForgeInfo forgeInfo = null; if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto") { @@ -166,7 +168,7 @@ private static void InitializeClient() if (protocolversion == 0) { Console.WriteLine("Retrieving Server Info..."); - if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion)) + if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo)) { HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); return; @@ -180,9 +182,9 @@ private static void InitializeClient() //Start the main TCP client if (Settings.SingleCommand != "") { - Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, Settings.SingleCommand); + Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); } - else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, Settings.ServerIP, Settings.ServerPort); + else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); //Update console title if (Settings.ConsoleTitle != "") diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs new file mode 100755 index 0000000000..dd3a83de47 --- /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/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index bb826930c0..c11479a325 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -7,6 +7,7 @@ using MinecraftClient.Crypto; using MinecraftClient.Proxy; using System.Security.Cryptography; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient.Protocol.Handlers { @@ -25,18 +26,22 @@ class Protocol18Handler : IMinecraftCom private bool encrypted = false; private int protocolversion; + // Server forge info -- may be null. + private ForgeInfo forgeInfo; + 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; } private Protocol18Handler(TcpClient Client) @@ -730,7 +735,7 @@ public string AutoComplete(string BehindCursor) /// /// True if ping was successful - public static bool doPing(string host, int port, ref int protocolversion) + public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo) { string version = ""; TcpClient tcp = ProxyHandler.newTcpClient(host, port); @@ -766,21 +771,37 @@ public static bool doPing(string host, int port, ref int protocolversion) Json.JSONData jsonData = Json.ParseJson(result); if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) { - jsonData = jsonData.Properties["version"]; + Json.JSONData versionData = jsonData.Properties["version"]; //Retrieve display name of the Minecraft version - if (jsonData.Properties.ContainsKey("name")) - version = jsonData.Properties["name"].StringValue; + if (versionData.Properties.ContainsKey("name")) + version = versionData.Properties["name"].StringValue; //Retrieve protocol version number for handling this server - if (jsonData.Properties.ContainsKey("protocol")) - protocolversion = atoi(jsonData.Properties["protocol"].StringValue); + 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"); ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ")."); + + // 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); + + ConsoleIO.WriteLineFormatted("§8Server is running forge. Mod list:"); + foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods) + { + ConsoleIO.WriteLineFormatted("§8 " + mod.ToString()); + } + } + } return true; } } diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index dcd507d786..067ab4bf6d 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -6,6 +6,7 @@ using MinecraftClient.Proxy; using System.Net.Sockets; using System.Net.Security; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient.Protocol { @@ -23,16 +24,17 @@ public static class ProtocolHandler /// Will contain protocol version, if ping successful /// TRUE if ping was successful - public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion) + 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)) + || Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp, ref forgeInfoTmp)) { success = true; } @@ -40,11 +42,12 @@ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int pro } catch (Exception e) { - ConsoleIO.WriteLineFormatted("§8" + e.Message); + ConsoleIO.WriteLineFormatted("§8" + e.ToString()); } }, TimeSpan.FromSeconds(30))) { protocolversion = protocolversionTmp; + forgeInfo = forgeInfoTmp; return success; } else @@ -62,14 +65,14 @@ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int pro /// Handler with the appropriate callbacks /// - public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler) + 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 }; if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1) - return new Protocol18Handler(Client, ProtocolVersion, Handler); + return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo); throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported."); } From b154639a6b8f1d92207b9198298cfd11cc7e8a66 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 24 Oct 2015 13:31:43 -0700 Subject: [PATCH 079/102] Handle forge handshake up to mod list sending. --- MinecraftClient/MinecraftClient.csproj | 2 + .../Handlers/Forge/FMLHandshakeClientState.cs | 23 ++ .../Forge/FMLHandshakeDiscriminator.cs | 21 ++ .../Protocol/Handlers/Protocol18.cs | 271 ++++++++++++------ 4 files changed, 234 insertions(+), 83 deletions(-) create mode 100755 MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs create mode 100755 MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 2e2ad7271c..9c09a5e138 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -114,6 +114,8 @@ + + diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs new file mode 100755 index 0000000000..a72b4ad57f --- /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 0000000000..2402eff08a --- /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/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index c11479a325..3706e44c0f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -18,7 +18,7 @@ namespace MinecraftClient.Protocol.Handlers class Protocol18Handler : IMinecraftCom { private const int MC18Version = 47; - + private int compression_treshold = 0; private bool autocomplete_received = false; private string autocomplete_result = ""; @@ -28,6 +28,7 @@ class Protocol18Handler : IMinecraftCom // Server forge info -- may be null. private ForgeInfo forgeInfo; + private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START; IMinecraftComHandler handler; Thread netRead; @@ -138,94 +139,153 @@ private bool handlePacket(int packetID, byte[] packetData) return false; //Ignored packet } } - else //Regular in-game packets + // Regular in-game packets + + if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE) //Check forge login { - switch (packetID) + switch (fmlHandshakeState) { - case 0x00: //Keep-Alive - SendPacket(0x00, packetData); - break; - case 0x01: //Join game - handler.OnGameJoined(); - break; - case 0x02: //Chat message - string message = readNextString(ref packetData); - try + case FMLHandshakeClientState.START: + if (packetID != 0x3F) + break; + + String channel = readNextString(ref packetData); + + if (channel != "FML|HS") + break; + + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); + 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(ref packetData); + // There's another value afterwards for the dimension, but we don't need it. + + 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. + ConsoleIO.WriteLineFormatted("§8Sending falsified mod list to server..."); + byte[][] mods = new byte[forgeInfo.Mods.Count][]; + for (int i = 0; i < forgeInfo.Mods.Count; i++) { - //Hide system messages or xp bar messages? - byte messageType = readData(1, ref packetData)[0]; - if ((messageType == 1 && !Settings.DisplaySystemMessages) - || (messageType == 2 && !Settings.DisplayXPBarMessages)) - break; + ForgeInfo.ForgeMod mod = forgeInfo.Mods[i]; + mods[i] = concatBytes(getString(mod.ModID), getString(mod.Version)); } - catch (IndexOutOfRangeException) { /* No message type */ } - handler.OnTextReceived(ChatParser.ParseText(message)); - break; - case 0x38: //Player List update - if (protocolversion >= MC18Version) + SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList, concatBytes(getVarInt(forgeInfo.Mods.Count), concatBytes(mods))); + + fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; + + return true; + } + } + + switch (packetID) + { + case 0x00: //Keep-Alive + SendPacket(0x00, packetData); + break; + case 0x01: //Join game + handler.OnGameJoined(); + break; + case 0x02: //Chat message + string message = readNextString(ref packetData); + try + { + //Hide system messages or xp bar messages? + byte messageType = readData(1, ref packetData)[0]; + if ((messageType == 1 && !Settings.DisplaySystemMessages) + || (messageType == 2 && !Settings.DisplayXPBarMessages)) + break; + } + catch (IndexOutOfRangeException) { /* No message type */ } + handler.OnTextReceived(ChatParser.ParseText(message)); + break; + case 0x38: //Player List update + if (protocolversion >= MC18Version) + { + int action = readNextVarInt(ref packetData); + int numActions = readNextVarInt(ref packetData); + for (int i = 0; i < numActions; i++) { - int action = readNextVarInt(ref packetData); - int numActions = readNextVarInt(ref packetData); - for (int i = 0; i < numActions; i++) + Guid uuid = readNextUUID(ref packetData); + switch (action) { - Guid uuid = readNextUUID(ref packetData); - switch (action) - { - 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; - } + 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; } } - else //MC 1.7.X does not provide UUID in tab-list updates - { - string name = readNextString(ref packetData); - bool online = readNextBool(ref packetData); - short ping = readNextShort(ref 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 0x3A: //Tab-Complete Result - int autocomplete_count = readNextVarInt(ref packetData); - string tab_list = ""; - for (int i = 0; i < autocomplete_count; i++) + } + else //MC 1.7.X does not provide UUID in tab-list updates + { + string name = readNextString(ref packetData); + bool online = readNextBool(ref packetData); + short ping = readNextShort(ref 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 0x3A: //Tab-Complete Result + int autocomplete_count = readNextVarInt(ref packetData); + string tab_list = ""; + for (int i = 0; i < autocomplete_count; i++) + { + autocomplete_result = readNextString(ref 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 0x3F: //Plugin message. + String channel = readNextString(ref packetData); + if (channel == "FML|HS") + { + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); + if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) { - autocomplete_result = readNextString(ref 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 0x40: //Kick Packet - handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); - return false; - case 0x46: //Network Compression Treshold Info - if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); - break; - case 0x48: //Resource Pack Send - string url = readNextString(ref packetData); - string hash = readNextString(ref packetData); - //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory - SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); - SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); - break; - default: - return false; //Ignored packet - } + return true; + } + break; + case 0x40: //Kick Packet + handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); + return false; + case 0x46: //Network Compression Treshold Info + if (protocolversion >= MC18Version) + compression_treshold = readNextVarInt(ref packetData); + break; + case 0x48: //Resource Pack Send + string url = readNextString(ref packetData); + string hash = readNextString(ref packetData); + //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory + SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); + SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); + break; + default: + return false; //Ignored packet } return true; //Packet processed } @@ -263,7 +323,7 @@ public void Dispose() /// /// Amount of bytes to read /// The data read from the network as an array - + private byte[] readDataRAW(int offset) { if (offset > 0) @@ -278,7 +338,7 @@ private byte[] readDataRAW(int offset) } return new byte[] { }; } - + /// /// Read some data from a cache of bytes and remove it from the cache /// @@ -377,7 +437,7 @@ private int readNextVarIntRAW() } return i; } - + /// /// Read an integer from a cache of bytes and remove it from the cache /// @@ -401,6 +461,16 @@ private static int readNextVarInt(ref byte[] cache) return i; } + /// + /// Read a single byte from a cache of bytes and remove it from the cache + /// + /// The byte that was read + + private static byte readNextByte(ref byte[] cache) + { + return readData(1, ref cache)[0]; + } + /// /// Build an integer for sending over the network /// @@ -436,6 +506,19 @@ private byte[] getArray(byte[] 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 /// @@ -478,6 +561,28 @@ private void Receive(byte[] buffer, int start, int offset, SocketFlags f) } } + /// + /// 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 plugin channel packet (0x3F) to the server, compression and encryption will be handled automatically + /// + /// Channel to send packet on + /// packet Data + + private void SendPluginChannelPacket(string channel, byte[] data) + { + SendPacket(0x17, concatBytes(getString(channel), data)); + } + /// /// Send a packet to the server, compression and encryption will be handled automatically /// @@ -505,7 +610,7 @@ private void SendPacket(int packetID, byte[] packetData) } } - SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet)); + SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet)); } /// From c1c1c10d26e19f85e1bbad40aa29844491c5ad5c Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 24 Oct 2015 13:38:44 -0700 Subject: [PATCH 080/102] Relocate forge handshake code into the main packet handler. Also, handle handshake reset. --- .../Protocol/Handlers/Protocol18.cs | 99 +++++++++---------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 3706e44c0f..3adc663109 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -141,54 +141,6 @@ private bool handlePacket(int packetID, byte[] packetData) } // Regular in-game packets - if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE) //Check forge login - { - switch (fmlHandshakeState) - { - case FMLHandshakeClientState.START: - if (packetID != 0x3F) - break; - - String channel = readNextString(ref packetData); - - if (channel != "FML|HS") - break; - - FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); - 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(ref packetData); - // There's another value afterwards for the dimension, but we don't need it. - - 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. - 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; - } - } - switch (packetID) { case 0x00: //Keep-Alive @@ -260,16 +212,57 @@ private bool handlePacket(int packetID, byte[] packetData) break; case 0x3F: //Plugin message. String channel = readNextString(ref packetData); - if (channel == "FML|HS") + if (forgeInfo != null) { - FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); - if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) + if (channel == "FML|HS") { + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref 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(ref packetData); + // There's another value afterwards for the dimension, but we don't need it. + + 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. + 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; + } } - return true; } - break; + return false; case 0x40: //Kick Packet handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); return false; From ad38154f8fe6d7a66c831c6a2753b56574577a9d Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 24 Oct 2015 15:01:46 -0700 Subject: [PATCH 081/102] Full forge (1.8) connection support! --- .../Protocol/Handlers/Protocol18.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 3adc663109..9663c9ebf7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -258,6 +258,61 @@ private bool handlePacket(int packetID, byte[] packetData) fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; + return true; + case FMLHandshakeClientState.WAITINGSERVERDATA: + if (discriminator != FMLHandshakeDiscriminator.ModList) + return false; + + 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; + + bool hasNextRegistry = readNextBool(ref packetData); + string registryName = readNextString(ref packetData); + int registrySize = readNextVarInt(ref packetData); + + 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; + + 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 }); + ConsoleIO.WriteLine("Forge server connection complete!"); + + fmlHandshakeState = FMLHandshakeClientState.DONE; return true; } } From 7c8e8563924510d152e7f368e57274c953021756 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 24 Oct 2015 21:17:28 -0700 Subject: [PATCH 082/102] Fix connection to forge 1.7.10 servers. This includes making sure plugin channels have their packet. Also, it fixes a mistake in #92, where brand info doesn't send length in 1.7.10 (same channel issue). Finally, there's only 1 registry sent to the client in 1.7.10. --- .../Protocol/Handlers/Protocol18.cs | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 9663c9ebf7..70ef476679 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -212,6 +212,12 @@ private bool handlePacket(int packetID, byte[] packetData) break; case 0x3F: //Plugin message. String channel = readNextString(ref packetData); + if (protocolversion < MC18Version) + { + // 1.7 and lower prefix plugin channel packets with the length. + // We can skip it, though. + readNextShort(ref packetData); + } if (forgeInfo != null) { if (channel == "FML|HS") @@ -277,17 +283,33 @@ private bool handlePacket(int packetID, byte[] packetData) if (discriminator != FMLHandshakeDiscriminator.RegistryData) return false; - bool hasNextRegistry = readNextBool(ref packetData); - string registryName = readNextString(ref packetData); - int registrySize = readNextVarInt(ref packetData); + if (protocolversion < MC18Version) + { + // 1.7.10 and below have one registry + // with blocks and items. + int registrySize = readNextVarInt(ref packetData); - ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + - " with " + registrySize + " entries"); + ConsoleIO.WriteLineFormatted("§8Received registry " + + "with " + registrySize + " entries"); - if (!hasNextRegistry) - { fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE; } + else + { + // 1.8+ has more than one registry. + + bool hasNextRegistry = readNextBool(ref packetData); + string registryName = readNextString(ref packetData); + int registrySize = readNextVarInt(ref packetData); + + ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + + " with " + registrySize + " entries"); + + if (!hasNextRegistry) + { + fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE; + } + } return false; case FMLHandshakeClientState.PENDINGCOMPLETE: @@ -628,7 +650,19 @@ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, b private void SendPluginChannelPacket(string channel, byte[] data) { - SendPacket(0x17, concatBytes(getString(channel), data)); + // 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(0x17, concatBytes(getString(channel), data)); + } } /// @@ -829,11 +863,7 @@ public bool SendBrandInfo(string brandInfo) return false; try { - byte[] channel = Encoding.UTF8.GetBytes("MC|Brand"); - byte[] channelLen = getVarInt(channel.Length); - byte[] brand = Encoding.UTF8.GetBytes(brandInfo); - byte[] brandLen = getVarInt(brand.Length); - SendPacket(0x17, concatBytes(channelLen, channel, brandLen, brand)); + SendPluginChannelPacket("MC|Brand", getString(brandInfo)); return true; } catch (SocketException) { return false; } From 77277bcf8418d859ff6de330a9c7103d0a6b3a43 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 24 Oct 2015 22:26:45 -0700 Subject: [PATCH 083/102] Add SendPluginChannelPacket to the IMinecraftCom interface. --- .../Protocol/Handlers/Protocol16.cs | 25 ++++++++++ .../Protocol/Handlers/Protocol18.cs | 50 ++++++++++--------- MinecraftClient/Protocol/IMinecraftCom.cs | 11 ++++ 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 1813b9afd7..89cedbe67b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -615,6 +615,31 @@ public bool SendBrandInfo(string brandInfo) return false; //Only supported since MC 1.7 } + /// + /// 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)) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 70ef476679..8fc5eaae29 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -642,29 +642,6 @@ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, b SendPluginChannelPacket("FML|HS", concatBytes(new byte[] { (byte)discriminator }, data)); } - /// - /// Send a plugin channel packet (0x3F) to the server, compression and encryption will be handled automatically - /// - /// Channel to send packet on - /// packet Data - - private void SendPluginChannelPacket(string channel, byte[] data) - { - // 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(0x17, concatBytes(getString(channel), data)); - } - } - /// /// Send a packet to the server, compression and encryption will be handled automatically /// @@ -861,9 +838,34 @@ public bool SendBrandInfo(string brandInfo) { if (String.IsNullOrEmpty(brandInfo)) return false; + + return SendPluginChannelPacket("MC|Brand", getString(brandInfo)); + } + + /// + /// 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 { - SendPluginChannelPacket("MC|Brand", getString(brandInfo)); + // 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(0x17, concatBytes(getString(channel), data)); + } + return true; } catch (SocketException) { return false; } diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 9c351ebc30..218f49d4d5 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -51,5 +51,16 @@ public interface IMinecraftCom : IDisposable, IAutoComplete /// True if brand info was successfully sent bool SendBrandInfo(string brandInfo); + + /// + /// Send a plugin channel packet to the server. + /// + /// http://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/ + /// + /// Channel to send packet on + /// packet Data + /// True if message was successfully sent + + bool SendPluginChannelPacket(string channel, byte[] data); } } From b746b5612bba112c89cd643f25fab7749eed1101 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 24 Oct 2015 22:56:35 -0700 Subject: [PATCH 084/102] Only add `\0FML\0` to the IP if forgeinfo is not null (+ whitespace fixes) --- MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs | 2 +- MinecraftClient/Protocol/Handlers/Protocol18.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs index dd3a83de47..03180432b8 100755 --- a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs +++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs @@ -38,7 +38,7 @@ public override string ToString() /// The modinfo JSON tag. internal ForgeInfo(Json.JSONData data) { - // Example ModInfo (with spacing): + // Example ModInfo (with spacing): // "modinfo": { // "type": "FML", diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 8fc5eaae29..e8dd5adf23 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -223,7 +223,7 @@ private bool handlePacket(int packetID, byte[] packetData) if (channel == "FML|HS") { FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); - + if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) { fmlHandshakeState = FMLHandshakeClientState.START; @@ -259,7 +259,7 @@ private bool handlePacket(int packetID, byte[] packetData) ForgeInfo.ForgeMod mod = forgeInfo.Mods[i]; mods[i] = concatBytes(getString(mod.ModID), getString(mod.Version)); } - SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList, + SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList, concatBytes(getVarInt(forgeInfo.Mods.Count), concatBytes(mods))); fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; @@ -694,7 +694,7 @@ private void SendRAW(byte[] buffer) public bool Login() { byte[] protocol_version = getVarInt(protocolversion); - byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost() + "\0FML\0"); + 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[] next_state = getVarInt(2); From fb87de1ff5977de469afa1b7e6d60a7b9bccf418 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 25 Oct 2015 11:51:53 -0700 Subject: [PATCH 085/102] Fix compatability with Feed The Beast servers More percisely, use varshorts for the length of the 3F packet, as forge makes it longer. Only really matters if a bazillion mods are installed, which they are with FTB. --- .../Protocol/Handlers/Protocol18.cs | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index e8dd5adf23..8eeef8cc2f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -214,9 +214,17 @@ private bool handlePacket(int packetID, byte[] packetData) String channel = readNextString(ref packetData); if (protocolversion < MC18Version) { - // 1.7 and lower prefix plugin channel packets with the length. - // We can skip it, though. - readNextShort(ref packetData); + if (forgeInfo == null) + { + // 1.7 and lower prefix plugin channel packets with the length. + // We can skip it, though. + readNextShort(ref packetData); + } + else + { + // Forge does something even weirder with the length. + readNextVarShort(ref packetData); + } } if (forgeInfo != null) { @@ -269,9 +277,12 @@ private bool handlePacket(int packetID, byte[] packetData) if (discriminator != FMLHandshakeDiscriminator.ModList) return false; + Thread.Sleep(2000); + 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 }); @@ -461,6 +472,18 @@ private static short readNextShort(ref byte[] cache) return BitConverter.ToInt16(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(ref byte[] cache) + { + byte[] rawValue = readData(2, ref cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToUInt16(rawValue, 0); + } + /// /// Read a uuid from a cache of bytes and remove it from the cache /// @@ -531,6 +554,26 @@ private static int readNextVarInt(ref byte[] cache) 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(ref byte[] cache) + { + ushort low = readNextUShort(ref cache); + byte high = 0; + if ((low & 0x8000) != 0) + { + low &= 0x7FFF; + high = readNextByte(ref cache); + } + return ((high & 0xFF) << 15) | low; + } + /// /// Read a single byte from a cache of bytes and remove it from the cache /// From 3a19de82ae30eb11de0af0c871c78f871befe93b Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 25 Oct 2015 12:20:38 -0700 Subject: [PATCH 086/102] Finish forge hand shaking before enabling the chat prompt. --- .../Protocol/Handlers/Protocol18.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 8eeef8cc2f..a9631206c7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -772,6 +772,15 @@ public bool Login() { 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 } @@ -779,6 +788,33 @@ public bool Login() } } + /// + /// Completes the Minecraft Forge handshake. + /// + /// Whether the handshake was successful. + private bool CompleteForgeHandshake() + { + int packetID = -1; + byte[] packetData = new byte[0]; + + while (fmlHandshakeState != FMLHandshakeClientState.DONE) + { + readNextPacket(ref packetID, ref packetData); + + if (packetID == 0x40) // Disconect + { + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData))); + return false; + } + else + { + handlePacket(packetID, packetData); + } + } + + return true; + } + /// /// Start network encryption. Automatically called by Login() if the server requests encryption. /// @@ -826,6 +862,16 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string else if (packetID == 0x02) //Login successful { login_phase = false; + + if (forgeInfo != null) + { + // Do the forge handshake. + if (!CompleteForgeHandshake()) + { + return false; + } + } + StartUpdating(); return true; } From e5364566c3bc74803623ca87d2587ac7db51ea48 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 26 Oct 2015 23:19:06 +0100 Subject: [PATCH 087/102] Catch IndexOutOfRangeException for IsChatMessage --- MinecraftClient/ChatBot.cs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 6540b89240..38eb3a1824 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -307,7 +307,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string if (sender[0] == '~') { sender = sender.Substring(1); } return IsValidName(sender); } - catch (IndexOutOfRangeException) { return false; } + catch (IndexOutOfRangeException) { /* Not a vanilla/faction message */ } } //Detect HeroChat Messages @@ -315,11 +315,15 @@ protected static bool IsChatMessage(string text, ref string message, ref string //[Channel] [Rank] User: Message else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2) { - int name_end = text.IndexOf(':'); - int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; - sender = text.Substring(name_start, name_end - name_start); - message = text.Substring(name_end + 2); - return IsValidName(sender); + try + { + int name_end = text.IndexOf(':'); + int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; + sender = text.Substring(name_start, name_end - name_start); + message = text.Substring(name_end + 2); + return IsValidName(sender); + } + catch (IndexOutOfRangeException) { /* Not a herochat message */ } } //Detect (Unknown Plugin) Messages @@ -334,15 +338,19 @@ protected static bool IsChatMessage(string text, ref string message, ref string && text.IndexOf('>') < text.IndexOf(' ') && text.IndexOf(' ') < text.IndexOf(':')) { - string prefix = tmp[0]; - string user = tmp[1]; - string semicolon = tmp[2]; - if (prefix.All(c => char.IsLetterOrDigit(c) || new char[] { '*', '<', '>', '_' }.Contains(c)) - && semicolon == ":") + try { - message = text.Substring(prefix.Length + user.Length + 4); - return IsValidName(user); + string prefix = tmp[0]; + string user = tmp[1]; + string semicolon = tmp[2]; + if (prefix.All(c => char.IsLetterOrDigit(c) || new char[] { '*', '<', '>', '_' }.Contains(c)) + && semicolon == ":") + { + message = text.Substring(prefix.Length + user.Length + 4); + return IsValidName(user); + } } + catch (IndexOutOfRangeException) { /* Not a message */ } } } From 6dd003d04cd9cb4680771b1f31af971f4251a839 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 29 Oct 2015 18:28:46 +0100 Subject: [PATCH 088/102] Disable Forge when no mods are installed When no mods are installed, FML client/server will skip mod negociation phase and act as a vanilla client/server. MCC should do the same else login will not work properly. See #100 : Forge Support --- MinecraftClient/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 302e50bb3a..9e44753157 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -175,6 +175,11 @@ private static void InitializeClient() } } + if (forgeInfo != null && !forgeInfo.Mods.Any()) + { + forgeInfo = null; + } + if (protocolversion != 0) { try From 5654871a57a884e326a8ef13e711df2e8fb9107b Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 27 Nov 2015 16:52:41 +0100 Subject: [PATCH 089/102] First attempt at Realms list retrieval > See #51 - Realms Support + Catch exception while retrieving player head --- MinecraftClient/CSharpRunner.cs | 2 +- MinecraftClient/ConsoleIcon.cs | 10 ++- MinecraftClient/Protocol/ProtocolHandler.cs | 72 ++++++++++++++++----- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/MinecraftClient/CSharpRunner.cs b/MinecraftClient/CSharpRunner.cs index 46add1122a..b8ec098a3d 100644 --- a/MinecraftClient/CSharpRunner.cs +++ b/MinecraftClient/CSharpRunner.cs @@ -210,7 +210,7 @@ public CSharpAPI(ChatBot apiHandler, ManualResetEvent tickHandler) /// /// Text to send to the server /// True if the text was sent with no error - new public bool SendText(object text) + public bool SendText(object text) { bool result = base.SendText(text is string ? (string)text : text.ToString()); tickHandler.WaitOne(); diff --git a/MinecraftClient/ConsoleIcon.cs b/MinecraftClient/ConsoleIcon.cs index 2ef207832a..30dc6460d7 100644 --- a/MinecraftClient/ConsoleIcon.cs +++ b/MinecraftClient/ConsoleIcon.cs @@ -36,9 +36,13 @@ public static void setPlayerIconAsync(string playerName) { 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/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 067ab4bf6d..266cbdda6c 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -231,9 +231,40 @@ public static bool SessionCheck(string uuid, string accesstoken, string serverha 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); + } + /// - /// 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. + /// 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 @@ -242,6 +273,29 @@ public static bool SessionCheck(string uuid, string accesstoken, string serverha /// 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; @@ -250,21 +304,9 @@ private static int doHTTPSPost(string host, string endpoint, string request, ref TcpClient client = ProxyHandler.newTcpClient(host, 443, true); 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()))); + 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); From 72bd485e67ee001b122ab79dd24ef4be0a8e660a Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 27 Nov 2015 17:16:33 +0100 Subject: [PATCH 090/102] Add basic location handling - Retrieve player location from the server - Send back player location from the server - Requires that a specific setting is enabled - Should allow items to be picked up by the player - May also trigger some anti chead plugins --- MinecraftClient/ChatBot.cs | 2 +- MinecraftClient/ChatBots/AutoRelog.cs | 6 +- MinecraftClient/Mapping/Location.cs | 159 ++++++++++++++++++ MinecraftClient/McTcpClient.cs | 60 ++++++- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Program.cs | 8 +- .../Protocol/Handlers/Protocol16.cs | 6 + .../Protocol/Handlers/Protocol18.cs | 66 ++++++++ MinecraftClient/Protocol/IMinecraftCom.cs | 14 +- .../Protocol/IMinecraftComHandler.cs | 9 + MinecraftClient/Settings.cs | 3 + 11 files changed, 318 insertions(+), 16 deletions(-) create mode 100644 MinecraftClient/Mapping/Location.cs diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 38eb3a1824..a9349d7220 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -458,7 +458,7 @@ protected void LogToConsole(object text) protected void ReconnectToTheServer(int ExtraAttempts = 3) { - McTcpClient.AttemptsLeft = ExtraAttempts; + McTcpClient.ReconnectionAttemptsLeft = ExtraAttempts; Program.Restart(); } diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs index d98e30d5c3..2c53f58525 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -25,14 +25,14 @@ public AutoRelog(int DelayBeforeRelog, int retries) { attempts = retries; if (attempts == -1) { attempts = int.MaxValue; } - McTcpClient.AttemptsLeft = attempts; + McTcpClient.ReconnectionAttemptsLeft = attempts; delay = DelayBeforeRelog; if (delay < 1) { delay = 1; } } public override void Initialize() { - McTcpClient.AttemptsLeft = attempts; + McTcpClient.ReconnectionAttemptsLeft = attempts; if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile)) { dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile); @@ -55,7 +55,7 @@ public override bool OnDisconnect(DisconnectReason reason, string message) { LogToConsole("Waiting " + delay + " seconds before reconnecting..."); System.Threading.Thread.Sleep(delay * 1000); - McTcpClient.AttemptsLeft = attempts; + McTcpClient.ReconnectionAttemptsLeft = attempts; ReconnectToTheServer(); return true; } diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs new file mode 100644 index 0000000000..22145ad516 --- /dev/null +++ b/MinecraftClient/Mapping/Location.cs @@ -0,0 +1,159 @@ +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; + } + + /// + /// 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 GetLongRepresentation() + { + 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 FromLongRepresentation(ulong location) + { + return new Location(location >> 38, (location >> 26) & 0xFFF, location << 38 >> 38); + } + + /// + /// 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 + ); + } + + /// + /// 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; + } + } +} diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index e1e5c6bd94..66e8bc8abd 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -9,6 +9,7 @@ using MinecraftClient.Protocol; using MinecraftClient.Proxy; using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Mapping; namespace MinecraftClient { @@ -18,16 +19,20 @@ namespace MinecraftClient public class McTcpClient : IMinecraftComHandler { - private static List cmd_names = new List(); - private static Dictionary cmds = new Dictionary(); - private List bots = new List(); + 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 static List scripts_on_hold = new List(); + + 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(); Settings.SingleCommand = ""; } public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); } public void BotClear() { bots.Clear(); } - public static int AttemptsLeft = 0; + private Location location; + private int updateTicks = 0; private string host; private int port; @@ -40,6 +45,7 @@ public class McTcpClient : IMinecraftComHandler public string GetUsername() { return username; } public string GetUserUUID() { return uuid; } public string GetSessionID() { return sessionid; } + public Location GetCurrentLocation() { return location; } TcpClient client; IMinecraftCom handler; @@ -162,10 +168,10 @@ private void StartClient(string user, string uuid, string sessionID, string serv if (retry) { - if (AttemptsLeft > 0) + if (ReconnectionAttemptsLeft > 0) { - ConsoleIO.WriteLogLine("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) { @@ -330,6 +336,34 @@ public void OnGameJoined() handler.SendBrandInfo(Settings.BrandInfo.Trim()); } + /// + /// 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) + { + if (relative) + { + this.location += location; + } + else this.location = location; + } + + /// + /// 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); + } + /// /// Received some text from the server /// @@ -409,6 +443,16 @@ public void OnUpdate() else throw; //ThreadAbortException should not be caught } } + + if (Settings.TerrainAndMovements) + { + if (updateTicks >= 10) + { + handler.SendLocationUpdate(location, true); //TODO handle onGround once terrain data is available + updateTicks = 0; + } + updateTicks++; + } } /// diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 9c09a5e138..b9a92e7ed5 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -152,6 +152,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 9e44753157..1291618656 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -19,7 +19,11 @@ static class Program { private static McTcpClient Client; public static string[] startupargs; + public const string Version = "1.8.2"; + public const string MCLowestVersion = "1.4.6"; + public const string MCHighestVersion = "1.8.8"; + private static Thread offlinePrompt = null; private static bool useMcVersionOnce = false; @@ -29,7 +33,7 @@ static class Program static void Main(string[] args) { - Console.WriteLine("Console Client for MC 1.4.6 to 1.8.8 - v" + Version + " - By ORelio & Contributors"); + 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") @@ -137,6 +141,8 @@ private static void InitializeClient() ConsoleIcon.setPlayerIconAsync(Settings.Username); Console.WriteLine("Success. (session ID: " + sessionID + ')'); + + //ProtocolHandler.RealmsListWorlds(Settings.Username, UUID, sessionID); //TODO REMOVE if (Settings.ServerIP == "") { diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 89cedbe67b..07ee63dddb 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -7,6 +7,7 @@ using MinecraftClient.Crypto; using MinecraftClient.Proxy; using System.Security.Cryptography; +using MinecraftClient.Mapping; namespace MinecraftClient.Protocol.Handlers { @@ -615,6 +616,11 @@ 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. /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index a9631206c7..ddb8771bbe 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -8,6 +8,7 @@ using MinecraftClient.Proxy; using System.Security.Cryptography; using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Mapping; namespace MinecraftClient.Protocol.Handlers { @@ -162,6 +163,23 @@ private bool handlePacket(int packetID, byte[] packetData) catch (IndexOutOfRangeException) { /* No message type */ } handler.OnTextReceived(ChatParser.ParseText(message)); break; + case 0x08: + if (Settings.TerrainAndMovements) + { + double x = readNextDouble(ref packetData); + double y = readNextDouble(ref packetData); + double z = readNextDouble(ref packetData); + + byte locMask = readNextByte(ref 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); + } + break; case 0x38: //Player List update if (protocolversion >= MC18Version) { @@ -509,6 +527,18 @@ private byte[] readNextByteArray(ref byte[] cache) return readData(len, ref cache); } + /// + /// Read a double from a cache of bytes and remove it from the cache + /// + /// The double value + + private static double readNextDouble(ref byte[] cache) + { + byte[] rawValue = readData(8, ref cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToDouble(rawValue, 0); + } + /// /// Read an integer from the network /// @@ -602,6 +632,19 @@ private static byte[] getVarInt(int paramInt) 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 /// @@ -931,6 +974,29 @@ public bool SendBrandInfo(string brandInfo) 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(0x04, concatBytes( + getDouble(location.X), getDouble(location.X), getDouble(location.X), + 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 /// diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 218f49d4d5..927258fd21 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using MinecraftClient.Crypto; +using MinecraftClient.Mapping; namespace MinecraftClient.Protocol { @@ -45,18 +46,25 @@ public interface IMinecraftCom : IDisposable, IAutoComplete bool SendRespawnPacket(); /// - /// Tell the server what client is being used to connect to the server + /// 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. - /// - /// http://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/ /// + /// /// Channel to send packet on /// packet Data /// True if message was successfully sent diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 606e80eaf8..5376e31ad4 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 { @@ -22,6 +23,7 @@ public interface IMinecraftComHandler string GetUserUUID(); string GetSessionID(); string[] GetOnlinePlayers(); + Location GetCurrentLocation(); /// /// Called when a server was successfully joined @@ -50,6 +52,13 @@ public interface IMinecraftComHandler 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 /// diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index c0119ff16a..a0c6fc5da3 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -54,6 +54,7 @@ public static class Settings public static string BrandInfo = MCCBrandInfo; public static bool DisplaySystemMessages = true; public static bool DisplayXPBarMessages = true; + public static bool TerrainAndMovements = false; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -178,6 +179,7 @@ public static void LoadSettings(string settingsfile) case "scriptcache": CacheScripts = str2bool(argValue); break; case "showsystemmessages": DisplaySystemMessages = str2bool(argValue); break; case "showxpbarmessages": DisplayXPBarMessages = str2bool(argValue); break; + case "handleterrainandmovements": TerrainAndMovements = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); @@ -407,6 +409,7 @@ public static void WriteDefaultSettings(string settingsfile) + "chatbotlogfile= #leave empty for no logfile\r\n" + "showsystemmessages=true #system messages for server ops\r\n" + "showxpbarmessages=true #messages displayed above xp bar\r\n" + + "handleterrainandmovements=false #requires more ram and cpu\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" From 2e4544fc5a070d823ad5c6053d77fb129de857dc Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 29 Nov 2015 20:19:43 +0100 Subject: [PATCH 091/102] Fix location sendback (Item pickup!) Location is now properly sent back to the server Item are now properly being picked up (Need to enable movements in INI file) --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index ddb8771bbe..3b08771020 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -988,7 +988,7 @@ public bool SendLocationUpdate(Location location, bool onGround) try { SendPacket(0x04, concatBytes( - getDouble(location.X), getDouble(location.X), getDouble(location.X), + getDouble(location.X), getDouble(location.Y), getDouble(location.Z), new byte[] { onGround ? (byte)1 : (byte)0 })); return true; } From cb00c28b6e0d55d626c5ebeee9710358c5c373d9 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 30 Nov 2015 15:30:49 +0100 Subject: [PATCH 092/102] Add world handling (and fall to ground) - World is now properly parsed and stored from chunk data - Block changes are also handled and world updated accordingly - Added ground checking, the player will move down to reach the ground - Performance tweaking in Protocol18, using lists instead of arrays - Fix player look not properly skipped causing invalid location after teleport --- MinecraftClient/Mapping/Block.cs | 99 ++++++ MinecraftClient/Mapping/Chunk.cs | 63 ++++ MinecraftClient/Mapping/ChunkColumn.cs | 48 +++ MinecraftClient/Mapping/Location.cs | 75 ++++ MinecraftClient/Mapping/World.cs | 103 ++++++ MinecraftClient/McTcpClient.cs | 21 +- MinecraftClient/MinecraftClient.csproj | 4 + .../Protocol/Handlers/Protocol18.cs | 336 +++++++++++++----- .../Protocol/IMinecraftComHandler.cs | 1 + MinecraftClient/Settings.cs | 2 +- 10 files changed, 661 insertions(+), 91 deletions(-) create mode 100644 MinecraftClient/Mapping/Block.cs create mode 100644 MinecraftClient/Mapping/Chunk.cs create mode 100644 MinecraftClient/Mapping/ChunkColumn.cs create mode 100644 MinecraftClient/Mapping/World.cs diff --git a/MinecraftClient/Mapping/Block.cs b/MinecraftClient/Mapping/Block.cs new file mode 100644 index 0000000000..29a082af25 --- /dev/null +++ b/MinecraftClient/Mapping/Block.cs @@ -0,0 +1,99 @@ +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)); + } + } + + /// + /// Check if the block can be passed through or not + /// + public bool Solid + { + get + { + return BlockId != 0; + } + } + + /// + /// 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 + /// + /// + public Block(ushort typeAndMeta) + { + this.blockIdAndMeta = typeAndMeta; + } + + /// + /// Represents an empty block + /// + public static Block Air + { + get + { + return new Block(0); + } + } + + /// + /// 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 0000000000..a64cc329af --- /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[((int)location.X) % Chunk.SizeX, ((int)location.Y) % Chunk.SizeY, ((int)location.Z) % Chunk.SizeZ]; + } + } +} diff --git a/MinecraftClient/Mapping/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs new file mode 100644 index 0000000000..26cbab3918 --- /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/Location.cs b/MinecraftClient/Mapping/Location.cs index 22145ad516..a34f7a0ef9 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -46,6 +46,72 @@ public Location(double x, double y, double z) Z = z; } + /// + /// The X index of the corresponding chunk in the world + /// + public int ChunkX + { + get + { + return ((int)X) / Chunk.SizeX; + } + } + + /// + /// The Y index of the corresponding chunk in the world + /// + public int ChunkY + { + get + { + return ((int)Y) / Chunk.SizeY; + } + } + + /// + /// The Z index of the corresponding chunk in the world + /// + public int ChunkZ + { + get + { + return ((int)Z) / Chunk.SizeY; + } + } + + /// + /// The X index of the corresponding block in the corresponding chunk of the world + /// + public int ChunkBlockX + { + get + { + return ((int)X) % Chunk.SizeX; + } + } + + /// + /// The Y index of the corresponding block in the corresponding chunk of the world + /// + public int ChunkBlockY + { + get + { + return ((int)Y) % Chunk.SizeY; + } + } + + /// + /// The Z index of the corresponding block in the corresponding chunk of the world + /// + public int ChunkBlockZ + { + get + { + return ((int)Z) % Chunk.SizeZ; + } + } + /// /// Compare two locations. Locations are equals if the integer part of their coordinates are equals. /// @@ -155,5 +221,14 @@ public override int GetHashCode() | (((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/World.cs b/MinecraftClient/Mapping/World.cs new file mode 100644 index 0000000000..0214d10056 --- /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 Block.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 66e8bc8abd..0e74b0e387 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -31,6 +31,8 @@ public class McTcpClient : IMinecraftComHandler public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); } public void BotClear() { bots.Clear(); } + private object locationLock = new object(); + private World world = new World(); private Location location; private int updateTicks = 0; @@ -46,6 +48,7 @@ public class McTcpClient : IMinecraftComHandler public string GetUserUUID() { return uuid; } public string GetSessionID() { return sessionid; } public Location GetCurrentLocation() { return location; } + public World GetWorld() { return world; } TcpClient client; IMinecraftCom handler; @@ -345,11 +348,14 @@ public void OnGameJoined() public void UpdateLocation(Location location, bool relative) { - if (relative) + lock (locationLock) { - this.location += location; + if (relative) + { + this.location += location; + } + else this.location = location; } - else this.location = location; } /// @@ -448,7 +454,14 @@ public void OnUpdate() { if (updateTicks >= 10) { - handler.SendLocationUpdate(location, true); //TODO handle onGround once terrain data is available + lock (locationLock) + { + Location belowMe = location + new Location(0, -1, 0); + Block blockBelowMe = world.GetBlock(belowMe); + handler.SendLocationUpdate(location, blockBelowMe.Solid); + if (!blockBelowMe.Solid) + location = belowMe; + } updateTicks = 0; } updateTicks++; diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index b9a92e7ed5..a761553786 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -114,6 +114,10 @@ + + + + diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 3b08771020..06f4fa3031 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -86,9 +86,9 @@ private bool Update() 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; } @@ -102,21 +102,26 @@ private bool Update() /// 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) { int size = readNextVarIntRAW(); //Packet size - packetData = readDataRAW(size); //Packet contents + packetData.AddRange(readDataRAW(size)); //Packet contents //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 } /// @@ -126,7 +131,7 @@ private void readNextPacket(ref int packetID, ref byte[] packetData) /// 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) { @@ -134,7 +139,7 @@ private bool handlePacket(int packetID, byte[] packetData) { case 0x03: if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); + compression_treshold = readNextVarInt(packetData); break; default: return false; //Ignored packet @@ -151,11 +156,11 @@ private bool handlePacket(int packetID, byte[] packetData) handler.OnGameJoined(); break; case 0x02: //Chat message - string message = readNextString(ref packetData); + string message = readNextString(packetData); try { //Hide system messages or xp bar messages? - byte messageType = readData(1, ref packetData)[0]; + byte messageType = readNextByte(packetData); if ((messageType == 1 && !Settings.DisplaySystemMessages) || (messageType == 2 && !Settings.DisplayXPBarMessages)) break; @@ -163,14 +168,14 @@ private bool handlePacket(int packetID, byte[] packetData) catch (IndexOutOfRangeException) { /* No message type */ } handler.OnTextReceived(ChatParser.ParseText(message)); break; - case 0x08: + case 0x08: //Player Position and Look if (Settings.TerrainAndMovements) { - double x = readNextDouble(ref packetData); - double y = readNextDouble(ref packetData); - double z = readNextDouble(ref packetData); - - byte locMask = readNextByte(ref packetData); + 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; @@ -180,18 +185,72 @@ private bool handlePacket(int packetID, byte[] packetData) handler.UpdateLocation(location); } break; + case 0x21: //Chunk Data + if (Settings.TerrainAndMovements) + { + int chunkX = readNextInt(packetData); + int chunkZ = readNextInt(packetData); + bool chunksContinuous = readNextBool(packetData); + ushort chunkMask = readNextUShort(packetData); + int dataSize = readNextVarInt(packetData); + ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData); + } + break; + case 0x22: //Multi Block Change + if (Settings.TerrainAndMovements) + { + int chunkX = readNextInt(packetData); + int chunkZ = readNextInt(packetData); + int recordCount = readNextVarInt(packetData); + for (int i = 0; i < recordCount; i++) + { + 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(blockX + chunkX * Chunk.SizeX, blockY, blockZ + chunkZ * Chunk.SizeZ), block); + } + } + break; + case 0x23: //Block Change + if (Settings.TerrainAndMovements) + handler.GetWorld().SetBlock(Location.FromLongRepresentation(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); + break; + case 0x26: //Map Chunk Bulk + if (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++) + { + chunkXs[chunkColumnNo] = readNextInt(packetData); + chunkZs[chunkColumnNo] = readNextInt(packetData); + chunkMasks[chunkColumnNo] = readNextUShort(packetData); + } + + //Process chunk records + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData); + } + break; case 0x38: //Player List update if (protocolversion >= MC18Version) { - int action = readNextVarInt(ref packetData); - int numActions = readNextVarInt(ref packetData); + int action = readNextVarInt(packetData); + int numActions = readNextVarInt(packetData); for (int i = 0; i < numActions; i++) { - Guid uuid = readNextUUID(ref packetData); + Guid uuid = readNextUUID(packetData); switch (action) { case 0x00: //Player Join - string name = readNextString(ref packetData); + string name = readNextString(packetData); handler.OnPlayerJoin(uuid, name); break; case 0x04: //Player Leave @@ -205,9 +264,9 @@ private bool handlePacket(int packetID, byte[] packetData) } else //MC 1.7.X does not provide UUID in tab-list updates { - string name = readNextString(ref packetData); - bool online = readNextBool(ref packetData); - short ping = readNextShort(ref packetData); + 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); @@ -215,11 +274,11 @@ private bool handlePacket(int packetID, byte[] packetData) } break; case 0x3A: //Tab-Complete Result - int autocomplete_count = readNextVarInt(ref packetData); + int autocomplete_count = readNextVarInt(packetData); string tab_list = ""; for (int i = 0; i < autocomplete_count; i++) { - autocomplete_result = readNextString(ref packetData); + autocomplete_result = readNextString(packetData); if (autocomplete_result != "") tab_list = tab_list + autocomplete_result + " "; } @@ -229,26 +288,26 @@ private bool handlePacket(int packetID, byte[] packetData) ConsoleIO.WriteLineFormatted("§8" + tab_list, false); break; case 0x3F: //Plugin message. - String channel = readNextString(ref packetData); + 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(ref packetData); + readNextShort(packetData); } else { // Forge does something even weirder with the length. - readNextVarShort(ref packetData); + readNextVarShort(packetData); } } if (forgeInfo != null) { if (channel == "FML|HS") { - FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(packetData); if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) { @@ -269,7 +328,7 @@ private bool handlePacket(int packetID, byte[] packetData) string[] channels = { "FML|HS", "FML", "FML|MP", "FML", "FORGE" }; SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels))); - byte fmlProtocolVersion = readNextByte(ref packetData); + byte fmlProtocolVersion = readNextByte(packetData); // There's another value afterwards for the dimension, but we don't need it. ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion); @@ -316,7 +375,7 @@ private bool handlePacket(int packetID, byte[] packetData) { // 1.7.10 and below have one registry // with blocks and items. - int registrySize = readNextVarInt(ref packetData); + int registrySize = readNextVarInt(packetData); ConsoleIO.WriteLineFormatted("§8Received registry " + "with " + registrySize + " entries"); @@ -327,9 +386,9 @@ private bool handlePacket(int packetID, byte[] packetData) { // 1.8+ has more than one registry. - bool hasNextRegistry = readNextBool(ref packetData); - string registryName = readNextString(ref packetData); - int registrySize = readNextVarInt(ref packetData); + bool hasNextRegistry = readNextBool(packetData); + string registryName = readNextString(packetData); + int registrySize = readNextVarInt(packetData); ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + " with " + registrySize + " entries"); @@ -370,15 +429,15 @@ private bool handlePacket(int packetID, byte[] packetData) } return false; case 0x40: //Kick Packet - handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); + handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(packetData))); return false; case 0x46: //Network Compression Treshold Info if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); + compression_treshold = readNextVarInt(packetData); break; case 0x48: //Resource Pack Send - string url = readNextString(ref packetData); - string hash = readNextString(ref packetData); + string url = readNextString(packetData); + string hash = readNextString(packetData); //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); @@ -389,6 +448,66 @@ private bool handlePacket(int packetID, byte[] packetData) 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 (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. /// @@ -445,10 +564,10 @@ private byte[] readDataRAW(int offset) /// Cache of bytes to read from /// The data read from the cache as an array - private static byte[] readData(int offset, ref byte[] cache) + private static byte[] readData(int offset, List cache) { byte[] result = cache.Take(offset).ToArray(); - cache = cache.Skip(offset).ToArray(); + cache.RemoveRange(0, offset); return result; } @@ -458,12 +577,12 @@ private static byte[] readData(int offset, ref byte[] cache) /// Cache of bytes to read from /// The string - private static 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 ""; } @@ -473,9 +592,9 @@ private static string readNextString(ref byte[] cache) /// /// The boolean value - private static bool readNextBool(ref byte[] cache) + private static bool readNextBool(List cache) { - return readData(1, ref cache)[0] != 0x00; + return readNextByte(cache) != 0x00; } /// @@ -483,34 +602,79 @@ private static bool readNextBool(ref byte[] cache) /// /// The short integer value - private static short readNextShort(ref byte[] cache) + private static short readNextShort(List cache) { - byte[] rawValue = readData(2, ref 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(ref byte[] cache) + private static ushort readNextUShort(List cache) { - byte[] rawValue = readData(2, ref 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); + byte[] rawValue = new byte[2]; + ushort[] result = new ushort[amount]; + + for (int i = 0; i < amount; i++) + { + rawValue[0] = rawValues[i * 2]; + rawValue[1] = rawValues[i * 2 + 1]; + result[i] = BitConverter.ToUInt16(rawValue, 0); + } + + 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 static Guid readNextUUID(ref byte[] cache) + private static Guid readNextUUID(List cache) { - return new Guid(readData(16, ref cache)); + return new Guid(readData(16, cache)); } /// @@ -519,12 +683,12 @@ private static Guid readNextUUID(ref byte[] cache) /// Cache of bytes to read from /// The byte array - private byte[] readNextByteArray(ref byte[] cache) + private byte[] readNextByteArray(List cache) { int len = protocolversion >= MC18Version - ? readNextVarInt(ref cache) - : readNextShort(ref cache); - return readData(len, ref cache); + ? readNextVarInt(cache) + : readNextShort(cache); + return readData(len, cache); } /// @@ -532,9 +696,9 @@ private byte[] readNextByteArray(ref byte[] cache) /// /// The double value - private static double readNextDouble(ref byte[] cache) + private static double readNextDouble(List cache) { - byte[] rawValue = readData(8, ref cache); + byte[] rawValue = readData(8, cache); Array.Reverse(rawValue); //Endianness return BitConverter.ToDouble(rawValue, 0); } @@ -567,16 +731,14 @@ private int readNextVarIntRAW() /// Cache of bytes to read from /// The integer - private static 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; @@ -592,14 +754,14 @@ private static int readNextVarInt(ref byte[] cache) /// Cache of bytes to read from /// The int - private static int readNextVarShort(ref byte[] cache) + private static int readNextVarShort(List cache) { - ushort low = readNextUShort(ref cache); + ushort low = readNextUShort(cache); byte high = 0; if ((low & 0x8000) != 0) { low &= 0x7FFF; - high = readNextByte(ref cache); + high = readNextByte(cache); } return ((high & 0xFF) << 15) | low; } @@ -609,9 +771,11 @@ private static int readNextVarShort(ref byte[] cache) /// /// The byte that was read - private static byte readNextByte(ref byte[] cache) + private static byte readNextByte(List cache) { - return readData(1, ref cache)[0]; + byte result = cache[0]; + cache.RemoveAt(0); + return result; } /// @@ -734,10 +898,10 @@ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, b /// 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? { @@ -795,20 +959,20 @@ public bool Login() 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); + 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 @@ -838,15 +1002,15 @@ public bool Login() private bool CompleteForgeHandshake() { int packetID = -1; - byte[] packetData = new byte[0]; + List packetData = new List(); while (fmlHandshakeState != FMLHandshakeClientState.DONE) { - readNextPacket(ref packetID, ref packetData); + readNextPacket(ref packetID, packetData); if (packetID == 0x40) // Disconect { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData))); + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData))); return false; } else @@ -893,13 +1057,13 @@ private bool StartEncryption(string uuid, string sessionID, byte[] token, string //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 @@ -1101,10 +1265,10 @@ public static bool doPing(string host, int port, ref int protocolversion, ref Fo int packetLength = ComTmp.readNextVarIntRAW(); if (packetLength > 0) //Read Response length { - byte[] packetData = ComTmp.readDataRAW(packetLength); - if (readNextVarInt(ref packetData) == 0x00) //Read Packet ID + List packetData = new List(ComTmp.readDataRAW(packetLength)); + if (readNextVarInt(packetData) == 0x00) //Read Packet ID { - string result = readNextString(ref packetData); //Get the Json data + string result = readNextString(packetData); //Get the Json data if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) { diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 5376e31ad4..b9de09d8f4 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -24,6 +24,7 @@ public interface IMinecraftComHandler string GetSessionID(); string[] GetOnlinePlayers(); Location GetCurrentLocation(); + World GetWorld(); /// /// Called when a server was successfully joined diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index a0c6fc5da3..437cbbab31 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -409,7 +409,7 @@ public static void WriteDefaultSettings(string settingsfile) + "chatbotlogfile= #leave empty for no logfile\r\n" + "showsystemmessages=true #system messages for server ops\r\n" + "showxpbarmessages=true #messages displayed above xp bar\r\n" - + "handleterrainandmovements=false #requires more ram and cpu\r\n" + + "handleterrainandmovements=false #requires quite more ram\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" From 5d8d42e3d182ebc5225322bafeff134f4d3d8faa Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 8 Dec 2015 00:34:40 +0100 Subject: [PATCH 093/102] Terrain: Fix coordinate parsing (negative coords) - Optimize readNextUShortsLittleEndian network reading method - Various coordinate computation issues, negative chunk offsets - Properly parse negative coordinates for block change events - Properly reach ground if less than 1 block over the ground --- MinecraftClient/Mapping/Chunk.cs | 2 +- MinecraftClient/Mapping/Location.cs | 38 +++++++++++++++---- MinecraftClient/McTcpClient.cs | 14 ++++--- .../Protocol/Handlers/Protocol18.cs | 11 +----- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/MinecraftClient/Mapping/Chunk.cs b/MinecraftClient/Mapping/Chunk.cs index a64cc329af..5b84ecf238 100644 --- a/MinecraftClient/Mapping/Chunk.cs +++ b/MinecraftClient/Mapping/Chunk.cs @@ -57,7 +57,7 @@ public class Chunk /// The block public Block GetBlock(Location location) { - return this[((int)location.X) % Chunk.SizeX, ((int)location.Y) % Chunk.SizeY, ((int)location.Z) % Chunk.SizeZ]; + return this[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ]; } } } diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index a34f7a0ef9..e55c5a88a1 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -46,6 +46,21 @@ public Location(double x, double y, double z) 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 /// @@ -53,7 +68,7 @@ public int ChunkX { get { - return ((int)X) / Chunk.SizeX; + return (int)Math.Floor(X / Chunk.SizeX); } } @@ -64,7 +79,7 @@ public int ChunkY { get { - return ((int)Y) / Chunk.SizeY; + return (int)Math.Floor(Y / Chunk.SizeY); } } @@ -75,7 +90,7 @@ public int ChunkZ { get { - return ((int)Z) / Chunk.SizeY; + return (int)Math.Floor(Z / Chunk.SizeZ); } } @@ -86,7 +101,7 @@ public int ChunkBlockX { get { - return ((int)X) % Chunk.SizeX; + return ((int)Math.Floor(X) % Chunk.SizeX + Chunk.SizeX) % Chunk.SizeX; } } @@ -97,7 +112,7 @@ public int ChunkBlockY { get { - return ((int)Y) % Chunk.SizeY; + return ((int)Math.Floor(Y) % Chunk.SizeY + Chunk.SizeY) % Chunk.SizeY; } } @@ -108,7 +123,7 @@ public int ChunkBlockZ { get { - return ((int)Z) % Chunk.SizeZ; + return ((int)Math.Floor(Z) % Chunk.SizeZ + Chunk.SizeZ) % Chunk.SizeZ; } } @@ -153,7 +168,16 @@ public ulong GetLongRepresentation() public static Location FromLongRepresentation(ulong location) { - return new Location(location >> 38, (location >> 26) & 0xFFF, location << 38 >> 38); + 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); } /// diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 0e74b0e387..88baf667e5 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -456,11 +456,15 @@ public void OnUpdate() { lock (locationLock) { - Location belowMe = location + new Location(0, -1, 0); - Block blockBelowMe = world.GetBlock(belowMe); - handler.SendLocationUpdate(location, blockBelowMe.Solid); - if (!blockBelowMe.Solid) - location = belowMe; + Location onFoots = new Location(location.X, Math.Floor(location.Y), location.Z); + Location belowFoots = location + new Location(0, -1, 0); + Block blockOnFoots = world.GetBlock(onFoots); + Block blockBelowFoots = world.GetBlock(belowFoots); + handler.SendLocationUpdate(location, blockBelowFoots.Solid); + if (!blockBelowFoots.Solid) + location = belowFoots; + else if (!blockOnFoots.Solid) + location = onFoots; } updateTicks = 0; } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 06f4fa3031..6d39567686 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -209,7 +209,7 @@ private bool handlePacket(int packetID, List packetData) int blockZ = locationXZ & 0x0F; int blockY = (ushort)readNextByte(packetData); Block block = new Block((ushort)readNextVarInt(packetData)); - handler.GetWorld().SetBlock(new Location(blockX + chunkX * Chunk.SizeX, blockY, blockZ + chunkZ * Chunk.SizeZ), block); + handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); } } break; @@ -653,16 +653,9 @@ private static ulong readNextULong(List cache) private static ushort[] readNextUShortsLittleEndian(int amount, List cache) { byte[] rawValues = readData(2 * amount, cache); - byte[] rawValue = new byte[2]; ushort[] result = new ushort[amount]; - for (int i = 0; i < amount; i++) - { - rawValue[0] = rawValues[i * 2]; - rawValue[1] = rawValues[i * 2 + 1]; - result[i] = BitConverter.ToUInt16(rawValue, 0); - } - + result[i] = BitConverter.ToUInt16(rawValues, i * 2); return result; } From 49702e30b868f850b1ca2968eb7faf69bf13bfc7 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 9 Dec 2015 23:04:00 +0100 Subject: [PATCH 094/102] Add block material database Taken from Bukkit's Material class, with credits. Allows to know types and properties of blocks. + Use database for "is solid" checks + Add "can harm players" method + Faster movements, falling seems natural now + Shorter error message when ping failed --- MinecraftClient/Mapping/Block.cs | 20 +- MinecraftClient/Mapping/Material.cs | 346 ++++++++++++++++++++ MinecraftClient/Mapping/World.cs | 2 +- MinecraftClient/McTcpClient.cs | 26 +- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Protocol/ProtocolHandler.cs | 2 +- 6 files changed, 367 insertions(+), 30 deletions(-) create mode 100644 MinecraftClient/Mapping/Material.cs diff --git a/MinecraftClient/Mapping/Block.cs b/MinecraftClient/Mapping/Block.cs index 29a082af25..e7aa0d958e 100644 --- a/MinecraftClient/Mapping/Block.cs +++ b/MinecraftClient/Mapping/Block.cs @@ -46,13 +46,13 @@ public byte BlockMeta } /// - /// Check if the block can be passed through or not + /// Material of the block /// - public bool Solid + public Material Type { get { - return BlockId != 0; + return (Material)BlockId; } } @@ -71,22 +71,18 @@ public Block(short type, byte metadata = 0) /// /// Get a block of the specified type and metadata /// - /// + /// Type and metadata packed in the same value public Block(ushort typeAndMeta) { this.blockIdAndMeta = typeAndMeta; } /// - /// Represents an empty block + /// Get a block of the specified type and metadata /// - public static Block Air - { - get - { - return new Block(0); - } - } + /// Block type + public Block(Material type, byte metadata = 0) + : this((short)type, metadata) { } /// /// String representation of the block diff --git a/MinecraftClient/Mapping/Material.cs b/MinecraftClient/Mapping/Material.cs new file mode 100644 index 0000000000..d95a960555 --- /dev/null +++ b/MinecraftClient/Mapping/Material.cs @@ -0,0 +1,346 @@ +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.StationaryLava: + case Material.StationaryWater: + return true; + default: + return false; + } + } + } +} diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs index 0214d10056..25b7962518 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -80,7 +80,7 @@ public Block GetBlock(Location location) if (chunk != null) return chunk.GetBlock(location); } - return Block.Air; + return new Block(Material.Air); } /// diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 88baf667e5..7a6da5f300 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -34,7 +34,6 @@ public class McTcpClient : IMinecraftComHandler private object locationLock = new object(); private World world = new World(); private Location location; - private int updateTicks = 0; private string host; private int port; @@ -452,23 +451,18 @@ public void OnUpdate() if (Settings.TerrainAndMovements) { - if (updateTicks >= 10) + lock (locationLock) { - lock (locationLock) - { - Location onFoots = new Location(location.X, Math.Floor(location.Y), location.Z); - Location belowFoots = location + new Location(0, -1, 0); - Block blockOnFoots = world.GetBlock(onFoots); - Block blockBelowFoots = world.GetBlock(belowFoots); - handler.SendLocationUpdate(location, blockBelowFoots.Solid); - if (!blockBelowFoots.Solid) - location = belowFoots; - else if (!blockOnFoots.Solid) - location = onFoots; - } - updateTicks = 0; + Location onFoots = new Location(location.X, Math.Floor(location.Y), location.Z); + Location belowFoots = location + new Location(0, -1, 0); + Block blockOnFoots = world.GetBlock(onFoots); + Block blockBelowFoots = world.GetBlock(belowFoots); + handler.SendLocationUpdate(location, blockBelowFoots.Type.IsSolid()); + if (!blockBelowFoots.Type.IsSolid()) + location = belowFoots; + else if (!blockOnFoots.Type.IsSolid()) + location = onFoots; } - updateTicks++; } } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index a761553786..dbff60b7b5 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -117,6 +117,7 @@ + diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 266cbdda6c..6377cd498d 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -42,7 +42,7 @@ public static bool GetServerInfo(string serverIP, ushort serverPort, ref int pro } catch (Exception e) { - ConsoleIO.WriteLineFormatted("§8" + e.ToString()); + ConsoleIO.WriteLineFormatted(String.Format("§8{0}: {1}", e.GetType().FullName, e.Message)); } }, TimeSpan.FromSeconds(30))) { From 00131de08b5bb5f22b9513f060fe4bf1fb6603c0 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 10 Dec 2015 18:33:01 +0100 Subject: [PATCH 095/102] Fix CanHarmPlayers in Material.cs - Add flowing lava - Remove stationary water Reported by Pokechu22 :) --- MinecraftClient/Mapping/Material.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Mapping/Material.cs b/MinecraftClient/Mapping/Material.cs index d95a960555..c506ef94ee 100644 --- a/MinecraftClient/Mapping/Material.cs +++ b/MinecraftClient/Mapping/Material.cs @@ -335,8 +335,8 @@ public static bool CanHarmPlayers(this Material m) { case Material.Fire: case Material.Cactus: + case Material.Lava: case Material.StationaryLava: - case Material.StationaryWater: return true; default: return false; From b0c8f82697daada2e5a16126d6bac741e705898d Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 12 Dec 2015 16:48:29 +0100 Subject: [PATCH 096/102] Add simple movements with /move command - Determine if we can move to the specified direction - Add moving ability to the specified direction - Add /move command for triggering moves - Add move decomp. into steps (more natural) - Add pathfinding routines (still WIP) - SO YES YOU CAN NOW WALK USING MCC!!! --- MinecraftClient/Commands/Move.cs | 59 ++++ MinecraftClient/Mapping/Direction.cs | 21 ++ MinecraftClient/Mapping/Location.cs | 83 +++++- MinecraftClient/Mapping/Material.cs | 19 ++ MinecraftClient/Mapping/Movement.cs | 264 ++++++++++++++++++ MinecraftClient/McTcpClient.cs | 37 ++- MinecraftClient/MinecraftClient.csproj | 3 + .../Protocol/Handlers/Protocol18.cs | 2 +- MinecraftClient/Settings.cs | 4 +- 9 files changed, 475 insertions(+), 17 deletions(-) create mode 100644 MinecraftClient/Commands/Move.cs create mode 100644 MinecraftClient/Mapping/Direction.cs create mode 100644 MinecraftClient/Mapping/Movement.cs diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs new file mode 100644 index 0000000000..25ab8e161e --- /dev/null +++ b/MinecraftClient/Commands/Move.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MinecraftClient.Mapping; + +namespace MinecraftClient.Commands +{ + public class Move : Command + { + public override string CMDName { get { return "move"; } } + public override string CMDDesc { get { return "move : walk or start walking."; } } + + public override string Run(McTcpClient handler, string command) + { + if (Settings.TerrainAndMovements) + { + string[] args = getArgs(command); + if (args.Length == 1) + { + string dirStr = getArg(command).Trim().ToLower(); + Direction direction; + switch (dirStr) + { + case "up": direction = Direction.Up; break; + case "down": direction = Direction.Down; break; + case "east": direction = Direction.East; break; + case "west": direction = Direction.West; break; + case "north": direction = Direction.North; break; + case "south": direction = Direction.South; break; + default: return "Unknown direction '" + dirStr + "'."; + } + if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction)) + { + handler.MoveTo(Movement.Move(handler.GetCurrentLocation(), direction)); + return "Moving " + dirStr + '.'; + } + else return "Cannot move in that direction."; + } + else if (args.Length == 3) + { + try + { + int x = int.Parse(args[0]); + int y = int.Parse(args[1]); + int z = int.Parse(args[2]); + Location goal = new Location(x, y, z); + if (handler.MoveTo(goal)) + return "Walking to " + goal; + return "Failed to compute path to " + goal; + } + catch (FormatException) { return CMDDesc; } + } + else return CMDDesc; + } + else return "Please enable terrainandmovements in config to use this command."; + } + } +} diff --git a/MinecraftClient/Mapping/Direction.cs b/MinecraftClient/Mapping/Direction.cs new file mode 100644 index 0000000000..5092e7ff94 --- /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 index e55c5a88a1..ed683134a8 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -127,6 +127,28 @@ public int ChunkBlockZ } } + /// + /// 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. /// @@ -156,7 +178,7 @@ public override bool Equals(object obj) /// /// Location representation as ulong - public ulong GetLongRepresentation() + public ulong GetLong() { return ((((ulong)X) & 0x3FFFFFF) << 38) | ((((ulong)Y) & 0xFFF) << 26) | (((ulong)Z) & 0x3FFFFFF); } @@ -166,7 +188,7 @@ public ulong GetLongRepresentation() /// /// Location represented by the ulong - public static Location FromLongRepresentation(ulong location) + public static Location FromLong(ulong location) { int x = (int)(location >> 38); int y = (int)((location >> 26) & 0xFFF); @@ -186,7 +208,7 @@ public static Location FromLongRepresentation(ulong location) /// First location to compare /// Second location to compare /// TRUE if the locations are equals - public static bool operator == (Location loc1, Location loc2) + public static bool operator ==(Location loc1, Location loc2) { if (loc1 == null && loc2 == null) return true; @@ -201,7 +223,7 @@ public static Location FromLongRepresentation(ulong location) /// First location to compare /// Second location to compare /// TRUE if the locations are equals - public static bool operator != (Location loc1, Location loc2) + public static bool operator !=(Location loc1, Location loc2) { if (loc1 == null && loc2 == null) return true; @@ -219,7 +241,7 @@ public static Location FromLongRepresentation(ulong location) /// First location to sum /// Second location to sum /// Sum of the two locations - public static Location operator + (Location loc1, Location loc2) + public static Location operator +(Location loc1, Location loc2) { return new Location ( @@ -229,6 +251,57 @@ public static Location FromLongRepresentation(ulong location) ); } + /// + /// 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 == /// diff --git a/MinecraftClient/Mapping/Material.cs b/MinecraftClient/Mapping/Material.cs index c506ef94ee..005b0b5e5a 100644 --- a/MinecraftClient/Mapping/Material.cs +++ b/MinecraftClient/Mapping/Material.cs @@ -342,5 +342,24 @@ public static bool CanHarmPlayers(this Material m) 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 0000000000..ac963f84fc --- /dev/null +++ b/MinecraftClient/Mapping/Movement.cs @@ -0,0 +1,264 @@ +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) + { + HashSet ClosedSet = new HashSet(); // The set of locations already evaluated. + HashSet OpenSet = new HashSet(); // 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) + Queue total_path = new Queue(new Location[] { current }); + while (Came_From.ContainsKey(current)) + { + current = Came_From[current]; + total_path.Enqueue(current); + } + return 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) + } + } + + return null; + } + + /* ========= 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/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 7a6da5f300..a4750e939f 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -33,6 +33,8 @@ public class McTcpClient : IMinecraftComHandler private object locationLock = new object(); private World world = new World(); + private Queue steps; + private Queue path; private Location location; private string host; @@ -369,6 +371,23 @@ 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; + } + } + /// /// Received some text from the server /// @@ -453,15 +472,15 @@ public void OnUpdate() { lock (locationLock) { - Location onFoots = new Location(location.X, Math.Floor(location.Y), location.Z); - Location belowFoots = location + new Location(0, -1, 0); - Block blockOnFoots = world.GetBlock(onFoots); - Block blockBelowFoots = world.GetBlock(belowFoots); - handler.SendLocationUpdate(location, blockBelowFoots.Type.IsSolid()); - if (!blockBelowFoots.Type.IsSolid()) - location = belowFoots; - else if (!blockOnFoots.Type.IsSolid()) - location = onFoots; + 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)); + } } } } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index dbff60b7b5..c0a631c9d7 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -86,6 +86,7 @@ + @@ -117,7 +118,9 @@ + + diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 6d39567686..5bbd7da514 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -215,7 +215,7 @@ private bool handlePacket(int packetID, List packetData) break; case 0x23: //Block Change if (Settings.TerrainAndMovements) - handler.GetWorld().SetBlock(Location.FromLongRepresentation(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); + handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); break; case 0x26: //Map Chunk Bulk if (Settings.TerrainAndMovements) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 437cbbab31..90b3689dbc 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -179,7 +179,7 @@ public static void LoadSettings(string settingsfile) case "scriptcache": CacheScripts = str2bool(argValue); break; case "showsystemmessages": DisplaySystemMessages = str2bool(argValue); break; case "showxpbarmessages": DisplayXPBarMessages = str2bool(argValue); break; - case "handleterrainandmovements": TerrainAndMovements = str2bool(argValue); break; + case "terrainandmovements": TerrainAndMovements = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); @@ -409,7 +409,7 @@ public static void WriteDefaultSettings(string settingsfile) + "chatbotlogfile= #leave empty for no logfile\r\n" + "showsystemmessages=true #system messages for server ops\r\n" + "showxpbarmessages=true #messages displayed above xp bar\r\n" - + "handleterrainandmovements=false #requires quite more ram\r\n" + + "terrainandmovements=false #uses more ram, cpu, bandwidth\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" From 902b04656cafb7f5ce4689ef7e92062be847e2d4 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 13 Dec 2015 21:58:55 +0100 Subject: [PATCH 097/102] Fix pathfinding to coordinates - Now possible to walk to given coordinates - Fix sending location before it is received --- MinecraftClient/Mapping/Movement.cs | 9 +++++---- MinecraftClient/McTcpClient.cs | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs index ac963f84fc..b85852dba4 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -99,7 +99,7 @@ public static Queue Move2Steps(Location start, Location goal, int step public static Queue CalculatePath(World world, Location start, Location goal, bool allowUnsafe = false) { HashSet ClosedSet = new HashSet(); // The set of locations already evaluated. - HashSet OpenSet = new HashSet(); // The set of tentative nodes to be evaluated, initially containing the start node + 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 @@ -117,13 +117,14 @@ public static Queue CalculatePath(World world, Location start, Locatio .OrderBy(pair => pair.Value).First().Key; if (current == goal) { //reconstruct_path(Came_From, goal) - Queue total_path = new Queue(new Location[] { current }); + List total_path = new List(new[] { current }); while (Came_From.ContainsKey(current)) { current = Came_From[current]; - total_path.Enqueue(current); + total_path.Add(current); } - return total_path; + total_path.Reverse(); + return new Queue(total_path); } OpenSet.Remove(current); ClosedSet.Add(current); diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index a4750e939f..6423460af5 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -32,6 +32,7 @@ public class McTcpClient : IMinecraftComHandler public void BotClear() { bots.Clear(); } private object locationLock = new object(); + private bool locationReceived = false; private World world = new World(); private Queue steps; private Queue path; @@ -356,6 +357,7 @@ public void UpdateLocation(Location location, bool relative) this.location += location; } else this.location = location; + locationReceived = true; } } @@ -468,7 +470,7 @@ public void OnUpdate() } } - if (Settings.TerrainAndMovements) + if (Settings.TerrainAndMovements && locationReceived) { lock (locationLock) { From 71277362be44ff23cbbed2b182cdc929c8b5a273 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 17 Dec 2015 17:40:26 +0100 Subject: [PATCH 098/102] Add timeout when calculating unreachable path 5s timeout, assuming destination is unreachable otherwise. --- MinecraftClient/Mapping/Movement.cs | 89 +++++++++++++++-------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs index b85852dba4..a12af234f3 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -98,54 +98,59 @@ public static Queue Move2Steps(Location start, Location goal, int step /// A list of locations, or null if calculation failed public static Queue CalculatePath(World world, Location start, Location goal, bool allowUnsafe = false) { - 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. + Queue result = null; - 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) + AutoTimeout.Perform(() => { - 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(); - return new Queue(total_path); - } - OpenSet.Remove(current); - ClosedSet.Add(current); - foreach (Location neighbor in GetAvailableMoves(world, current, allowUnsafe)) + 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) { - 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. + 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) + // 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 null; + return result; } /* ========= LOCATION PROPERTIES ========= */ From d36647af3e4c86618e5dc268bed8aa622063840e Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 16 Jan 2016 17:51:08 +0100 Subject: [PATCH 099/102] Add GetWorld() API method for ChatBots --- MinecraftClient/CSharpRunner.cs | 1 + MinecraftClient/ChatBot.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/MinecraftClient/CSharpRunner.cs b/MinecraftClient/CSharpRunner.cs index b8ec098a3d..29a980cbaa 100644 --- a/MinecraftClient/CSharpRunner.cs +++ b/MinecraftClient/CSharpRunner.cs @@ -74,6 +74,7 @@ public static object Run(ChatBot apiHandler, ManualResetEvent tickHandler, strin "using System.IO;", "using System.Threading;", "using MinecraftClient;", + "using MinecraftClient.Mapping;", "namespace ScriptLoader {", "public class Script {", "public CSharpAPI MCC;", diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index a9349d7220..ef4494dd50 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -502,6 +502,18 @@ protected void RunScript(string filename, string playername = "") Handler.BotLoad(new ChatBots.Script(filename, playername)); } + /// + /// Get the current Minecraft World + /// + /// Minecraft world or null if associated setting is disabled + + protected Mapping.World GetWorld() + { + if (Settings.TerrainAndMovements) + return Handler.GetWorld(); + return null; + } + /// /// Get a Y-M-D h:m:s timestamp representing the current system date and time /// From 67939774baaaf06805adca115624509995e2a321 Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 26 Jan 2016 10:35:44 +0100 Subject: [PATCH 100/102] Chat formats : Catch Argument out of range Whas happening for chat messages starting with '<' but never ending with '>'. Bug report by Aderpace. --- MinecraftClient/ChatBot.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index ef4494dd50..152e36c7ac 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -250,6 +250,7 @@ protected static bool IsPrivateMessage(string text, ref string message, ref stri else return false; } catch (IndexOutOfRangeException) { /* Not an expected chat format */ } + catch (ArgumentOutOfRangeException) { /* Same here */ } } //User-defined regex for private chat messages @@ -308,6 +309,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string return IsValidName(sender); } catch (IndexOutOfRangeException) { /* Not a vanilla/faction message */ } + catch (ArgumentOutOfRangeException) { /* Same here */ } } //Detect HeroChat Messages @@ -324,6 +326,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string return IsValidName(sender); } catch (IndexOutOfRangeException) { /* Not a herochat message */ } + catch (ArgumentOutOfRangeException) { /* Same here */ } } //Detect (Unknown Plugin) Messages @@ -351,6 +354,7 @@ protected static bool IsChatMessage(string text, ref string message, ref string } } catch (IndexOutOfRangeException) { /* Not a message */ } + catch (ArgumentOutOfRangeException) { /* Same here */ } } } From 14b8895716cb058475b5eb75c49f3f743b0e3327 Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 27 Jan 2016 00:21:18 +0100 Subject: [PATCH 101/102] Add regex access for C# scripts Suggested by _initsuj --- MinecraftClient/CSharpRunner.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MinecraftClient/CSharpRunner.cs b/MinecraftClient/CSharpRunner.cs index 29a980cbaa..340bddc841 100644 --- a/MinecraftClient/CSharpRunner.cs +++ b/MinecraftClient/CSharpRunner.cs @@ -69,6 +69,7 @@ public static object Run(ChatBot apiHandler, ManualResetEvent tickHandler, strin { "using System;", "using System.Collections.Generic;", + "using System.Text.RegularExpressions;", "using System.Linq;", "using System.Text;", "using System.IO;", From ba41268aca64105378d150e97fb8b0807fff474c Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 27 Jan 2016 00:23:25 +0100 Subject: [PATCH 102/102] Add setting for setting private msg command So that the /tell command can be changed into eg /msg. Suggested by _initsuj --- MinecraftClient/ChatBot.cs | 2 +- MinecraftClient/Settings.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 152e36c7ac..816e997871 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -492,7 +492,7 @@ protected void UnloadBot() protected void SendPrivateMessage(string player, string message) { - SendText("/tell " + player + ' ' + message); + SendText(String.Format("/{0} {1} {2}", Settings.PrivateMsgsCmdName, player, message)); } /// diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 90b3689dbc..36ce2f877c 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -55,6 +55,7 @@ public static class Settings public static bool DisplaySystemMessages = true; public static bool DisplayXPBarMessages = true; public static bool TerrainAndMovements = false; + public static string PrivateMsgsCmdName = "tell"; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -180,6 +181,7 @@ public static void LoadSettings(string settingsfile) 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 "botowners": Bots_Owners.Clear(); @@ -407,6 +409,7 @@ public static void WriteDefaultSettings(string settingsfile) + "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"