From e3c38ed6acdd8e795aa565334af7dc3713f9667e Mon Sep 17 00:00:00 2001 From: ORelio Date: Wed, 11 Mar 2015 19:58:28 +0100 Subject: [PATCH 001/131] 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 c65a6ab3..4ad3ae27 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -18,7 +18,7 @@ namespace MinecraftClient { 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 @@ namespace MinecraftClient 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 08b309f3..cad60365 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -109,6 +109,8 @@ namespace MinecraftClient.Protocol 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 a6a705e3..9811ca28 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/131] 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 2ba5c7d5..781b61d6 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -20,7 +20,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient public void OnPlayerJoin(Guid uuid, string name) { - onlinePlayers[uuid] = name; + lock (onlinePlayers) + { + onlinePlayers[uuid] = name; + } } /// @@ -422,7 +425,10 @@ namespace MinecraftClient public void OnPlayerLeave(Guid uuid) { - onlinePlayers.Remove(uuid); + lock (onlinePlayers) + { + onlinePlayers.Remove(uuid); + } } /// @@ -432,7 +438,10 @@ namespace MinecraftClient 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/131] 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 00000000..a1b54456 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/AesFastEngine.cs @@ -0,0 +1,855 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * an implementation of the AES (Rijndael)), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor), they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations), 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each), for a total of 2Kbytes), + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first + * + * The slowest version uses no static tables at all and computes the values in each round + *

+ *

+ * This file contains the fast version with 8Kbytes of static tables for round precomputation + *

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

+ * Note: in the case where the underlying cipher is either a CFB cipher or an + * OFB one the last block may not be a multiple of the block size. + *

+ */ + public class BufferedBlockCipher + : BufferedCipherBase + { + internal byte[] buf; + internal int bufOff; + internal bool forEncryption; + internal IBlockCipher cipher; + + /** + * constructor for subclasses + */ + protected BufferedBlockCipher() + { + } + + /** + * Create a buffered block cipher without padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + * false otherwise. + */ + public BufferedBlockCipher( + IBlockCipher cipher) + { + if (cipher == null) + throw new ArgumentNullException("cipher"); + + this.cipher = cipher; + buf = new byte[cipher.GetBlockSize()]; + bufOff = 0; + } + + public override string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + // Note: This doubles as the Init in the event that this cipher is being used as an IWrapper + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + //ParametersWithRandom pwr = parameters as ParametersWithRandom; + //if (pwr != null) + // parameters = pwr.Parameters; + + Reset(); + + cipher.Init(forEncryption, parameters); + } + + /** + * return the blocksize for the underlying cipher. + * + * @return the blocksize for the underlying cipher. + */ + public override int GetBlockSize() + { + return cipher.GetBlockSize(); + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public override int GetUpdateOutputSize( + int length) + { + int total = length + bufOff; + int leftOver = total % buf.Length; + return total - leftOver; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public override int GetOutputSize( + int length) + { + // Note: Can assume IsPartialBlockOkay is true for purposes of this calculation + return length + bufOff; + } + + /** + * process a single byte, producing an output block if necessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessByte( + byte input, + byte[] output, + int outOff) + { + buf[bufOff++] = input; + + if (bufOff == buf.Length) + { + if ((outOff + buf.Length) > output.Length) + throw new DataLengthException("output buffer too short"); + + bufOff = 0; + return cipher.ProcessBlock(buf, 0, output, outOff); + } + + return 0; + } + + public override byte[] ProcessByte( + byte input) + { + int outLength = GetUpdateOutputSize(1); + + byte[] outBytes = outLength > 0 ? new byte[outLength] : null; + + int pos = ProcessByte(input, outBytes, 0); + + if (outLength > 0 && pos < outLength) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + public override byte[] ProcessBytes( + byte[] input, + int inOff, + int length) + { + if (input == null) + throw new ArgumentNullException("input"); + if (length < 1) + return null; + + int outLength = GetUpdateOutputSize(length); + + byte[] outBytes = outLength > 0 ? new byte[outLength] : null; + + int pos = ProcessBytes(input, inOff, length, outBytes, 0); + + if (outLength > 0 && pos < outLength) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + if (length < 1) + { + if (length < 0) + throw new ArgumentException("Can't have a negative input length!"); + + return 0; + } + + int blockSize = GetBlockSize(); + int outLength = GetUpdateOutputSize(length); + + if (outLength > 0) + { + Check.OutputLength(output, outOff, outLength, "output buffer too short"); + } + + int resultLen = 0; + int gapLen = buf.Length - bufOff; + if (length > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + resultLen += cipher.ProcessBlock(buf, 0, output, outOff); + bufOff = 0; + length -= gapLen; + inOff += gapLen; + while (length > buf.Length) + { + resultLen += cipher.ProcessBlock(input, inOff, output, outOff + resultLen); + length -= blockSize; + inOff += blockSize; + } + } + Array.Copy(input, inOff, buf, bufOff, length); + bufOff += length; + if (bufOff == buf.Length) + { + resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen); + bufOff = 0; + } + return resultLen; + } + + public override byte[] DoFinal() + { + byte[] outBytes = EmptyBuffer; + + int length = GetOutputSize(0); + if (length > 0) + { + outBytes = new byte[length]; + + int pos = DoFinal(outBytes, 0); + if (pos < outBytes.Length) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + } + else + { + Reset(); + } + + return outBytes; + } + + public override byte[] DoFinal( + byte[] input, + int inOff, + int inLen) + { + if (input == null) + throw new ArgumentNullException("input"); + + int length = GetOutputSize(inLen); + + byte[] outBytes = EmptyBuffer; + + if (length > 0) + { + outBytes = new byte[length]; + + int pos = (inLen > 0) + ? ProcessBytes(input, inOff, inLen, outBytes, 0) + : 0; + + pos += DoFinal(outBytes, pos); + + if (pos < outBytes.Length) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + } + else + { + Reset(); + } + + return outBytes; + } + + /** + * Process the last block in the buffer. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output, or the input is not block size aligned and should be. + * @exception InvalidOperationException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + * @exception DataLengthException if the input is not block size + * aligned. + */ + public override int DoFinal( + byte[] output, + int outOff) + { + try + { + if (bufOff != 0) + { + Check.DataLength(!cipher.IsPartialBlockOkay, "data not block size aligned"); + Check.OutputLength(output, outOff, bufOff, "output buffer too short for DoFinal()"); + + // NB: Can't copy directly, or we may write too much output + cipher.ProcessBlock(buf, 0, buf, 0); + Array.Copy(buf, 0, output, outOff, bufOff); + } + + return bufOff; + } + finally + { + Reset(); + } + } + + /** + * Reset the buffer and cipher. After resetting the object is in the same + * state as it was after the last init (if there was one). + */ + public override void Reset() + { + Array.Clear(buf, 0, buf.Length); + bufOff = 0; + + cipher.Reset(); + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs b/MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs new file mode 100644 index 00000000..9d861021 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/BufferedCipherBase.cs @@ -0,0 +1,113 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + public abstract class BufferedCipherBase + : IBufferedCipher + { + protected static readonly byte[] EmptyBuffer = new byte[0]; + + public abstract string AlgorithmName { get; } + + public abstract void Init(bool forEncryption, ICipherParameters parameters); + + public abstract int GetBlockSize(); + + public abstract int GetOutputSize(int inputLen); + public abstract int GetUpdateOutputSize(int inputLen); + + public abstract byte[] ProcessByte(byte input); + + public virtual int ProcessByte( + byte input, + byte[] output, + int outOff) + { + byte[] outBytes = ProcessByte(input); + if (outBytes == null) + return 0; + if (outOff + outBytes.Length > output.Length) + throw new DataLengthException("output buffer too short"); + outBytes.CopyTo(output, outOff); + return outBytes.Length; + } + + public virtual byte[] ProcessBytes( + byte[] input) + { + return ProcessBytes(input, 0, input.Length); + } + + public abstract byte[] ProcessBytes(byte[] input, int inOff, int length); + + public virtual int ProcessBytes( + byte[] input, + byte[] output, + int outOff) + { + return ProcessBytes(input, 0, input.Length, output, outOff); + } + + public virtual int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + byte[] outBytes = ProcessBytes(input, inOff, length); + if (outBytes == null) + return 0; + if (outOff + outBytes.Length > output.Length) + throw new DataLengthException("output buffer too short"); + outBytes.CopyTo(output, outOff); + return outBytes.Length; + } + + public abstract byte[] DoFinal(); + + public virtual byte[] DoFinal( + byte[] input) + { + return DoFinal(input, 0, input.Length); + } + + public abstract byte[] DoFinal( + byte[] input, + int inOff, + int length); + + public virtual int DoFinal( + byte[] output, + int outOff) + { + byte[] outBytes = DoFinal(); + if (outOff + outBytes.Length > output.Length) + throw new DataLengthException("output buffer too short"); + outBytes.CopyTo(output, outOff); + return outBytes.Length; + } + + public virtual int DoFinal( + byte[] input, + byte[] output, + int outOff) + { + return DoFinal(input, 0, input.Length, output, outOff); + } + + public virtual int DoFinal( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + int len = ProcessBytes(input, inOff, length, output, outOff); + len += DoFinal(output, outOff + len); + return len; + } + + public abstract void Reset(); + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs new file mode 100644 index 00000000..43371653 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/CfbBlockCipher.cs @@ -0,0 +1,224 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ + public class CfbBlockCipher + : IBlockCipher + { + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + private bool encrypting; + + private readonly int blockSize; + private readonly IBlockCipher cipher; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param blockSize the block size in bits (note: a multiple of 8) + */ + public CfbBlockCipher( + IBlockCipher cipher, + int bitBlockSize) + { + this.cipher = cipher; + this.blockSize = bitBlockSize / 8; + this.IV = new byte[cipher.GetBlockSize()]; + this.cfbV = new byte[cipher.GetBlockSize()]; + this.cfbOutV = new byte[cipher.GetBlockSize()]; + } + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public IBlockCipher GetUnderlyingCipher() + { + return cipher; + } + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.encrypting = forEncryption; + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV) parameters; + byte[] iv = ivParam.GetIV(); + int diff = IV.Length - iv.Length; + Array.Copy(iv, 0, IV, diff, iv.Length); + Array.Clear(IV, 0, diff); + + parameters = ivParam.Parameters; + } + Reset(); + + // if it's null, key is to be reused. + if (parameters != null) + { + cipher.Init(true, parameters); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/CFB" + (blockSize * 8); } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int GetBlockSize() + { + return blockSize; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + return (encrypting) + ? EncryptBlock(input, inOff, output, outOff) + : DecryptBlock(input, inOff, output, outOff); + } + + /** + * Do the appropriate processing for CFB mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int EncryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + if ((outOff + blockSize) > outBytes.Length) + { + throw new DataLengthException("output buffer too short"); + } + cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); + // + // XOR the cfbV with the plaintext producing the ciphertext + // + for (int i = 0; i < blockSize; i++) + { + outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); + } + // + // change over the input block. + // + Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); + Array.Copy(outBytes, outOff, cfbV, cfbV.Length - blockSize, blockSize); + return blockSize; + } + /** + * Do the appropriate processing for CFB mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + if ((outOff + blockSize) > outBytes.Length) + { + throw new DataLengthException("output buffer too short"); + } + cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); + // + // change over the input block. + // + Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); + Array.Copy(input, inOff, cfbV, cfbV.Length - blockSize, blockSize); + // + // XOR the cfbV with the ciphertext producing the plaintext + // + for (int i = 0; i < blockSize; i++) + { + outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); + } + return blockSize; + } + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void Reset() + { + Array.Copy(IV, 0, cfbV, 0, IV.Length); + cipher.Reset(); + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/Check.cs b/MinecraftClient/Crypto/Streams/BouncyAes/Check.cs new file mode 100644 index 00000000..96a05c64 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/Check.cs @@ -0,0 +1,25 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + internal class Check + { + internal static void DataLength(bool condition, string msg) + { + if (condition) + throw new DataLengthException(msg); + } + + internal static void DataLength(byte[] buf, int off, int len, string msg) + { + if (off + len > buf.Length) + throw new DataLengthException(msg); + } + + internal static void OutputLength(byte[] buf, int off, int len, string msg) + { + if (off + len > buf.Length) + throw new OutputLengthException(msg); + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs b/MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs new file mode 100644 index 00000000..b6920854 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/CipherStream.cs @@ -0,0 +1,234 @@ +using System; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.IO +{ + public class CipherStream + : Stream + { + internal Stream stream; + internal IBufferedCipher inCipher, outCipher; + private byte[] mInBuf; + private int mInPos; + private bool inStreamEnded; + + public CipherStream( + Stream stream, + IBufferedCipher readCipher, + IBufferedCipher writeCipher) + { + this.stream = stream; + + if (readCipher != null) + { + this.inCipher = readCipher; + mInBuf = null; + } + + if (writeCipher != null) + { + this.outCipher = writeCipher; + } + } + + public IBufferedCipher ReadCipher + { + get { return inCipher; } + } + + public IBufferedCipher WriteCipher + { + get { return outCipher; } + } + + public override int ReadByte() + { + if (inCipher == null) + return stream.ReadByte(); + + if (mInBuf == null || mInPos >= mInBuf.Length) + { + if (!FillInBuf()) + return -1; + } + + return mInBuf[mInPos++]; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + if (inCipher == null) + return stream.Read(buffer, offset, count); + + int num = 0; + while (num < count) + { + if (mInBuf == null || mInPos >= mInBuf.Length) + { + if (!FillInBuf()) + break; + } + + int numToCopy = System.Math.Min(count - num, mInBuf.Length - mInPos); + Array.Copy(mInBuf, mInPos, buffer, offset + num, numToCopy); + mInPos += numToCopy; + num += numToCopy; + } + + return num; + } + + private bool FillInBuf() + { + if (inStreamEnded) + return false; + + mInPos = 0; + + do + { + mInBuf = ReadAndProcessBlock(); + } + while (!inStreamEnded && mInBuf == null); + + return mInBuf != null; + } + + private byte[] ReadAndProcessBlock() + { + int blockSize = inCipher.GetBlockSize(); + int readSize = (blockSize == 0) ? 256 : blockSize; + + byte[] block = new byte[readSize]; + int numRead = 0; + do + { + int count = stream.Read(block, numRead, block.Length - numRead); + if (count < 1) + { + inStreamEnded = true; + break; + } + numRead += count; + } + while (numRead < block.Length); + + Debug.Assert(inStreamEnded || numRead == block.Length); + + byte[] bytes = inStreamEnded + ? inCipher.DoFinal(block, 0, numRead) + : inCipher.ProcessBytes(block); + + if (bytes != null && bytes.Length == 0) + { + bytes = null; + } + + return bytes; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + Debug.Assert(buffer != null); + Debug.Assert(0 <= offset && offset <= buffer.Length); + Debug.Assert(count >= 0); + + int end = offset + count; + + Debug.Assert(0 <= end && end <= buffer.Length); + + if (outCipher == null) + { + stream.Write(buffer, offset, count); + return; + } + + byte[] data = outCipher.ProcessBytes(buffer, offset, count); + if (data != null) + { + stream.Write(data, 0, data.Length); + } + } + + public override void WriteByte( + byte b) + { + if (outCipher == null) + { + stream.WriteByte(b); + return; + } + + byte[] data = outCipher.ProcessByte(b); + if (data != null) + { + stream.Write(data, 0, data.Length); + } + } + + public override bool CanRead + { + get { return stream.CanRead && (inCipher != null); } + } + + public override bool CanWrite + { + get { return stream.CanWrite && (outCipher != null); } + } + + public override bool CanSeek + { + get { return false; } + } + + public sealed override long Length + { + get { throw new NotSupportedException(); } + } + + public sealed override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override void Close() + { + if (outCipher != null) + { + byte[] data = outCipher.DoFinal(); + stream.Write(data, 0, data.Length); + stream.Flush(); + } + stream.Close(); + } + + public override void Flush() + { + // Note: outCipher.DoFinal is only called during Close() + stream.Flush(); + } + + public sealed override long Seek( + long offset, + SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public sealed override void SetLength( + long length) + { + throw new NotSupportedException(); + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs b/MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs new file mode 100644 index 00000000..67f0d86f --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/CryptoException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class CryptoException + : Exception + { + public CryptoException() + { + } + + public CryptoException( + string message) + : base(message) + { + } + + public CryptoException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs b/MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs new file mode 100644 index 00000000..e9efc0bd --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/DataLengthException.cs @@ -0,0 +1,42 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * this exception is thrown if a buffer that is meant to have output + * copied into it turns out to be too short, or if we've been given + * insufficient input. In general this exception will Get thrown rather + * than an ArrayOutOfBounds exception. + */ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class DataLengthException + : CryptoException + { + /** + * base constructor. + */ + public DataLengthException() + { + } + + /** + * create a DataLengthException with the given message. + * + * @param message the message to be carried with the exception. + */ + public DataLengthException( + string message) + : base(message) + { + } + + public DataLengthException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs new file mode 100644 index 00000000..a3ad6d6e --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/IBlockCipher.cs @@ -0,0 +1,36 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// Base interface for a symmetric key block cipher. + public interface IBlockCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// Initialise the cipher. + /// Initialise for encryption if true, for decryption if false. + /// The key or other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); + + /// The block size for this cipher, in bytes. + int GetBlockSize(); + + /// Indicates whether this cipher can handle partial blocks. + bool IsPartialBlockOkay { get; } + + /// Process a block. + /// The input buffer. + /// The offset into inBuf that the input block begins. + /// The output buffer. + /// The offset into outBuf to write the output block. + /// If input block is wrong size, or outBuf too small. + /// The number of bytes processed and produced. + int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff); + + /// + /// Reset the cipher to the same state as it was after the last init (if there was one). + /// + void Reset(); + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs b/MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs new file mode 100644 index 00000000..69dec959 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/IBufferedCipher.cs @@ -0,0 +1,44 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// Block cipher engines are expected to conform to this interface. + public interface IBufferedCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// Initialise the cipher. + /// If true the cipher is initialised for encryption, + /// if false for decryption. + /// The key and other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); + + int GetBlockSize(); + + int GetOutputSize(int inputLen); + + int GetUpdateOutputSize(int inputLen); + + byte[] ProcessByte(byte input); + int ProcessByte(byte input, byte[] output, int outOff); + + byte[] ProcessBytes(byte[] input); + byte[] ProcessBytes(byte[] input, int inOff, int length); + int ProcessBytes(byte[] input, byte[] output, int outOff); + int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff); + + byte[] DoFinal(); + byte[] DoFinal(byte[] input); + byte[] DoFinal(byte[] input, int inOff, int length); + int DoFinal(byte[] output, int outOff); + int DoFinal(byte[] input, byte[] output, int outOff); + int DoFinal(byte[] input, int inOff, int length, byte[] output, int outOff); + + /// + /// Reset the cipher. After resetting the cipher is in the same state + /// as it was after the last init (if there was one). + /// + void Reset(); + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs b/MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs new file mode 100644 index 00000000..fff0941c --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/ICipherParameters.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * all parameter classes implement this. + */ + public interface ICipherParameters + { + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs b/MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs new file mode 100644 index 00000000..33dff96d --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/KeyParameter.cs @@ -0,0 +1,43 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class KeyParameter + : ICipherParameters + { + private readonly byte[] key; + + public KeyParameter( + byte[] key) + { + if (key == null) + throw new ArgumentNullException("key"); + + this.key = (byte[]) key.Clone(); + } + + public KeyParameter( + byte[] key, + int keyOff, + int keyLen) + { + if (key == null) + throw new ArgumentNullException("key"); + if (keyOff < 0 || keyOff > key.Length) + throw new ArgumentOutOfRangeException("keyOff"); + if (keyLen < 0 || (keyOff + keyLen) > key.Length) + throw new ArgumentOutOfRangeException("keyLen"); + + this.key = new byte[keyLen]; + Array.Copy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] GetKey() + { + return (byte[]) key.Clone(); + } + } + +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs b/MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs new file mode 100644 index 00000000..e1cf4492 --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/OutputLengthException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class OutputLengthException + : DataLengthException + { + public OutputLengthException() + { + } + + public OutputLengthException( + string message) + : base(message) + { + } + + public OutputLengthException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs b/MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs new file mode 100644 index 00000000..087cb7ce --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/Pack.cs @@ -0,0 +1,266 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + internal sealed class Pack + { + private Pack() + { + } + + internal static void UInt16_To_BE(ushort n, byte[] bs) + { + bs[0] = (byte)(n >> 8); + bs[1] = (byte)(n); + } + + internal static void UInt16_To_BE(ushort n, byte[] bs, int off) + { + bs[off] = (byte)(n >> 8); + bs[off + 1] = (byte)(n); + } + + internal static ushort BE_To_UInt16(byte[] bs) + { + uint n = (uint)bs[0] << 8 + | (uint)bs[1]; + return (ushort)n; + } + + internal static ushort BE_To_UInt16(byte[] bs, int off) + { + uint n = (uint)bs[off] << 8 + | (uint)bs[off + 1]; + return (ushort)n; + } + + internal static byte[] UInt32_To_BE(uint n) + { + byte[] bs = new byte[4]; + UInt32_To_BE(n, bs, 0); + return bs; + } + + internal static void UInt32_To_BE(uint n, byte[] bs) + { + bs[0] = (byte)(n >> 24); + bs[1] = (byte)(n >> 16); + bs[2] = (byte)(n >> 8); + bs[3] = (byte)(n); + } + + internal static void UInt32_To_BE(uint n, byte[] bs, int off) + { + bs[off] = (byte)(n >> 24); + bs[off + 1] = (byte)(n >> 16); + bs[off + 2] = (byte)(n >> 8); + bs[off + 3] = (byte)(n); + } + + internal static byte[] UInt32_To_BE(uint[] ns) + { + byte[] bs = new byte[4 * ns.Length]; + UInt32_To_BE(ns, bs, 0); + return bs; + } + + internal static void UInt32_To_BE(uint[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.Length; ++i) + { + UInt32_To_BE(ns[i], bs, off); + off += 4; + } + } + + internal static uint BE_To_UInt32(byte[] bs) + { + return (uint)bs[0] << 24 + | (uint)bs[1] << 16 + | (uint)bs[2] << 8 + | (uint)bs[3]; + } + + internal static uint BE_To_UInt32(byte[] bs, int off) + { + return (uint)bs[off] << 24 + | (uint)bs[off + 1] << 16 + | (uint)bs[off + 2] << 8 + | (uint)bs[off + 3]; + } + + internal static void BE_To_UInt32(byte[] bs, int off, uint[] ns) + { + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = BE_To_UInt32(bs, off); + off += 4; + } + } + + internal static byte[] UInt64_To_BE(ulong n) + { + byte[] bs = new byte[8]; + UInt64_To_BE(n, bs, 0); + return bs; + } + + internal static void UInt64_To_BE(ulong n, byte[] bs) + { + UInt32_To_BE((uint)(n >> 32), bs); + UInt32_To_BE((uint)(n), bs, 4); + } + + internal static void UInt64_To_BE(ulong n, byte[] bs, int off) + { + UInt32_To_BE((uint)(n >> 32), bs, off); + UInt32_To_BE((uint)(n), bs, off + 4); + } + + internal static ulong BE_To_UInt64(byte[] bs) + { + uint hi = BE_To_UInt32(bs); + uint lo = BE_To_UInt32(bs, 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static ulong BE_To_UInt64(byte[] bs, int off) + { + uint hi = BE_To_UInt32(bs, off); + uint lo = BE_To_UInt32(bs, off + 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static void UInt16_To_LE(ushort n, byte[] bs) + { + bs[0] = (byte)(n); + bs[1] = (byte)(n >> 8); + } + + internal static void UInt16_To_LE(ushort n, byte[] bs, int off) + { + bs[off] = (byte)(n); + bs[off + 1] = (byte)(n >> 8); + } + + internal static ushort LE_To_UInt16(byte[] bs) + { + uint n = (uint)bs[0] + | (uint)bs[1] << 8; + return (ushort)n; + } + + internal static ushort LE_To_UInt16(byte[] bs, int off) + { + uint n = (uint)bs[off] + | (uint)bs[off + 1] << 8; + return (ushort)n; + } + + internal static byte[] UInt32_To_LE(uint n) + { + byte[] bs = new byte[4]; + UInt32_To_LE(n, bs, 0); + return bs; + } + + internal static void UInt32_To_LE(uint n, byte[] bs) + { + bs[0] = (byte)(n); + bs[1] = (byte)(n >> 8); + bs[2] = (byte)(n >> 16); + bs[3] = (byte)(n >> 24); + } + + internal static void UInt32_To_LE(uint n, byte[] bs, int off) + { + bs[off] = (byte)(n); + bs[off + 1] = (byte)(n >> 8); + bs[off + 2] = (byte)(n >> 16); + bs[off + 3] = (byte)(n >> 24); + } + + internal static byte[] UInt32_To_LE(uint[] ns) + { + byte[] bs = new byte[4 * ns.Length]; + UInt32_To_LE(ns, bs, 0); + return bs; + } + + internal static void UInt32_To_LE(uint[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.Length; ++i) + { + UInt32_To_LE(ns[i], bs, off); + off += 4; + } + } + + internal static uint LE_To_UInt32(byte[] bs) + { + return (uint)bs[0] + | (uint)bs[1] << 8 + | (uint)bs[2] << 16 + | (uint)bs[3] << 24; + } + + internal static uint LE_To_UInt32(byte[] bs, int off) + { + return (uint)bs[off] + | (uint)bs[off + 1] << 8 + | (uint)bs[off + 2] << 16 + | (uint)bs[off + 3] << 24; + } + + internal static void LE_To_UInt32(byte[] bs, int off, uint[] ns) + { + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = LE_To_UInt32(bs, off); + off += 4; + } + } + + internal static void LE_To_UInt32(byte[] bs, int bOff, uint[] ns, int nOff, int count) + { + for (int i = 0; i < count; ++i) + { + ns[nOff + i] = LE_To_UInt32(bs, bOff); + bOff += 4; + } + } + + internal static byte[] UInt64_To_LE(ulong n) + { + byte[] bs = new byte[8]; + UInt64_To_LE(n, bs, 0); + return bs; + } + + internal static void UInt64_To_LE(ulong n, byte[] bs) + { + UInt32_To_LE((uint)(n), bs); + UInt32_To_LE((uint)(n >> 32), bs, 4); + } + + internal static void UInt64_To_LE(ulong n, byte[] bs, int off) + { + UInt32_To_LE((uint)(n), bs, off); + UInt32_To_LE((uint)(n >> 32), bs, off + 4); + } + + internal static ulong LE_To_UInt64(byte[] bs) + { + uint lo = LE_To_UInt32(bs); + uint hi = LE_To_UInt32(bs, 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static ulong LE_To_UInt64(byte[] bs, int off) + { + uint lo = LE_To_UInt32(bs, off); + uint hi = LE_To_UInt32(bs, off + 4); + return ((ulong)hi << 32) | (ulong)lo; + } + } +} diff --git a/MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs b/MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs new file mode 100644 index 00000000..11a8b77a --- /dev/null +++ b/MinecraftClient/Crypto/Streams/BouncyAes/ParametersWithIV.cs @@ -0,0 +1,43 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ParametersWithIV + : ICipherParameters + { + private readonly ICipherParameters parameters; + private readonly byte[] iv; + + public ParametersWithIV( + ICipherParameters parameters, + byte[] iv) + : this(parameters, iv, 0, iv.Length) + { + } + + public ParametersWithIV( + ICipherParameters parameters, + byte[] iv, + int ivOff, + int ivLen) + { + // NOTE: 'parameters' may be null to imply key re-use + if (iv == null) + throw new ArgumentNullException("iv"); + + this.parameters = parameters; + this.iv = new byte[ivLen]; + Array.Copy(iv, ivOff, this.iv, 0, ivLen); + } + + public byte[] GetIV() + { + return (byte[]) iv.Clone(); + } + + public ICipherParameters Parameters + { + get { return parameters; } + } + } +} diff --git a/MinecraftClient/Crypto/Streams/MonoAesStream.cs b/MinecraftClient/Crypto/Streams/MonoAesStream.cs index b7d2875f..430483b6 100644 --- a/MinecraftClient/Crypto/Streams/MonoAesStream.cs +++ b/MinecraftClient/Crypto/Streams/MonoAesStream.cs @@ -4,28 +4,29 @@ using System.Linq; using System.Text; using System.Security.Cryptography; using System.IO; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.IO; namespace MinecraftClient.Crypto.Streams { /// /// An encrypted stream using AES, used for encrypting network data on the fly using AES. - /// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks. - /// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible. + /// This is a mono-compatible adaptation which uses AES engine from the BouncyCastle project. /// public class MonoAesStream : Stream, IAesStream { IPaddingProvider pad; - ICryptoTransform enc; - ICryptoTransform dec; - List dec_cache = new List(); - List tosend_cache = new List(); + 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 @@ namespace MinecraftClient.Crypto.Streams public override int Read(byte[] buffer, int offset, int count) { - while (dec_cache.Count < count) - { - byte[] temp_in = new byte[16]; - byte[] temp_out = new byte[16]; - int read = 0; - while (read < 16) - read += BaseStream.Read(temp_in, read, 16 - read); - dec.TransformBlock(temp_in, 0, 16, temp_out, 0); - foreach (byte b in temp_out) - dec_cache.Add(b); - } - - for (int i = offset; i - offset < count; i++) - { - buffer[i] = dec_cache[0]; - dec_cache.RemoveAt(0); - } - - return count; + return cstream.Read(buffer, offset, count); } public override long Seek(long offset, System.IO.SeekOrigin origin) @@ -114,35 +97,13 @@ namespace MinecraftClient.Crypto.Streams public override void Write(byte[] buffer, int offset, int count) { - for (int i = offset; i - offset < count; i++) - tosend_cache.Add(buffer[i]); - - if (tosend_cache.Count < 16) - tosend_cache.AddRange(pad.getPaddingPacket()); - - while (tosend_cache.Count > 16) - { - byte[] temp_in = new byte[16]; - byte[] temp_out = new byte[16]; - for (int i = 0; i < 16; i++) - { - temp_in[i] = tosend_cache[0]; - tosend_cache.RemoveAt(0); - } - enc.TransformBlock(temp_in, 0, 16, temp_out, 0); - BaseStream.Write(temp_out, 0, 16); - } + cstream.Write(buffer, offset, count); } - private RijndaelManaged GenerateAES(byte[] key) + private BufferedBlockCipher GenerateAES(byte[] key, bool forEncryption) { - RijndaelManaged cipher = new RijndaelManaged(); - cipher.Mode = CipherMode.CFB; - cipher.Padding = PaddingMode.None; - cipher.KeySize = 128; - cipher.FeedbackSize = 8; - cipher.Key = key; - cipher.IV = key; + BufferedBlockCipher cipher = new BufferedBlockCipher(new CfbBlockCipher(new AesFastEngine(), 8)); + cipher.Init(forEncryption, new ParametersWithIV(new KeyParameter(key), key)); return cipher; } } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index c174cb1e..afbcb28b 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/131] 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 781b61d6..8716ffe3 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -305,8 +305,21 @@ namespace MinecraftClient public void OnTextReceived(string text) { ConsoleIO.WriteLineFormatted(text, false); - foreach (ChatBot bot in new List(bots)) - bot.GetText(text); + for (int i = 0; i < bots.Count; i++) + { + try + { + bots[i].GetText(text); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLineFormatted("§8GetText: Got error from " + bots[i].ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } } /// @@ -357,7 +370,7 @@ namespace MinecraftClient { 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/131] 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 b60a39ee..5cdff088 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -67,10 +67,13 @@ namespace MinecraftClient.ChatBots if (ConsoleIO.basicIO) //Using a GUI? Pass text as is. ConsoleIO.WriteLine(text.Replace(alert, "§c" + alert + "§r")); - else //Using Consome Prompt : Print text with alert highlighted + else //Using Console Prompt : Print text with alert highlighted { string[] splitted = text.Split(new string[] { alert }, StringSplitOptions.None); + ConsoleColor fore = Console.ForegroundColor; + ConsoleColor back = Console.BackgroundColor; + if (splitted.Length > 0) { Console.BackgroundColor = ConsoleColor.DarkGray; @@ -89,8 +92,8 @@ namespace MinecraftClient.ChatBots } } - 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 f73514a5..ac627c28 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -193,8 +193,6 @@ namespace MinecraftClient writing_lock = true; if (reading) { - ConsoleColor fore = Console.ForegroundColor; - ConsoleColor back = Console.BackgroundColor; string buf = buffer; string buf2 = buffer2; ClearLineAndBuffer(); @@ -208,8 +206,6 @@ namespace MinecraftClient } 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 @@ namespace MinecraftClient 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/131] 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 cad60365..10a0aa02 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -260,7 +260,7 @@ namespace MinecraftClient.Protocol 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/131] 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 8716ffe3..ebf99e75 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -149,7 +149,10 @@ namespace MinecraftClient 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 4ad3ae27..1cfdfa60 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -20,6 +20,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient else { Console.WriteLine("Failed to determine server version."); - OfflineCommandPrompt(); + if (Settings.interactiveMode && MinecraftVersionPrompt()) + { + Restart(); + return; + } } } else @@ -256,7 +279,7 @@ namespace MinecraftClient public static void OfflineCommandPrompt() { - if (!Settings.exitOnFailure && offlinePrompt == null) + if (Settings.interactiveMode && offlinePrompt == null) { offlinePrompt = new Thread(new ThreadStart(delegate { @@ -269,31 +292,33 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } } + /// + /// + /// + /// + + 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 10a0aa02..5141d16e 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -124,7 +124,7 @@ namespace MinecraftClient.Protocol } catch { - return -1; + return 0; } } } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 09e508f1..8746eae2 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -40,7 +40,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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/131] 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 8746eae2..e074da88 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -342,7 +342,7 @@ namespace MinecraftClient + "\r\n" + "language=en_GB\r\n" + "botowners=Player1,Player2,Player3\r\n" - + "consoletitle=%username% - Minecraft Console Client\r\n" + + "consoletitle=%username%@%serverip% - Minecraft Console Client\r\n" + "internalcmdchar=slash #use 'none', 'slash' or 'backslash'\r\n" + "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/131] 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 ebf99e75..25144ebd 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -89,6 +89,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient client.ReceiveBufferSize = 1024 * 1024; handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this); Console.WriteLine("Version is supported.\nLogging in..."); - - if (handler.Login()) + + try { - if (singlecommand) + if (handler.Login()) { - handler.SendChatMessage(command); - ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent."); - Thread.Sleep(5000); - handler.Disconnect(); - Thread.Sleep(1000); - } - else - { - foreach (ChatBot bot in scripts_on_hold) - bot.SetHandler(this); - bots.AddRange(scripts_on_hold); - scripts_on_hold.Clear(); - - Console.WriteLine("Server was successfully joined.\nType '" - + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) - + "quit' to leave the server."); - - cmdprompt = new Thread(new ThreadStart(CommandPrompt)); - cmdprompt.Name = "MCC Command prompt"; - cmdprompt.Start(); + if (singlecommand) + { + handler.SendChatMessage(command); + ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent."); + Thread.Sleep(5000); + handler.Disconnect(); + Thread.Sleep(1000); + } + else + { + foreach (ChatBot bot in scripts_on_hold) + bot.SetHandler(this); + bots.AddRange(scripts_on_hold); + scripts_on_hold.Clear(); + + Console.WriteLine("Server was successfully joined.\nType '" + + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + + "quit' to leave the server."); + + cmdprompt = new Thread(new ThreadStart(CommandPrompt)); + cmdprompt.Name = "MCC Command prompt"; + cmdprompt.Start(); + } } } + catch (Exception e) + { + ConsoleIO.WriteLineFormatted("§8" + e.Message); + Console.WriteLine("Failed to join this server."); + retry = true; + } } catch (SocketException) { 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 @@ namespace MinecraftClient 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 a1eb60f3..ae1ae240 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -458,7 +458,7 @@ namespace MinecraftClient.Protocol.Handlers 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 6ac63582..71b5faba 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -57,11 +57,15 @@ namespace MinecraftClient.Proxy } 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/131] 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 ac627c28..131768bd 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -193,26 +193,36 @@ namespace MinecraftClient 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/131] 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 1cfdfa60..80fa62b1 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -327,9 +327,9 @@ namespace MinecraftClient } /// - /// + /// 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 5141d16e..d33ef7e8 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -25,25 +25,31 @@ namespace MinecraftClient.Protocol 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)) + 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 if (Protocol17Handler.doPing(serverIP, serverPort, ref protocolversion)) + catch { - return true; - } - else - { - 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 @@ namespace MinecraftClient.Protocol 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/131] 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 5f2b9e2c..19d831f5 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -62,5 +62,15 @@ namespace MinecraftClient.ChatBots } 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 25144ebd..01d77dd7 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -151,8 +151,9 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } else if (!singlecommand && Settings.interactiveMode) { - Program.OfflineCommandPrompt(); + Program.HandleOfflineMode(); } } } @@ -369,7 +370,8 @@ namespace MinecraftClient 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 80fa62b1..4ece8838 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -169,24 +169,8 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } /// - /// 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 @@ namespace MinecraftClient /// /// 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 @@ namespace MinecraftClient 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 d33ef7e8..eb7231f0 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -38,9 +38,9 @@ namespace MinecraftClient.Protocol } 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 71b5faba..d18d4ea1 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -57,15 +57,11 @@ namespace MinecraftClient.Proxy } 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/131] 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 00000000..405187bb --- /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/131] 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 405187bb..00000000 --- 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/131] 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 131768bd..463d309e 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 01d77dd7..cf14046f 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -167,7 +167,7 @@ namespace MinecraftClient } else if (!singlecommand && Settings.interactiveMode) { - Program.HandleOfflineMode(); + Program.HandleFailure(); } } } @@ -371,7 +371,7 @@ namespace MinecraftClient will_restart |= bot.OnDisconnect(reason, message); if (!will_restart) - Program.HandleOfflineMode(); + Program.HandleFailure(); } /// diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 4ece8838..399e2f14 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -167,11 +167,7 @@ namespace MinecraftClient { 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } /// - /// 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 + ConsoleIO.Reset(); + Console.WriteLine(errorMessage); + + if (disconnectReason.HasValue) { - 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(); + 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/131] 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 399e2f14..0794b765 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -167,7 +167,10 @@ namespace MinecraftClient { 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 @@ namespace MinecraftClient + '\n' + "mozroots --import --ask-remove"); return; } - while (Console.KeyAvailable) { Console.ReadKey(false); } HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); } } @@ -258,6 +260,8 @@ namespace MinecraftClient 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/131] 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 463d309e..9f6f18ab 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 @@ namespace MinecraftClient { if (basicIO) { return Console.ReadLine(); } ConsoleKeyInfo k = new ConsoleKeyInfo(); - Console.Write('>'); - reading = true; - buffer = ""; - buffer2 = ""; + + lock (io_lock) + { + Console.Write('>'); + reading = true; + buffer = ""; + buffer2 = ""; + } while (k.Key != ConsoleKey.Enter) { k = Console.ReadKey(true); - while (writing_lock) { } - reading_lock = true; + lock (io_lock) + { if (k.Key == ConsoleKey.V && k.Modifiers == ConsoleModifiers.Control) { string clip = ReadClipboard(); @@ -173,13 +191,16 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient { //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 @@ namespace MinecraftClient case 'd': Console.ForegroundColor = ConsoleColor.Magenta; break; case 'e': Console.ForegroundColor = ConsoleColor.Yellow; break; case 'f': Console.ForegroundColor = ConsoleColor.White; break; - case 'r': Console.ForegroundColor = ConsoleColor.White; break; + case 'r': Console.ForegroundColor = ConsoleColor.Gray; break; } if (subs[i].Length > 1) @@ -387,6 +410,18 @@ namespace MinecraftClient return clipdata; } #endregion + + #region AutoComplete API + /// + /// Set an auto-completion engine for TAB autocompletion + /// + /// Engine implementing the IAutoComplete interface + + public static void SetAutoCompleteEngine(IAutoComplete engine) + { + autocomplete_engine = engine; + } + #endregion } /// From 33b84584aa4299fe505aa65728d689becb76f631 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 10 May 2015 18:59:00 +0200 Subject: [PATCH 018/131] 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 9f6f18ab..eee3d3d8 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -110,87 +110,87 @@ namespace MinecraftClient 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(); + string clip = ReadClipboard(); + foreach (char c in clip) + AddChar(c); + } + else + { + switch (k.Key) + { + case ConsoleKey.Escape: + ClearLineAndBuffer(); + break; + case ConsoleKey.Backspace: RemoveOneChar(); - } - break; - case ConsoleKey.Oem6: - break; - case ConsoleKey.DownArrow: - if (previous.Count > 0) - { - ClearLineAndBuffer(); - buffer = previous.First.Value; - previous.AddLast(buffer); - previous.RemoveFirst(); - Console.Write(buffer); - } - break; - case ConsoleKey.UpArrow: - if (previous.Count > 0) - { - ClearLineAndBuffer(); - buffer = previous.Last.Value; - previous.AddFirst(buffer); - previous.RemoveLast(); - Console.Write(buffer); - } - break; - case ConsoleKey.Tab: - if (autocomplete_engine != null && buffer.Length > 0) - { - string[] tmp = buffer.Split(' '); - if (tmp.Length > 0) + break; + case ConsoleKey.Enter: + Console.Write('\n'); + break; + case ConsoleKey.LeftArrow: + GoLeft(); + break; + case ConsoleKey.RightArrow: + GoRight(); + break; + case ConsoleKey.Home: + while (buffer.Length > 0) { GoLeft(); } + break; + case ConsoleKey.End: + while (buffer2.Length > 0) { GoRight(); } + break; + case ConsoleKey.Delete: + if (buffer2.Length > 0) { - string word_tocomplete = tmp[tmp.Length - 1]; - string word_autocomplete = autocomplete_engine.AutoComplete(buffer); - if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete) + GoRight(); + RemoveOneChar(); + } + break; + case ConsoleKey.Oem6: + break; + case ConsoleKey.DownArrow: + if (previous.Count > 0) + { + ClearLineAndBuffer(); + buffer = previous.First.Value; + previous.AddLast(buffer); + previous.RemoveFirst(); + Console.Write(buffer); + } + break; + case ConsoleKey.UpArrow: + if (previous.Count > 0) + { + ClearLineAndBuffer(); + buffer = previous.Last.Value; + previous.AddFirst(buffer); + previous.RemoveLast(); + Console.Write(buffer); + } + break; + case ConsoleKey.Tab: + if (autocomplete_engine != null && buffer.Length > 0) + { + string[] 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 @@ namespace MinecraftClient { 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--; + try + { + string buf = buffer; + string buf2 = buffer2; + ClearLineAndBuffer(); + if (Console.CursorLeft == 0) + { + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; + Console.Write(' '); + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; + } + else Console.Write("\b \b"); + Console.Write(text); + buffer = buf; + buffer2 = buf2; + Console.Write(">" + buffer); + if (buffer2.Length > 0) + { + Console.Write(buffer2 + " \b"); + for (int i = 0; i < buffer2.Length; i++) { GoBack(); } + } + } + catch (ArgumentOutOfRangeException) + { + //Console resized: Try again + Console.Write('\n'); + Write(text); + } } - else Console.Write("\b \b"); - Console.Write(text); - buffer = buf; - buffer2 = buf2; - Console.Write(">" + buffer); - if (buffer2.Length > 0) - { - Console.Write(buffer2 + " \b"); - for (int i = 0; i < buffer2.Length; i++) { GoBack(); } - } - } - catch (ArgumentOutOfRangeException) - { - //Console resized: Try again - Console.Write('\n'); - Write(text); - } - } - else Console.Write(text); + else Console.Write(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/131] 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 eee3d3d8..1043748b 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -17,13 +17,13 @@ namespace MinecraftClient 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 0794b765..f33790f4 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -28,7 +28,7 @@ namespace MinecraftClient 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 eb7231f0..9c4f3a2d 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -117,6 +117,7 @@ namespace MinecraftClient.Protocol 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/131] 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 ae1ae240..760f398a 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -282,7 +282,14 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 1043748b..90c714b8 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -321,9 +321,9 @@ namespace MinecraftClient } } } + 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 df86f8a4..7526ea44 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -169,25 +169,43 @@ namespace MinecraftClient.Protocol.Handlers if (!init) { InitRules(); init = true; } if (TranslationRules.ContainsKey(rulename)) { - if ((TranslationRules[rulename].IndexOf("%1$s") >= 0 && TranslationRules[rulename].IndexOf("%2$s") >= 0) - && (TranslationRules[rulename].IndexOf("%1$s") > TranslationRules[rulename].IndexOf("%2$s"))) + int using_idx = 0; + string rule = TranslationRules[rulename]; + StringBuilder result = new StringBuilder(); + for (int i = 0; i < rule.Length; i++) { - while (using_data.Count < 2) { using_data.Add(""); } - string tmp = using_data[0]; - using_data[0] = using_data[1]; - using_data[1] = tmp; + if (rule[i] == '%' && i + 1 < rule.Length) + { + //Using string or int with %s or %d + if (rule[i + 1] == 's' || rule[i + 1] == 'd') + { + if (using_data.Count > using_idx) + { + result.Append(using_data[using_idx]); + using_idx++; + i += 1; + continue; + } + } + + //Using specified string or int with %1$s, %2$s... + else if (char.IsDigit(rule[i + 1]) + && i + 3 < rule.Length && rule[i + 2] == '$' + && (rule[i + 3] == 's' || rule[i + 3] == 'd')) + { + int specified_idx = rule[i + 1] - '1'; + if (using_data.Count > specified_idx) + { + result.Append(using_data[specified_idx]); + using_idx++; + i += 3; + continue; + } + } + } + result.Append(rule[i]); } - string[] syntax = TranslationRules[rulename].Split(new string[] { "%s", "%d", "%1$s", "%2$s" }, StringSplitOptions.None); - while (using_data.Count < syntax.Length - 1) { using_data.Add(""); } - string[] using_array = using_data.ToArray(); - string translated = ""; - for (int i = 0; i < syntax.Length - 1; i++) - { - translated += syntax[i]; - translated += using_array[i]; - } - translated += syntax[syntax.Length - 1]; - return translated; + return result.ToString(); } else return "[" + rulename + "] " + String.Join(" ", using_data); } From 5b662e2d07530938336a7fb67f2619913a4ebb9e Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 18 May 2015 16:15:58 +0200 Subject: [PATCH 022/131] 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 d591c419..a3ae5ebb 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient /// /// 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 @@ namespace MinecraftClient } /// - /// 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 90c714b8..dd488d52 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -284,7 +284,7 @@ namespace MinecraftClient if (Settings.chatTimeStamps) { int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second; - ConsoleIO.Write(hour.ToString("00") + ':' + minute.ToString("00") + ':' + second.ToString("00") + ' '); + ConsoleIO.Write(String.Format("{0}:{1}:{2} ", hour.ToString("00"), minute.ToString("00"), second.ToString("00"))); } if (!acceptnewlines) { str = str.Replace('\n', ' '); } if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(str); return; } From 43fa3fb4b47206696416163b984585b170577b5a Mon Sep 17 00:00:00 2001 From: Bancey Date: Tue, 19 May 2015 15:36:20 +0100 Subject: [PATCH 023/131] 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 00000000..c861241e --- /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 cf14046f..e3f792e1 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -20,7 +20,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient onlinePlayers[uuid] = name; } } - + /// /// Triggered when a player has left the game /// diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index afbcb28b..9ae1ccdc 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -74,6 +74,7 @@ + diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index e074da88..d66bb17e 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -21,7 +21,7 @@ namespace MinecraftClient public static string Password = ""; public static string ServerIP = ""; public static ushort ServerPort = 25565; - public static string ServerVersion = ""; + public static string ServerVersion = ""; public static string SingleCommand = ""; public static string ConsoleTitle = ""; @@ -88,12 +88,17 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient Servers[server_data[0]] = new KeyValuePair(ServerIP, ServerPort); } - + //Restore current server info ServerIP = server_host_temp; ServerPort = server_port_temp; @@ -284,7 +290,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "[RemoteControl]\r\n" + "enabled=false\r\n" + "autotpaccept=true\r\n" - + "tpaccepteveryone=false\r\n", Encoding.UTF8); + + "tpaccepteveryone=false\r\n" + + "\r\n" + + "[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 @@ namespace MinecraftClient string[] sip = server.Split(':'); string host = sip[0]; ushort port = 25565; - + if (sip.Length > 1) { try @@ -458,7 +480,7 @@ namespace MinecraftClient 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/131] 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 cf14046f..9db2dc2b 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -420,6 +420,8 @@ namespace MinecraftClient { handler.SendChatMessage(text.Substring(0, 100)); text = text.Substring(100, text.Length - 100); + if (Settings.splitMessageDelay.TotalSeconds > 0) + Thread.Sleep(Settings.splitMessageDelay); } return handler.SendChatMessage(text); } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index e074da88..72fbc1b7 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -37,6 +37,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 a3ae5ebb..579430cc 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -380,5 +380,29 @@ namespace MinecraftClient 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 5cdff088..cd21bae4 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -14,36 +14,13 @@ namespace MinecraftClient.ChatBots 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 c861241e..3a0d6575 100644 --- a/MinecraftClient/ChatBots/Auto Respond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -6,34 +6,16 @@ using System.IO; 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 6a991d96..3f152002 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -106,7 +106,7 @@ namespace MinecraftClient 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 9ae1ccdc..8129d2ec 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/131] 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 9811ca28..496dcea7 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/131] 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 760f398a..362bbd41 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -146,19 +146,22 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 3a0d6575..52a14944 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] "; - //Initalize the bot + /// + /// 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; + } + } + + /// + /// 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 3f152002..46b40c59 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -106,7 +106,7 @@ namespace MinecraftClient 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 4c4779a7..b7daa524 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -90,16 +90,15 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 362bbd41..432425f2 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -615,16 +615,12 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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/131] 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 a514ffc5..b8950cc7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -508,18 +508,12 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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/131] 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 f33790f4..6fc954de 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -28,7 +28,7 @@ namespace MinecraftClient 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 740342f5..935ad3cd 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -670,7 +670,7 @@ namespace MinecraftClient.Protocol.Handlers { 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 9c4f3a2d..7262da16 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -36,7 +36,7 @@ namespace MinecraftClient.Protocol { 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 @@ namespace MinecraftClient.Protocol 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/131] 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 8129d2ec..a530522c 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 7526ea44..937cb72b 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -19,31 +19,7 @@ namespace MinecraftClient.Protocol.Handlers public static string ParseText(string json) { - int cursorpos = 0; - JSONData jsonData = String2Data(json, ref cursorpos); - return JSONData2String(jsonData, ""); - } - - /// - /// An internal class to store unserialized JSON data - /// The data can be an object, an array or a string - /// - - private class JSONData - { - public enum DataType { Object, Array, String }; - private DataType type; - public DataType Type { get { return type; } } - public Dictionary Properties; - public List DataArray; - public string StringValue; - public JSONData(DataType datatype) - { - type = datatype; - Properties = new Dictionary(); - DataArray = new List(); - StringValue = String.Empty; - } + return JSONData2String(Json.ParseJson(json), ""); } /// @@ -210,135 +186,27 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers data.Properties["with"] = data.Properties["using"]; if (data.Properties.ContainsKey("with")) { - JSONData[] array = data.Properties["with"].DataArray.ToArray(); + Json.JSONData[] array = data.Properties["with"].DataArray.ToArray(); for (int i = 0; i < array.Length; i++) { using_data.Add(JSONData2String(array[i], colorcode)); @@ -362,29 +230,21 @@ namespace MinecraftClient.Protocol.Handlers } else return extra_result; - case JSONData.DataType.Array: + case Json.JSONData.DataType.Array: string result = ""; - foreach (JSONData item in data.DataArray) + foreach (Json.JSONData item in data.DataArray) { result += JSONData2String(item, colorcode); } return result; - case JSONData.DataType.String: + case Json.JSONData.DataType.String: return colorcode + data.StringValue; } return ""; } - /// - /// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f) - /// - /// Char to test - /// True if hexadecimal - - private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); } - /// /// Do a HTTP request to get a webpage or text data from a server file /// diff --git a/MinecraftClient/Protocol/Handlers/Json.cs b/MinecraftClient/Protocol/Handlers/Json.cs new file mode 100644 index 00000000..c95fdc89 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/Json.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Protocol.Handlers +{ + /// + /// This class parses JSON data and returns an object describing that data. + /// Really lightweight JSON handling by ORelio - (c) 2013 - 2014 + /// + + static class Json + { + /// + /// Parse some JSON and return the corresponding JSON object + /// + + public static JSONData ParseJson(string json) + { + int cursorpos = 0; + return String2Data(json, ref cursorpos); + } + + /// + /// The class storing unserialized JSON data + /// The data can be an object, an array or a string + /// + + public class JSONData + { + public enum DataType { Object, Array, String }; + private DataType type; + public DataType Type { get { return type; } } + public Dictionary Properties; + public List DataArray; + public string StringValue; + public JSONData(DataType datatype) + { + type = datatype; + Properties = new Dictionary(); + DataArray = new List(); + StringValue = String.Empty; + } + } + + /// + /// Parse a JSON string to build a JSON object + /// + /// String to parse + /// Cursor start (set to 0 for function init) + + private static JSONData String2Data(string toparse, ref int cursorpos) + { + try + { + JSONData data; + switch (toparse[cursorpos]) + { + //Object + case '{': + data = new JSONData(JSONData.DataType.Object); + cursorpos++; + while (toparse[cursorpos] != '}') + { + if (toparse[cursorpos] == '"') + { + JSONData propertyname = String2Data(toparse, ref cursorpos); + if (toparse[cursorpos] == ':') { cursorpos++; } else { /* parse error ? */ } + JSONData propertyData = String2Data(toparse, ref cursorpos); + data.Properties[propertyname.StringValue] = propertyData; + } + else cursorpos++; + } + cursorpos++; + break; + + //Array + case '[': + data = new JSONData(JSONData.DataType.Array); + cursorpos++; + while (toparse[cursorpos] != ']') + { + if (toparse[cursorpos] == ',') { cursorpos++; } + JSONData arrayItem = String2Data(toparse, ref cursorpos); + data.DataArray.Add(arrayItem); + } + cursorpos++; + break; + + //String + case '"': + data = new JSONData(JSONData.DataType.String); + cursorpos++; + while (toparse[cursorpos] != '"') + { + if (toparse[cursorpos] == '\\') + { + try //Unicode character \u0123 + { + if (toparse[cursorpos + 1] == 'u' + && isHex(toparse[cursorpos + 2]) + && isHex(toparse[cursorpos + 3]) + && isHex(toparse[cursorpos + 4]) + && isHex(toparse[cursorpos + 5])) + { + //"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string + data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber)); + cursorpos += 6; continue; + } + else cursorpos++; //Normal character escapement \" + } + catch (IndexOutOfRangeException) { cursorpos++; } // \u01 + catch (ArgumentOutOfRangeException) { cursorpos++; } // Unicode index 0123 was invalid + } + data.StringValue += toparse[cursorpos]; + cursorpos++; + } + cursorpos++; + break; + + //Number + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': + data = new JSONData(JSONData.DataType.String); + StringBuilder sb = new StringBuilder(); + while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.') + { + sb.Append(toparse[cursorpos]); + cursorpos++; + } + data.StringValue = sb.ToString(); + break; + + //Boolean : true + case 't': + data = new JSONData(JSONData.DataType.String); + cursorpos++; + if (toparse[cursorpos] == 'r') { cursorpos++; } + if (toparse[cursorpos] == 'u') { cursorpos++; } + if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "true"; } + break; + + //Boolean : false + case 'f': + data = new JSONData(JSONData.DataType.String); + cursorpos++; + if (toparse[cursorpos] == 'a') { cursorpos++; } + if (toparse[cursorpos] == 'l') { cursorpos++; } + if (toparse[cursorpos] == 's') { cursorpos++; } + if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "false"; } + break; + + //Unknown data + default: + cursorpos++; + return String2Data(toparse, ref cursorpos); + } + while (cursorpos < toparse.Length + && (char.IsWhiteSpace(toparse[cursorpos]) + || toparse[cursorpos] == '\r' + || toparse[cursorpos] == '\n')) + cursorpos++; + return data; + } + catch (IndexOutOfRangeException) + { + return new JSONData(JSONData.DataType.String); + } + } + + /// + /// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f) + /// + /// Char to test + /// True if hexadecimal + + private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); } + } +} diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index b8950cc7..d3215068 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -580,18 +580,21 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 7d31e162..ae46ccf5 100644 --- a/MinecraftClient/Crypto/CryptoHandler.cs +++ b/MinecraftClient/Crypto/CryptoHandler.cs @@ -197,14 +197,13 @@ namespace MinecraftClient.Crypto /// /// Stream to encrypt /// Key to use - /// Padding provider for Mono implementation /// Return an appropriate stream depending on the framework being used - public static IAesStream getAesStream(Stream underlyingStream, byte[] AesKey, IPaddingProvider paddingProvider) + public static IAesStream getAesStream(Stream underlyingStream, byte[] AesKey) { if (Program.isUsingMono) { - return new Streams.MonoAesStream(underlyingStream, AesKey, paddingProvider); + return new Streams.MonoAesStream(underlyingStream, AesKey); } else return new Streams.RegularAesStream(underlyingStream, AesKey); } diff --git a/MinecraftClient/Crypto/IPaddingProvider.cs b/MinecraftClient/Crypto/IPaddingProvider.cs deleted file mode 100644 index 48290494..00000000 --- a/MinecraftClient/Crypto/IPaddingProvider.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace MinecraftClient.Crypto -{ - /// - /// Interface for padding provider - /// Allow to get a padding plugin message from the current network protocol implementation. - /// - - public interface IPaddingProvider - { - byte[] getPaddingPacket(); - } -} diff --git a/MinecraftClient/Crypto/Streams/MonoAesStream.cs b/MinecraftClient/Crypto/Streams/MonoAesStream.cs index 430483b6..57d5598e 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 a530522c..0ef5d9ec 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 935ad3cd..38686d17 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -42,19 +42,11 @@ namespace MinecraftClient.Protocol.Handlers private void Updater() { - int keep_alive_interval = 100; - int keep_alive_timer = 100; try { do { Thread.Sleep(100); - keep_alive_timer--; - if (keep_alive_timer <= 0) - { - Send(getPaddingPacket()); - keep_alive_timer = keep_alive_interval; - } } while (Update()); } @@ -504,7 +496,7 @@ namespace MinecraftClient.Protocol.Handlers if (pid[0] == 0xFC) { readData(4); - s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this); + s = CryptoHandler.getAesStream(c.GetStream(), secretKey); encrypted = true; return true; } @@ -652,18 +644,6 @@ namespace MinecraftClient.Protocol.Handlers return result.ToArray(); } - public byte[] getPaddingPacket() - { - //Will generate a 15-bytes long padding packet - byte[] id = new byte[1] { 0xFA }; //Plugin Message - byte[] channel_name = Encoding.BigEndianUnicode.GetBytes("MCC|"); - byte[] channel_name_len = BitConverter.GetBytes((short)channel_name.Length); Array.Reverse(channel_name_len); - byte[] data = new byte[] { 0x00, 0x00 }; - byte[] data_len = BitConverter.GetBytes((short)data.Length); Array.Reverse(data_len); - byte[] packet_data = concatBytes(id, channel_name_len, channel_name, data_len, data); - return packet_data; - } - public static bool doPing(string host, int port, ref int protocolversion) { try diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index d3215068..b7c08e37 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -45,19 +45,11 @@ namespace MinecraftClient.Protocol.Handlers private void Updater() { - int keep_alive_interval = 100; - int keep_alive_timer = 100; try { do { Thread.Sleep(100); - keep_alive_timer--; - if (keep_alive_timer <= 0) - { - Send(getPaddingPacket()); - keep_alive_timer = keep_alive_interval; - } } while (Update()); } @@ -431,7 +423,7 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 432425f2..67fb96ca 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -46,19 +46,11 @@ namespace MinecraftClient.Protocol.Handlers private void Updater() { - int keep_alive_interval = 100; - int keep_alive_timer = 100; try { do { Thread.Sleep(100); - keep_alive_timer--; - if (keep_alive_timer <= 0) - { - SendRAW(getPaddingPacket()); - keep_alive_timer = keep_alive_interval; - } } while (Update()); } @@ -531,7 +523,7 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers } } - /// - /// Useless padding packet for solving Mono issue. - /// - /// The padding packet - - public byte[] getPaddingPacket() - { - //Will generate a 15-bytes long padding packet - byte[] compression = compression_treshold >= 0 ? getVarInt(0) : new byte[] { }; - byte[] id = getVarInt(0x17); //Plugin Message - byte[] channel_name = Encoding.UTF8.GetBytes("MCC|Pad"); - byte[] channel_name_len = getVarInt(channel_name.Length); - byte[] data = compression_treshold >= 0 ? new byte[] { 0x00, 0x00, 0x00 } : new byte[] { 0x00, 0x00, 0x00, 0x00 }; - byte[] data_len = getVarInt(data.Length); - byte[] packet_data = concatBytes(compression, id, channel_name_len, channel_name, data_len, data); - byte[] packet_length = getVarInt(packet_data.Length); - return concatBytes(packet_length, packet_data); - } - /// /// Send a chat message to the server /// diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index d35a1fd7..2ae82c14 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/131] 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 | 173 ++++++++++++++---- 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, 321 insertions(+), 135 deletions(-) create mode 100644 MinecraftClient/config/sample-script.cs diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 579430cc..59fff3cd 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; +using System.Threading; namespace MinecraftClient { @@ -33,8 +34,11 @@ namespace MinecraftClient public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost }; //Will be automatically set on bot loading, don't worry about this - public void SetHandler(McTcpClient handler) { this.handler = handler; } - private McTcpClient handler; + 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 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient /// May contain a confirmation or error message after processing the command, or "" otherwise. /// TRUE if the command was indeed an internal MCC command - 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 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient } 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient { 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 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient /// /// 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient protected void UnloadBot() { - handler.BotUnLoad(this); + Handler.BotUnLoad(this); } /// @@ -362,14 +368,14 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 cd21bae4..9ad12b2b 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -30,7 +30,7 @@ namespace MinecraftClient.ChatBots public override void GetText(string text) { //Remove color codes and convert to lowercase - text = getVerbatim(text).ToLower(); + text = GetVerbatim(text).ToLower(); //Proceed only if no exclusions are found in text if (!excludelist.Any(exclusion => text.Contains(exclusion))) diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs index 19d831f5..d98e30d5 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -47,7 +47,7 @@ namespace MinecraftClient.ChatBots public override bool OnDisconnect(DisconnectReason reason, string message) { - message = getVerbatim(message); + message = GetVerbatim(message); string comp = message.ToLower(); foreach (string msg in dictionary) { diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 52a14944..567b38b0 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -182,14 +182,14 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots { 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 7d4acc07..54468253 100644 --- a/MinecraftClient/ChatBots/ChatLog.cs +++ b/MinecraftClient/ChatBots/ChatLog.cs @@ -69,15 +69,15 @@ namespace MinecraftClient.ChatBots public override void GetText(string text) { - text = getVerbatim(text); + text = GetVerbatim(text); string sender = ""; string message = ""; - if (saveChat && isChatMessage(text, ref message, ref sender)) + if (saveChat && IsChatMessage(text, ref message, ref sender)) { save("Chat " + sender + ": " + message); } - else if (savePrivate && isPrivateMessage(text, ref message, ref sender)) + else if (savePrivate && IsPrivateMessage(text, ref message, ref sender)) { save("Private " + sender + ": " + message); } @@ -90,7 +90,7 @@ namespace MinecraftClient.ChatBots private void save(string tosave) { if (dateandtime) - tosave = getTimestamp() + ' ' + tosave; + tosave = GetTimestamp() + ' ' + tosave; string directory = Path.GetDirectoryName(logfile); if (!String.IsNullOrEmpty(directory) && !Directory.Exists(directory)) diff --git a/MinecraftClient/ChatBots/HangmanGame.cs b/MinecraftClient/ChatBots/HangmanGame.cs index 9b11cc2c..0ea3c2ea 100644 --- a/MinecraftClient/ChatBots/HangmanGame.cs +++ b/MinecraftClient/ChatBots/HangmanGame.cs @@ -52,9 +52,9 @@ namespace MinecraftClient.ChatBots { string message = ""; string username = ""; - text = getVerbatim(text); + text = GetVerbatim(text); - if (isPrivateMessage(text, ref message, ref username)) + if (IsPrivateMessage(text, ref message, ref username)) { if (Settings.Bots_Owners.Contains(username.ToLower())) { @@ -73,7 +73,7 @@ namespace MinecraftClient.ChatBots } else { - if (running && isChatMessage(text, ref message, ref username)) + if (running && IsChatMessage(text, ref message, ref username)) { if (message.Length == 1) { diff --git a/MinecraftClient/ChatBots/PlayerListLogger.cs b/MinecraftClient/ChatBots/PlayerListLogger.cs index 9124b664..1d1c5cb9 100644 --- a/MinecraftClient/ChatBots/PlayerListLogger.cs +++ b/MinecraftClient/ChatBots/PlayerListLogger.cs @@ -46,7 +46,7 @@ namespace MinecraftClient.ChatBots LogToConsole("Saving Player List"); DateTime now = DateTime.Now; string TimeStamp = "[" + now.Year + '/' + now.Month + '/' + now.Day + ' ' + now.Hour + ':' + now.Minute + ']'; - System.IO.File.AppendAllText(file, TimeStamp + "\n" + getVerbatim(text) + "\n\n"); + System.IO.File.AppendAllText(file, TimeStamp + "\n" + GetVerbatim(text) + "\n\n"); } } } diff --git a/MinecraftClient/ChatBots/RemoteControl.cs b/MinecraftClient/ChatBots/RemoteControl.cs index dfdf5d2a..300beb02 100644 --- a/MinecraftClient/ChatBots/RemoteControl.cs +++ b/MinecraftClient/ChatBots/RemoteControl.cs @@ -13,19 +13,19 @@ namespace MinecraftClient.ChatBots { public override void GetText(string text) { - text = getVerbatim(text); + text = GetVerbatim(text); string command = "", sender = ""; - if (isPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower().Trim())) + if (IsPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower().Trim())) { string response = ""; - performInternalCommand(command, ref response); + PerformInternalCommand(command, ref response); if (response.Length > 0) { SendPrivateMessage(sender, response); } } else if (Settings.RemoteCtrl_AutoTpaccept - && isTeleportRequest(text, ref sender) + && IsTeleportRequest(text, ref sender) && (Settings.RemoteCtrl_AutoTpaccept_Everyone || Settings.Bots_Owners.Contains(sender.ToLower().Trim()))) { SendText("/tpaccept"); diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs index cb96ffa8..44ae5891 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 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots { filename, filename + ".txt", + filename + ".cs", "scripts" + dir_slash + filename, "scripts" + dir_slash + filename + ".txt", + "scripts" + dir_slash + filename + ".cs", "config" + dir_slash + filename, "config" + dir_slash + filename + ".txt", + "config" + dir_slash + filename + ".cs", }; foreach (string possible_file in files) @@ -62,63 +70,152 @@ namespace MinecraftClient.ChatBots if (lookForScript(ref file)) { lines = System.IO.File.ReadAllLines(file); - if (owner != null) { SendPrivateMessage(owner, "Script '" + file + "' loaded."); } + csharp = file.EndsWith(".cs"); + thread = null; + + if (owner != null) + SendPrivateMessage(owner, "Script '" + file + "' loaded."); } else { LogToConsole("File not found: '" + file + "'"); + if (owner != null) SendPrivateMessage(owner, "File not found: '" + file + "'"); + UnloadBot(); //No need to keep the bot active } } public override void Update() { - if (sleepticks > 0) { sleepticks--; } - else + if (csharp) //C# compiled script { - if (nextline < lines.Length) //Is there an instruction left to interpret? + //Initialize thread on first update + if (thread == null) { - string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line - nextline++; //Move the cursor so that the next time the following line will be interpreted - sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming - - if (instruction_line.Length > 1) + tpause = new ManualResetEvent(false); + thread = new Thread(() => { - if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/') - { - instruction_line = Settings.expandVars(instruction_line); - string instruction_name = instruction_line.Split(' ')[0]; - switch (instruction_name.ToLower()) - { - case "wait": - int ticks = 10; - try - { - ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5)); - } - catch { } - sleepticks = ticks; - break; - default: - if (!performInternalCommand(instruction_line)) - { - sleepticks = 0; Update(); //Unknown command : process next line immediately - } - else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); } - break; - } - } - else { sleepticks = 0; Update(); } //Comment: process next line immediately - } + if (!RunCSharpScript(String.Join("\n", lines), file, tpause) && owner != null) + SendPrivateMessage(owner, "Script '" + file + "' failed to run."); + }); + thread.Start(); } + + //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 { - //No more instructions to interpret - UnloadBot(); + if (nextline < lines.Length) //Is there an instruction left to interpret? + { + string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line + nextline++; //Move the cursor so that the next time the following line will be interpreted + + if (instruction_line.Length > 1) + { + if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/') + { + instruction_line = Settings.ExpandVars(instruction_line); + string instruction_name = instruction_line.Split(' ')[0]; + switch (instruction_name.ToLower()) + { + case "wait": + int ticks = 10; + try + { + ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5)); + } + catch { } + sleepticks = ticks; + break; + default: + if (!PerformInternalCommand(instruction_line)) + { + Update(); //Unknown command : process next line immediately + } + else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); } + break; + } + } + else { Update(); } //Comment: process next line immediately + } + } + else + { + //No more instructions to interpret + UnloadBot(); + } } } } + + 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 ffd921f8..ce6ef8f7 100644 --- a/MinecraftClient/ChatBots/TestBot.cs +++ b/MinecraftClient/ChatBots/TestBot.cs @@ -15,13 +15,13 @@ namespace MinecraftClient.ChatBots { string message = ""; string username = ""; - text = getVerbatim(text); + text = GetVerbatim(text); - if (isPrivateMessage(text, ref message, ref username)) + if (IsPrivateMessage(text, ref message, ref username)) { ConsoleIO.WriteLine("Bot: " + username + " told me : " + message); } - else if (isChatMessage(text, ref message, ref username)) + else if (IsChatMessage(text, ref message, ref username)) { ConsoleIO.WriteLine("Bot: " + username + " said : " + message); } diff --git a/MinecraftClient/Commands/Connect.cs b/MinecraftClient/Commands/Connect.cs index 3d57dbd3..4e92ecc9 100644 --- a/MinecraftClient/Commands/Connect.cs +++ b/MinecraftClient/Commands/Connect.cs @@ -17,13 +17,13 @@ namespace MinecraftClient.Commands string[] args = getArgs(command); if (args.Length > 1) { - if (!Settings.setAccount(args[1])) + if (!Settings.SetAccount(args[1])) { return "Unknown account '" + args[1] + "'."; } } - if (Settings.setServerIP(args[0])) + if (Settings.SetServerIP(args[0])) { Program.Restart(); return ""; diff --git a/MinecraftClient/Commands/List.cs b/MinecraftClient/Commands/List.cs index 116085d0..da9368e0 100644 --- a/MinecraftClient/Commands/List.cs +++ b/MinecraftClient/Commands/List.cs @@ -12,7 +12,7 @@ namespace MinecraftClient.Commands public override string Run(McTcpClient handler, string command) { - return "PlayerList: " + String.Join(", ", handler.getOnlinePlayers()); + return "PlayerList: " + String.Join(", ", handler.GetOnlinePlayers()); } } } diff --git a/MinecraftClient/Commands/Reco.cs b/MinecraftClient/Commands/Reco.cs index 520f4fea..49e18e76 100644 --- a/MinecraftClient/Commands/Reco.cs +++ b/MinecraftClient/Commands/Reco.cs @@ -15,7 +15,7 @@ namespace MinecraftClient.Commands string[] args = getArgs(command); if (args.Length > 0) { - if (!Settings.setAccount(args[0])) + if (!Settings.SetAccount(args[0])) { return "Unknown account '" + args[0] + "'."; } diff --git a/MinecraftClient/Commands/Set.cs b/MinecraftClient/Commands/Set.cs index 9316077a..5fc2adab 100644 --- a/MinecraftClient/Commands/Set.cs +++ b/MinecraftClient/Commands/Set.cs @@ -17,7 +17,7 @@ namespace MinecraftClient.Commands string[] temp = getArg(command).Split('='); if (temp.Length > 1) { - if (Settings.setVar(temp[0], getArg(command).Substring(temp[0].Length + 1))) + if (Settings.SetVar(temp[0], getArg(command).Substring(temp[0].Length + 1))) { return ""; //Success } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 46b40c59..769c2d02 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -34,11 +34,11 @@ namespace MinecraftClient private string uuid; private string sessionid; - public int getServerPort() { return port; } - public string getServerHost() { return host; } - public string getUsername() { return username; } - public string getUserUUID() { return uuid; } - public string getSessionID() { return sessionid; } + public int GetServerPort() { return port; } + public string GetServerHost() { return host; } + public string GetUsername() { return username; } + public string GetUserUUID() { return uuid; } + public string GetSessionID() { return sessionid; } TcpClient client; IMinecraftCom handler; @@ -101,10 +101,10 @@ namespace MinecraftClient if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); } if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); } if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); } - if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.expandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); } - if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.expandVars(Settings.PlayerLog_File))); } + if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.ExpandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); } + if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.ExpandVars(Settings.PlayerLog_File))); } if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); } - if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.expandVars(Settings.ScriptScheduler_TasksFile))); } + if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.ExpandVars(Settings.ScriptScheduler_TasksFile))); } if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); } } @@ -209,7 +209,7 @@ namespace MinecraftClient { string response_msg = ""; string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1); - if (!performInternalCommand(Settings.expandVars(command), ref response_msg) && Settings.internalCmdChar == '/') + if (!PerformInternalCommand(Settings.ExpandVars(command), ref response_msg) && Settings.internalCmdChar == '/') { SendText(text); } @@ -234,7 +234,7 @@ namespace MinecraftClient /// May contain a confirmation or error message after processing the command, or "" otherwise. /// TRUE if the command was indeed an internal MCC command - public bool performInternalCommand(string command, ref string response_msg) + public bool PerformInternalCommand(string command, ref string response_msg) { /* Load commands from the 'Commands' namespace */ @@ -314,7 +314,8 @@ namespace MinecraftClient Thread.Sleep(1000); - if (client != null) { client.Close(); } + if (client != null) + client.Close(); } /// @@ -476,7 +477,7 @@ namespace MinecraftClient /// /// Online player names - public string[] getOnlinePlayers() + public string[] GetOnlinePlayers() { lock (onlinePlayers) { diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 6fc954de..2beabf47 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -63,7 +63,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient if (Settings.ServerIP == "") { Console.Write("Server IP : "); - Settings.setServerIP(Console.ReadLine()); + Settings.SetServerIP(Console.ReadLine()); } //Get server version @@ -186,7 +186,7 @@ namespace MinecraftClient //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 @@ namespace MinecraftClient 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 38686d17..4acf4447 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -505,7 +505,7 @@ namespace MinecraftClient.Protocol.Handlers public bool Login() { - if (Handshake(handler.getUserUUID(), handler.getUsername(), handler.getSessionID(), handler.getServerHost(), handler.getServerPort())) + if (Handshake(handler.GetUserUUID(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort())) { Send(new byte[] { 0xCD, 0 }); try diff --git a/MinecraftClient/Protocol/Handlers/Protocol17.cs b/MinecraftClient/Protocol/Handlers/Protocol17.cs index b7c08e37..7d74e80b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -349,16 +349,16 @@ namespace MinecraftClient.Protocol.Handlers { 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 @@ namespace MinecraftClient.Protocol.Handlers 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 67fb96ca..509bfb92 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -449,15 +449,15 @@ namespace MinecraftClient.Protocol.Handlers public bool Login() { byte[] protocol_version = getVarInt(protocolversion); - byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.getServerHost()); + byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost()); 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 @@ namespace MinecraftClient.Protocol.Handlers 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 f74d22a4..79f8659e 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -16,12 +16,12 @@ namespace MinecraftClient.Protocol /* 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 b7daa524..735de5cd 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -149,7 +149,7 @@ namespace MinecraftClient { case "login": Login = argValue; break; case "password": Password = argValue; break; - case "serverip": setServerIP(argValue); break; + case "serverip": SetServerIP(argValue); break; case "singlecommand": SingleCommand = argValue; break; case "language": Language = argValue; break; case "consoletitle": ConsoleTitle = argValue; break; @@ -204,7 +204,7 @@ namespace MinecraftClient if (server_data.Length == 2 && server_data[0] != "localhost" && !server_data[0].Contains('.') - && setServerIP(server_data[1])) + && SetServerIP(server_data[1])) Servers[server_data[0]] = new KeyValuePair(ServerIP, ServerPort); } @@ -313,7 +313,7 @@ namespace MinecraftClient break; case ParseMode.AppVars: - setVar(argName, argValue); + SetVar(argName, argValue); break; case ParseMode.AutoRespond: @@ -433,7 +433,7 @@ namespace MinecraftClient /// /// 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 @@ namespace MinecraftClient /// /// True if the server IP was valid and loaded, false otherwise - public static bool setServerIP(string server) + public static bool SetServerIP(string server) { server = server.ToLower(); string[] sip = server.Split(':'); @@ -489,7 +489,7 @@ namespace MinecraftClient /// Value of the variable /// True if the parameters were valid - public static bool setVar(string varName, string varData) + public static bool SetVar(string varName, string varData) { varName = new string(varName.TakeWhile(char.IsLetterOrDigit).ToArray()).ToLower(); if (varName.Length > 0) @@ -500,13 +500,26 @@ namespace MinecraftClient 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 00000000..6240c46a --- /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/131] 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 59fff3cd..a3d57840 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -296,13 +296,13 @@ namespace MinecraftClient } /// - /// 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 @@ namespace MinecraftClient /// 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 567b38b0..19146854 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -12,7 +12,6 @@ namespace MinecraftClient.ChatBots { private string matchesFile; private List respondRules; - private static string header = "[AutoRespond] "; /// /// Create a new AutoRespond bot @@ -84,7 +83,7 @@ namespace MinecraftClient.ChatBots } else if (!String.IsNullOrEmpty(match)) { - if (message.Contains(match)) + if (message.ToLower().Contains(match.ToLower())) { return (privateMsg ? actionPrivate @@ -137,7 +136,7 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots 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 afc2dcef..2e435553 100644 --- a/MinecraftClient/Commands/Log.cs +++ b/MinecraftClient/Commands/Log.cs @@ -14,7 +14,7 @@ namespace MinecraftClient.Commands { 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 dd488d52..9daeadc9 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -326,6 +326,16 @@ namespace MinecraftClient } } + /// + /// 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 769c2d02..db8b66fa 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -163,7 +163,7 @@ namespace MinecraftClient { 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 00000000..fcf228a0 --- /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/131] 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 a3d57840..f1080d8d 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -33,9 +33,10 @@ namespace MinecraftClient { 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 44ae5891..6fc8573d 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -97,7 +97,7 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots } } - 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 @@ namespace MinecraftClient.ChatBots "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 @@ namespace MinecraftClient.ChatBots //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 735de5cd..f6fc9b3a 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -94,9 +94,9 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 00000000..be6694da --- /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 00000000..264711fa --- /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 6240c46a..3d61b25c 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/131] 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 6fc8573d..0e1894d9 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -16,6 +16,7 @@ namespace MinecraftClient.ChatBots { 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 @@ namespace MinecraftClient.ChatBots public Script(string filename) { - file = filename; + ParseArguments(filename); } public Script(string filename, string ownername) @@ -35,7 +36,52 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots public override void Initialize() { //Load the given file from the startup parameters - if (lookForScript(ref file)) + if (LookForScript(ref file)) { lines = System.IO.File.ReadAllLines(file); csharp = file.EndsWith(".cs"); @@ -199,7 +245,7 @@ namespace MinecraftClient.ChatBots "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 @@ namespace MinecraftClient.ChatBots //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 d1783cb7..7605fc40 100644 --- a/MinecraftClient/ChatBots/ScriptScheduler.cs +++ b/MinecraftClient/ChatBots/ScriptScheduler.cs @@ -95,7 +95,7 @@ namespace MinecraftClient.ChatBots if (current_task != null) { //Check if we built a valid task before adding it - if (current_task.script_file != null && Script.lookForScript(ref current_task.script_file) //Check if file exists + if (current_task.script_file != null && Script.LookForScript(ref current_task.script_file) //Check if file exists && (current_task.triggerOnLogin || (current_task.triggerOnTime && current_task.triggerOnTime_Times.Count > 0)) || (current_task.triggerOnInterval && current_task.triggerOnInterval_Interval > 0)) //Look for a valid trigger diff --git a/MinecraftClient/config/sample-script-extended.cs b/MinecraftClient/config/sample-script-extended.cs index be6694da..68ff8cd1 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 @@ for (int i = 0; i < 5; i++) /* 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/131] 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 f6fc9b3a..0985287e 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -468,12 +468,14 @@ namespace MinecraftClient if (host == "localhost" || host.Contains('.')) { + //Server IP (IP or domain names contains at least a dot) ServerIP = host; ServerPort = port; return true; } else if (Servers.ContainsKey(server)) { + //Server Alias (if no dot then treat the server as an alias) ServerIP = Servers[server].Key; ServerPort = Servers[server].Value; return true; From a6a9814163dd8c5131552bd1321c41a6287978fd Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 7 Jul 2015 22:43:27 +0200 Subject: [PATCH 039/131] 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 7d74e80b..63580074 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol17.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol17.cs @@ -90,9 +90,11 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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/131] 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 9daeadc9..7e81590a 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -70,7 +70,6 @@ namespace MinecraftClient 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/131] 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 7e81590a..1272de20 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -175,9 +175,8 @@ namespace MinecraftClient 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/131] 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 4acf4447..cf648fc8 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -441,15 +441,11 @@ namespace MinecraftClient.Protocol.Handlers byte[] token = readNextByteArray(); if (serverID == "-") - { ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); - return true; //No need to check session or start encryption - } else - { 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/131] 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 1272de20..34bb7a35 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -172,15 +172,11 @@ namespace MinecraftClient 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/131] 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 2beabf47..a9e4575d 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -28,7 +28,7 @@ namespace MinecraftClient 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 7262da16..a6c2e981 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -121,6 +121,7 @@ namespace MinecraftClient.Protocol 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/131] 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 0ef5d9ec..bc276c9e 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 63580074..00000000 --- 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 509bfb92..4e663ae2 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -6,24 +6,28 @@ 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.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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The data read from the cache as an array - private byte[] readData(int offset, ref byte[] cache) + private static byte[] readData(int offset, 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers } //Encrypt the data - byte[] key_enc = RSAService.Encrypt(secretKey, false); - byte[] token_enc = RSAService.Encrypt(token, false); - byte[] key_len = getVarInt(key_enc.Length); - byte[] token_len = getVarInt(token_enc.Length); + byte[] key_enc = getArray(RSAService.Encrypt(secretKey, false)); + byte[] token_enc = getArray(RSAService.Encrypt(token, false)); //Encryption Response packet - SendPacket(0x01, concatBytes(key_len, key_enc, token_len, token_enc)); + SendPacket(0x01, concatBytes(key_enc, token_enc)); //Start client-side encryption s = CryptoHandler.getAesStream(c.GetStream(), secretKey); @@ -614,8 +674,10 @@ namespace MinecraftClient.Protocol.Handlers byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor); byte[] tocomplete_len = getVarInt(tocomplete_val.Length); - byte[] has_position = new byte[] { 0x00 }; //false, no position sent - byte[] tabcomplete_packet = concatBytes(tocomplete_len, tocomplete_val, has_position); + byte[] 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 @@ namespace MinecraftClient.Protocol.Handlers 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 a6c2e981..11a0873f 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -32,7 +32,7 @@ namespace MinecraftClient.Protocol 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 @@ namespace MinecraftClient.Protocol 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/131] 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 db8b66fa..0817755c 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -224,6 +224,7 @@ namespace MinecraftClient } } catch (IOException) { } + catch (NullReferenceException) { } } /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 4e663ae2..6cfbf89b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -120,13 +120,6 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 f1080d8d..b8c71706 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -199,11 +199,24 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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/131] 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 19146854..974ac8e6 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -12,6 +12,7 @@ namespace MinecraftClient.ChatBots { private string matchesFile; private List respondRules; + private enum MessageType { Public, Private, Other }; /// /// Create a new AutoRespond bot @@ -31,6 +32,7 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots /// 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 @@ namespace MinecraftClient.ChatBots /// 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 @@ namespace MinecraftClient.ChatBots /// /// 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 @@ namespace MinecraftClient.ChatBots { if (message.ToLower().Contains(match.ToLower())) { - return (privateMsg - ? actionPrivate - : actionPublic).Replace("$u", username); + return toSend.Replace("$u", username); } } + return null; } } @@ -105,6 +120,7 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots /// 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 @@ namespace MinecraftClient.ChatBots //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 fcf228a0..ec85b87c 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/131] 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 34bb7a35..07c67719 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -45,22 +45,18 @@ namespace MinecraftClient public static string ReadPassword() { - string password = ""; - ConsoleKeyInfo k = new ConsoleKeyInfo(); - while (k.Key != ConsoleKey.Enter) + StringBuilder password = new StringBuilder(); + + ConsoleKeyInfo k; + while ((k = Console.ReadKey(true)).Key != ConsoleKey.Enter) { - k = Console.ReadKey(true); switch (k.Key) { - case ConsoleKey.Enter: - Console.Write('\n'); - return password; - case ConsoleKey.Backspace: if (password.Length > 0) { Console.Write("\b \b"); - password = password.Substring(0, password.Length - 1); + password.Remove(password.Length - 1, 1); } break; @@ -79,12 +75,14 @@ namespace MinecraftClient if (k.KeyChar != 0) { Console.Write('*'); - password += k.KeyChar; + password.Append(k.KeyChar); } break; } } - return password; + + Console.WriteLine(); + return password.ToString(); } /// diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 11a0873f..624d6a7f 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -285,16 +285,18 @@ namespace MinecraftClient.Protocol 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/131] 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 6cfbf89b..6188accf 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -197,6 +197,12 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 6188accf..502390d5 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -738,7 +738,7 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 502390d5..398d8c58 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -200,7 +200,8 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 398d8c58..c6357f73 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -139,6 +139,9 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers } } + /// + /// 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/131] 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 00000000..46add112 --- /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 b8c71706..4c0a5c09 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -85,12 +85,10 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient /// /// 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 @@ namespace MinecraftClient 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 0e1894d9..4e464f1a 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using Microsoft.CSharp; using System.CodeDom.Compiler; +using System.Reflection; namespace MinecraftClient.ChatBots { @@ -143,8 +144,18 @@ namespace MinecraftClient.ChatBots 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 @@ namespace MinecraftClient.ChatBots { tpause.Set(); tpause.Reset(); - if (thread.Join(100)) + if (!thread.IsAlive) UnloadBot(); } } @@ -205,84 +216,5 @@ namespace MinecraftClient.ChatBots } } } - - 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 bc276c9e..9e1fc0c7 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -113,6 +113,7 @@ + diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 0985287e..40a51cb1 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -45,6 +45,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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 @@ namespace MinecraftClient /// Value of the variable /// True if the parameters were valid - public static bool SetVar(string varName, string varData) + public static bool SetVar(string varName, object varData) { lock (AppVars) { @@ -511,7 +514,7 @@ namespace MinecraftClient /// 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 @@ namespace MinecraftClient default: if (AppVars.ContainsKey(varname_lower)) { - result.Append(AppVars[varname_lower]); + result.Append(AppVars[varname_lower].ToString()); } else result.Append("%" + varname + '%'); break; diff --git a/MinecraftClient/config/sample-script-extended.cs b/MinecraftClient/config/sample-script-extended.cs index 68ff8cd1..b8751f0b 100644 --- a/MinecraftClient/config/sample-script-extended.cs +++ b/MinecraftClient/config/sample-script-extended.cs @@ -9,8 +9,8 @@ if (args.Length > 0) 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 @@ for (int i = 0; i < 5; i++) 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 264711fa..25cb13eb 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 3d61b25c..34d3354a 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/131] 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 4c0a5c09..27459bf9 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -197,6 +197,17 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 40a51cb1..d09a5912 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,6 +89,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 27459bf9..4c0a5c09 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -197,17 +197,6 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 d09a5912..40a51cb1 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,7 +89,6 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 4c0a5c09..27459bf9 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -197,6 +197,17 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 40a51cb1..d09a5912 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,6 +89,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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/131] 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 307f491a..163e9ada 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/131] 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 27459bf9..d7a7e412 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -241,18 +241,6 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } //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/131] 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 d09a5912..27240440 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,7 +89,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 d7a7e412..ebdbcc08 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -306,7 +306,8 @@ namespace MinecraftClient && 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 27240440..a22ec296 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -89,7 +89,10 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } 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 @@ namespace MinecraftClient + "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/131] 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 a22ec296..ee14b0e1 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -436,9 +436,10 @@ namespace MinecraftClient + "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/131] 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 ebdbcc08..5d568289 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -266,7 +266,7 @@ namespace MinecraftClient //<*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 ee14b0e1..ae231d63 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -93,6 +93,7 @@ namespace MinecraftClient //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 @@ namespace MinecraftClient { 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 @@ namespace MinecraftClient + "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/131] 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 c6357f73..26054daf 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -140,7 +140,10 @@ namespace MinecraftClient.Protocol.Handlers 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 ae231d63..93beb6a8 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -46,6 +46,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 0817755c..ccb04f71 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -120,6 +120,9 @@ namespace MinecraftClient { 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 cf648fc8..1813b9af 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -610,6 +610,11 @@ namespace MinecraftClient.Protocol.Handlers 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 26054daf..92512110 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -139,12 +139,6 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers } } - /// - /// 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 @@ namespace MinecraftClient.Protocol.Handlers 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 2ae82c14..9c351ebc 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -43,5 +43,13 @@ namespace MinecraftClient.Protocol /// 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 93beb6a8..7a4a4cb5 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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 @@ namespace MinecraftClient + "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/131] 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 92512110..f728c470 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -140,7 +140,17 @@ namespace MinecraftClient.Protocol.Handlers 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 7a4a4cb5..129e8d6d 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -50,6 +50,8 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 ccb04f71..fed884a1 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -120,9 +120,6 @@ namespace MinecraftClient { if (handler.Login()) { - if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) - handler.SendBrandInfo(Settings.BrandInfo.Trim()); - if (singlecommand) { handler.SendChatMessage(command); @@ -322,6 +319,16 @@ namespace MinecraftClient 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 f728c470..8fac698e 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -139,6 +139,9 @@ namespace MinecraftClient.Protocol.Handlers 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 79f8659e..606e80ea 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -23,6 +23,12 @@ namespace MinecraftClient.Protocol 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/131] 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 129e8d6d..b2479575 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -174,7 +174,7 @@ namespace MinecraftClient 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/131] 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 496dcea7..fa71e994 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/131] 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 974ac8e6..bb14c5b1 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -203,6 +203,7 @@ namespace MinecraftClient.ChatBots 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/131] 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 0817755c..53e5965f 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -111,7 +111,14 @@ namespace MinecraftClient 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 93beb6a8..5defadef 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -32,6 +32,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 53e5965f..41312158 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -111,7 +111,7 @@ namespace MinecraftClient try { - if (Settings.LoginOnlyProxy = true) + if (Settings.OnlyForLogin) { client = new TcpClient(host, port); } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 5defadef..b005e90c 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -32,7 +32,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 07c67719..65488382 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -338,15 +338,21 @@ namespace MinecraftClient { if (buffer.Length > 0) { - if (Console.CursorLeft == 0) + try { - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; - Console.Write(' '); - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; + if (Console.CursorLeft == 0) + { + Console.CursorLeft = Console.BufferWidth - 1; + if (Console.CursorTop > 0) + Console.CursorTop--; + Console.Write(' '); + Console.CursorLeft = Console.BufferWidth - 1; + if (Console.CursorTop > 0) + Console.CursorTop--; + } + else Console.Write("\b \b"); } - else Console.Write("\b \b"); + catch (ArgumentOutOfRangeException) { /* Console was resized!? */ } buffer = buffer.Substring(0, buffer.Length - 1); if (buffer2.Length > 0) @@ -358,12 +364,17 @@ namespace MinecraftClient } private static void GoBack() { - if (Console.CursorLeft == 0) + try { - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; + if (Console.CursorLeft == 0) + { + Console.CursorLeft = Console.BufferWidth - 1; + if (Console.CursorTop > 0) + Console.CursorTop--; + } + else Console.Write('\b'); } - else Console.Write('\b'); + catch (ArgumentOutOfRangeException) { /* Console was resized!? */ } } private static void GoLeft() { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 8fac698e..f5fe4071 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -85,6 +85,7 @@ namespace MinecraftClient.Protocol.Handlers } } 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/131] 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 cdcf3f6d..fed884a1 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -111,14 +111,7 @@ namespace MinecraftClient 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 624d6a7f..dcd507d7 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -244,7 +244,7 @@ namespace MinecraftClient.Protocol 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 d18d4ea1..a017e323 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -24,12 +24,15 @@ namespace MinecraftClient.Proxy /// /// Create a regular TcpClient or a proxied TcpClient according to the app Settings. /// + /// Target host + /// Target port + /// True if the purpose is logging in to a Minecraft account - public static TcpClient newTcpClient(string host, int port) + public static TcpClient newTcpClient(string host, int port, bool login = false) { try { - if (Settings.ProxyEnabled) + if (login ? Settings.ProxyEnabledLogin : Settings.ProxyEnabledIngame) { ProxyType innerProxytype = ProxyType.Http; diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index d4290520..407d730f 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -29,13 +29,13 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "#%username% and %serverip% are reserved variables.\r\n" + "\r\n" + "[Proxy]\r\n" - + "enabled=false\r\n" + + "enabled=false #use 'false', 'true', or 'login' for login only\r\n" + "type=HTTP #Supported types: HTTP, SOCKS4, SOCKS4a, SOCKS5\r\n" + "server=0.0.0.0:0000\r\n" + "username=\r\n" + "password=\r\n" - + "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/131] 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 | 245 +++++++++++++++++++----------- MinecraftClient/Settings.cs | 65 +++++--- MinecraftClient/config/README.txt | 9 ++ 3 files changed, 211 insertions(+), 108 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 5d568289..6540b892 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.IO; using System.Threading; +using System.Text.RegularExpressions; namespace MinecraftClient { @@ -142,11 +143,11 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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); + //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; } - else return false; + catch (IndexOutOfRangeException) { /* Not an expected chat format */ } } - catch (IndexOutOfRangeException) { return false; } + + //User-defined regex for private chat messages + if (Settings.ChatFormat_Private != null) + { + Match regexMatch = Settings.ChatFormat_Private.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 3) + { + sender = regexMatch.Groups[1].Value; + message = regexMatch.Groups[2].Value; + return IsValidName(sender); + } + } + + return false; } /// @@ -256,17 +277,22 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient //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 @@ namespace MinecraftClient && 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 @@ namespace MinecraftClient } } } + + //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 @@ namespace MinecraftClient 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]; + string[] tmp = text.Split(' '); - //Username has requested... - else sender = tmp[0]; + //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]; - //Final check on username validity - return IsValidName(sender); + //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 407d730f..c0119ff1 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; +using System.Text.RegularExpressions; namespace MinecraftClient { @@ -97,10 +98,11 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient } 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 @@ namespace MinecraftClient + "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 @@ namespace MinecraftClient + "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 fa71e994..04e460ae 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/131] 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 f5fe4071..bb826930 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -525,7 +525,7 @@ namespace MinecraftClient.Protocol.Handlers public bool Login() { byte[] protocol_version = getVarInt(protocolversion); - byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost()); + byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.GetServerHost() + "\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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 fed884a1..e1e5c6bd 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -8,6 +8,7 @@ using System.IO; using System.Net; using MinecraftClient.Protocol; using MinecraftClient.Proxy; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient { @@ -54,9 +55,9 @@ namespace MinecraftClient /// The server port to use /// Minecraft protocol version to use - public McTcpClient(string username, string uuid, string sessionID, int protocolversion, string server_ip, ushort port) + public McTcpClient(string username, string uuid, string sessionID, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port) { - StartClient(username, uuid, sessionID, server_ip, port, protocolversion, false, ""); + StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, false, ""); } /// @@ -70,9 +71,9 @@ namespace MinecraftClient /// Minecraft protocol version to use /// The text or command to send. - public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, string command) + public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command) { - StartClient(username, uuid, sessionID, server_ip, port, protocolversion, true, command); + StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, true, command); } /// @@ -87,7 +88,7 @@ namespace MinecraftClient /// If set to true, the client will send a single command and then disconnect from the server /// The text or command to send. Will only be sent if singlecommand is set to true. - private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, bool singlecommand, string command) + private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command) { bool retry = false; this.sessionid = sessionID; @@ -113,7 +114,7 @@ namespace MinecraftClient { 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 9e1fc0c7..2e2ad727 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -114,6 +114,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index a9e4575d..302e50bb 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -5,6 +5,7 @@ using System.Text; using MinecraftClient.Protocol; using System.Reflection; using System.Threading; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient { @@ -145,6 +146,7 @@ namespace MinecraftClient //Get server version int protocolversion = 0; + ForgeInfo forgeInfo = null; if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto") { @@ -166,7 +168,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient //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 00000000..dd3a83de --- /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 bb826930..c11479a3 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -7,6 +7,7 @@ using System.Threading; using MinecraftClient.Crypto; using MinecraftClient.Proxy; using System.Security.Cryptography; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient.Protocol.Handlers { @@ -25,18 +26,22 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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 @@ namespace MinecraftClient.Protocol.Handlers 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 dcd507d7..067ab4bf 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -6,6 +6,7 @@ using MinecraftClient.Protocol.Handlers; using MinecraftClient.Proxy; using System.Net.Sockets; using System.Net.Security; +using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient.Protocol { @@ -23,16 +24,17 @@ namespace MinecraftClient.Protocol /// 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 @@ namespace MinecraftClient.Protocol } 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 @@ namespace MinecraftClient.Protocol /// 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/131] 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 2e2ad727..9c09a5e1 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 00000000..a72b4ad5 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeClientState.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Protocol.Handlers.Forge +{ + /// + /// Copy of the forge enum for client states. + /// https://github.com/MinecraftForge/MinecraftForge/blob/ebe9b6d4cbc4a5281c386994f1fbda04df5d2e1f/src/main/java/net/minecraftforge/fml/common/network/handshake/FMLHandshakeClientState.java + /// + enum FMLHandshakeClientState : byte + { + START, + HELLO, + WAITINGSERVERDATA, + WAITINGSERVERCOMPLETE, + PENDINGCOMPLETE, + COMPLETE, + DONE, + ERROR + } +} diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs new file mode 100755 index 00000000..2402eff0 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/Forge/FMLHandshakeDiscriminator.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Protocol.Handlers.Forge +{ + /// + /// Different "discriminator byte" values for the forge handshake. + /// https://github.com/MinecraftForge/MinecraftForge/blob/ebe9b6d4cbc4a5281c386994f1fbda04df5d2e1f/src/main/java/net/minecraftforge/fml/common/network/handshake/FMLHandshakeCodec.java + /// + enum FMLHandshakeDiscriminator : byte + { + ServerHello = 0, + ClientHello = 1, + ModList = 2, + RegistryData = 3, + HandshakeAck = 255, //-1 + HandshakeReset = 254, //-2 + } +} diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index c11479a3..3706e44c 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 @@ namespace MinecraftClient.Protocol.Handlers // Server forge info -- may be null. private ForgeInfo forgeInfo; + private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START; IMinecraftComHandler handler; Thread netRead; @@ -138,94 +139,153 @@ namespace MinecraftClient.Protocol.Handlers 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 + } + 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) { - 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 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// Amount of bytes to read /// The data read from the network as an array - + private byte[] readDataRAW(int offset) { if (offset > 0) @@ -278,7 +338,7 @@ namespace MinecraftClient.Protocol.Handlers } return new byte[] { }; } - + /// /// Read some data from a cache of bytes and remove it from the cache /// @@ -377,7 +437,7 @@ namespace MinecraftClient.Protocol.Handlers } return i; } - + /// /// Read an integer from a cache of bytes and remove it from the cache /// @@ -401,6 +461,16 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers } } + /// + /// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically + /// + /// Discriminator to use. + /// packet Data + + private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, byte[] data) + { + SendPluginChannelPacket("FML|HS", concatBytes(new byte[] { (byte)discriminator }, data)); + } + + /// + /// Send a 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 @@ namespace MinecraftClient.Protocol.Handlers } } - 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/131] 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 3706e44c..3adc6631 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -141,54 +141,6 @@ namespace MinecraftClient.Protocol.Handlers } // 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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 3adc6631..9663c9eb 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -258,6 +258,61 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 9663c9eb..70ef4766 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -212,6 +212,12 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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) + if (protocolversion < MC18Version) { + // 1.7.10 and below have one registry + // with blocks and items. + int registrySize = readNextVarInt(ref packetData); + + ConsoleIO.WriteLineFormatted("§8Received registry " + + "with " + registrySize + " entries"); + 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 1813b9af..89cedbe6 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -615,6 +615,31 @@ namespace MinecraftClient.Protocol.Handlers 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 70ef4766..8fc5eaae 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -642,29 +642,6 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers { 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 9c351ebc..218f49d4 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -51,5 +51,16 @@ namespace MinecraftClient.Protocol /// 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/131] 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 dd3a83de..03180432 100755 --- a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs +++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs @@ -38,7 +38,7 @@ namespace MinecraftClient.Protocol.Handlers.Forge /// 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 8fc5eaae..e8dd5adf 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -223,7 +223,7 @@ namespace MinecraftClient.Protocol.Handlers if (channel == "FML|HS") { FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); - + if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) { fmlHandshakeState = FMLHandshakeClientState.START; @@ -259,7 +259,7 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 e8dd5adf..8eeef8cc 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -214,9 +214,17 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers return i; } + /// + /// Read an "extended short", which is actually an int of some kind, from the cache of bytes. + /// This is only done with forge. It looks like it's a normal short, except that if the high + /// bit is set, it has an extra byte. + /// + /// Cache of bytes to read from + /// The int + + private static int readNextVarShort(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/131] 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 8eeef8cc..a9631206 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -772,6 +772,15 @@ namespace MinecraftClient.Protocol.Handlers { 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 @@ namespace MinecraftClient.Protocol.Handlers } } + /// + /// 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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 6540b892..38eb3a18 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -307,7 +307,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient //[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 @@ namespace MinecraftClient && 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/131] 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 302e50bb..9e447531 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -175,6 +175,11 @@ namespace MinecraftClient } } + 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/131] 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 46add112..b8ec098a 100644 --- a/MinecraftClient/CSharpRunner.cs +++ b/MinecraftClient/CSharpRunner.cs @@ -210,7 +210,7 @@ namespace MinecraftClient /// /// 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 2ef20783..30dc6460 100644 --- a/MinecraftClient/ConsoleIcon.cs +++ b/MinecraftClient/ConsoleIcon.cs @@ -36,9 +36,13 @@ namespace MinecraftClient { using (HttpWebResponse httpWebReponse = (HttpWebResponse)httpWebRequest.GetResponse()) { - Bitmap skin = new Bitmap(Image.FromStream(httpWebReponse.GetResponseStream())); //Read skin from network - skin = skin.Clone(new Rectangle(8, 8, 8, 8), skin.PixelFormat); //Crop skin - SetConsoleIcon(skin.GetHicon()); //Set skin as icon + try + { + Bitmap skin = new Bitmap(Image.FromStream(httpWebReponse.GetResponseStream())); //Read skin from network + skin = skin.Clone(new Rectangle(8, 8, 8, 8), skin.PixelFormat); //Crop skin + SetConsoleIcon(skin.GetHicon()); //Set skin as icon + } + catch (ArgumentException) { /* Invalid image in HTTP response */ } } } catch (WebException) //Skin not found? Reset to default icon diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 067ab4bf..266cbdda 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -231,9 +231,40 @@ namespace MinecraftClient.Protocol 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 @@ namespace MinecraftClient.Protocol /// 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 @@ namespace MinecraftClient.Protocol 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/131] 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 | 16 +- .../Protocol/IMinecraftComHandler.cs | 9 + MinecraftClient/Settings.cs | 3 + 11 files changed, 319 insertions(+), 17 deletions(-) create mode 100644 MinecraftClient/Mapping/Location.cs diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 38eb3a18..a9349d72 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -458,7 +458,7 @@ namespace MinecraftClient 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 d98e30d5..2c53f585 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -25,14 +25,14 @@ namespace MinecraftClient.ChatBots { attempts = retries; if (attempts == -1) { attempts = int.MaxValue; } - McTcpClient.AttemptsLeft = attempts; + McTcpClient.ReconnectionAttemptsLeft = attempts; delay = DelayBeforeRelog; if (delay < 1) { delay = 1; } } public override void Initialize() { - McTcpClient.AttemptsLeft = attempts; + McTcpClient.ReconnectionAttemptsLeft = attempts; if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile)) { dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile); @@ -55,7 +55,7 @@ namespace MinecraftClient.ChatBots { 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 00000000..22145ad5 --- /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 e1e5c6bd..66e8bc8a 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -9,6 +9,7 @@ using System.Net; 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 9c09a5e1..b9a92e7e 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -152,6 +152,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 9e447531..12916186 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -19,7 +19,11 @@ namespace MinecraftClient { 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 89cedbe6..07ee63dd 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -7,6 +7,7 @@ using System.Threading; using MinecraftClient.Crypto; using MinecraftClient.Proxy; using System.Security.Cryptography; +using MinecraftClient.Mapping; namespace MinecraftClient.Protocol.Handlers { @@ -615,6 +616,11 @@ namespace MinecraftClient.Protocol.Handlers 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 a9631206..ddb8771b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -8,6 +8,7 @@ using MinecraftClient.Crypto; using MinecraftClient.Proxy; using System.Security.Cryptography; using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Mapping; namespace MinecraftClient.Protocol.Handlers { @@ -162,6 +163,23 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers return bytes.ToArray(); } + /// + /// Get byte array representing a double + /// + /// Array to process + /// Array ready to send + + private byte[] getDouble(double number) + { + byte[] theDouble = BitConverter.GetBytes(number); + Array.Reverse(theDouble); //Endianness + return theDouble; + } + /// /// Get byte array with length information prepended to it /// @@ -931,6 +974,29 @@ namespace MinecraftClient.Protocol.Handlers 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 218f49d4..927258fd 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using MinecraftClient.Crypto; +using MinecraftClient.Mapping; namespace MinecraftClient.Protocol { @@ -45,7 +46,7 @@ namespace MinecraftClient.Protocol 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 @@ -53,10 +54,17 @@ namespace MinecraftClient.Protocol bool SendBrandInfo(string brandInfo); /// - /// Send a plugin channel packet to the server. - /// - /// http://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/ + /// Send a location update telling that we moved to that location /// + /// The new location + /// True if packet was successfully sent + + bool SendLocationUpdate(Location location, bool onGround); + + /// + /// Send a plugin channel packet to the server. + /// + /// /// Channel to send packet on /// packet Data /// True if message was successfully sent diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 606e80ea..5376e31a 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 @@ namespace MinecraftClient.Protocol string GetUserUUID(); string GetSessionID(); string[] GetOnlinePlayers(); + Location GetCurrentLocation(); /// /// Called when a server was successfully joined @@ -50,6 +52,13 @@ namespace MinecraftClient.Protocol void OnPlayerLeave(Guid uuid); + /// + /// Called when the server sets the new location for the player + /// + /// New location of the player + + void UpdateLocation(Location location); + /// /// This method is called when the connection has been lost /// diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index c0119ff1..a0c6fc5d 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -54,6 +54,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 ddb8771b..3b087710 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -988,7 +988,7 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 00000000..29a082af --- /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 00000000..a64cc329 --- /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 00000000..26cbab39 --- /dev/null +++ b/MinecraftClient/Mapping/ChunkColumn.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Mapping +{ + /// + /// Represent a column of chunks of terrain in a Minecraft world + /// + public class ChunkColumn + { + public const int ColumnSize = 16; + + /// + /// Blocks contained into the chunk + /// + private readonly Chunk[] chunks = new Chunk[ColumnSize]; + + /// + /// Get or set the specified chunk column + /// + /// ChunkColumn X + /// ChunkColumn Y + /// chunk at the given location + public Chunk this[int chunkY] + { + get + { + return chunks[chunkY]; + } + set + { + chunks[chunkY] = value; + } + } + + /// + /// Get chunk at the specified location + /// + /// Location, a modulo will be applied + /// The chunk, or null if not loaded + public Chunk GetChunk(Location location) + { + return this[location.ChunkY]; + } + } +} diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index 22145ad5..a34f7a0e 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -46,6 +46,72 @@ namespace MinecraftClient.Mapping 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 @@ namespace MinecraftClient.Mapping | (((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 00000000..0214d100 --- /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 66e8bc8a..0e74b0e3 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -31,6 +31,8 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient { 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 b9a92e7e..a7615537 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 3b087710..06f4fa30 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -86,9 +86,9 @@ namespace MinecraftClient.Protocol.Handlers while (c.Client.Available > 0) { int packetID = 0; - byte[] packetData = new byte[] { }; - readNextPacket(ref packetID, ref packetData); - handlePacket(packetID, packetData); + List packetData = new List(); + readNextPacket(ref packetID, packetData); + handlePacket(packetID, new List(packetData)); } } catch (SocketException) { return false; } @@ -102,21 +102,26 @@ namespace MinecraftClient.Protocol.Handlers /// will contain packet ID /// will contain raw packet Data - private void readNextPacket(ref int packetID, ref byte[] packetData) + private void readNextPacket(ref int packetID, List packetData) { 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 @@ namespace MinecraftClient.Protocol.Handlers /// Packet contents /// TRUE if the packet was processed, FALSE if ignored or unknown - private bool handlePacket(int packetID, byte[] packetData) + private bool handlePacket(int packetID, List packetData) { if (login_phase) { @@ -134,7 +139,7 @@ namespace MinecraftClient.Protocol.Handlers { case 0x03: if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); + compression_treshold = readNextVarInt(packetData); break; default: return false; //Ignored packet @@ -151,11 +156,11 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers } 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 @@ namespace MinecraftClient.Protocol.Handlers } 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers { // 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 @@ namespace MinecraftClient.Protocol.Handlers { // 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 @@ namespace MinecraftClient.Protocol.Handlers } 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The byte array - private byte[] readNextByteArray(ref byte[] cache) + private byte[] readNextByteArray(List cache) { int len = 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// /// 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 @@ namespace MinecraftClient.Protocol.Handlers /// 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers //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 @@ namespace MinecraftClient.Protocol.Handlers 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 5376e31a..b9de09d8 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -24,6 +24,7 @@ namespace MinecraftClient.Protocol 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 a0c6fc5d..437cbbab 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -409,7 +409,7 @@ namespace MinecraftClient + "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/131] 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 a64cc329..5b84ecf2 100644 --- a/MinecraftClient/Mapping/Chunk.cs +++ b/MinecraftClient/Mapping/Chunk.cs @@ -57,7 +57,7 @@ namespace MinecraftClient.Mapping /// 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 a34f7a0e..e55c5a88 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -46,6 +46,21 @@ namespace MinecraftClient.Mapping 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 @@ namespace MinecraftClient.Mapping { get { - return ((int)X) / Chunk.SizeX; + return (int)Math.Floor(X / Chunk.SizeX); } } @@ -64,7 +79,7 @@ namespace MinecraftClient.Mapping { get { - return ((int)Y) / Chunk.SizeY; + return (int)Math.Floor(Y / Chunk.SizeY); } } @@ -75,7 +90,7 @@ namespace MinecraftClient.Mapping { get { - return ((int)Z) / Chunk.SizeY; + return (int)Math.Floor(Z / Chunk.SizeZ); } } @@ -86,7 +101,7 @@ namespace MinecraftClient.Mapping { get { - return ((int)X) % Chunk.SizeX; + return ((int)Math.Floor(X) % Chunk.SizeX + Chunk.SizeX) % Chunk.SizeX; } } @@ -97,7 +112,7 @@ namespace MinecraftClient.Mapping { get { - return ((int)Y) % Chunk.SizeY; + return ((int)Math.Floor(Y) % Chunk.SizeY + Chunk.SizeY) % Chunk.SizeY; } } @@ -108,7 +123,7 @@ namespace MinecraftClient.Mapping { get { - return ((int)Z) % Chunk.SizeZ; + return ((int)Math.Floor(Z) % Chunk.SizeZ + Chunk.SizeZ) % Chunk.SizeZ; } } @@ -153,7 +168,16 @@ namespace MinecraftClient.Mapping 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 0e74b0e3..88baf667 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -456,11 +456,15 @@ namespace MinecraftClient { 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 06f4fa30..6d395676 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -209,7 +209,7 @@ namespace MinecraftClient.Protocol.Handlers 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 @@ namespace MinecraftClient.Protocol.Handlers 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/131] 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 29a082af..e7aa0d95 100644 --- a/MinecraftClient/Mapping/Block.cs +++ b/MinecraftClient/Mapping/Block.cs @@ -46,13 +46,13 @@ namespace MinecraftClient.Mapping } /// - /// 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 @@ namespace MinecraftClient.Mapping /// /// 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 00000000..d95a9605 --- /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 0214d100..25b79625 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -80,7 +80,7 @@ namespace MinecraftClient.Mapping 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 88baf667..7a6da5f3 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -34,7 +34,6 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 a7615537..dbff60b7 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 266cbdda..6377cd49 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -42,7 +42,7 @@ namespace MinecraftClient.Protocol } 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/131] 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 d95a9605..c506ef94 100644 --- a/MinecraftClient/Mapping/Material.cs +++ b/MinecraftClient/Mapping/Material.cs @@ -335,8 +335,8 @@ { 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/131] 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 00000000..25ab8e16 --- /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 00000000..5092e7ff --- /dev/null +++ b/MinecraftClient/Mapping/Direction.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Mapping +{ + /// + /// Represents a unit movement in the world + /// + /// + public enum Direction + { + South = 0, + West = 1, + North = 2, + East = 3, + Up = 4, + Down = 5 + } +} diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index e55c5a88..ed683134 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -127,6 +127,28 @@ namespace MinecraftClient.Mapping } } + /// + /// 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 @@ namespace MinecraftClient.Mapping /// /// 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 @@ namespace MinecraftClient.Mapping /// /// 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 @@ namespace MinecraftClient.Mapping /// 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 @@ namespace MinecraftClient.Mapping /// 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 @@ namespace MinecraftClient.Mapping /// 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 @@ namespace MinecraftClient.Mapping ); } + /// + /// 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 c506ef94..005b0b5e 100644 --- a/MinecraftClient/Mapping/Material.cs +++ b/MinecraftClient/Mapping/Material.cs @@ -342,5 +342,24 @@ return false; } } + + /// + /// Check if the provided material is a liquid a player can swim into + /// + /// Material to test + /// True if the material is a liquid + public static bool IsLiquid(this Material m) + { + switch (m) + { + case Material.Water: + case Material.StationaryWater: + case Material.Lava: + case Material.StationaryLava: + return true; + default: + return false; + } + } } } diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs new file mode 100644 index 00000000..ac963f84 --- /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 7a6da5f3..a4750e93 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -33,6 +33,8 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient { 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 dbff60b7..c0a631c9 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 6d395676..5bbd7da5 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -215,7 +215,7 @@ namespace MinecraftClient.Protocol.Handlers 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 437cbbab..90b3689d 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -179,7 +179,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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/131] 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 ac963f84..b85852db 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -99,7 +99,7 @@ namespace MinecraftClient.Mapping 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 @@ namespace MinecraftClient.Mapping .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 a4750e93..6423460a 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -32,6 +32,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient this.location += location; } else this.location = location; + locationReceived = true; } } @@ -468,7 +470,7 @@ namespace MinecraftClient } } - 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/131] 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 b85852db..a12af234 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -98,54 +98,59 @@ namespace MinecraftClient.Mapping /// 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/131] 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 b8ec098a..29a980cb 100644 --- a/MinecraftClient/CSharpRunner.cs +++ b/MinecraftClient/CSharpRunner.cs @@ -74,6 +74,7 @@ namespace MinecraftClient "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 a9349d72..ef4494dd 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -502,6 +502,18 @@ namespace MinecraftClient 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/131] 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 ef4494dd..152e36c7 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -250,6 +250,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient return IsValidName(sender); } catch (IndexOutOfRangeException) { /* Not a vanilla/faction message */ } + catch (ArgumentOutOfRangeException) { /* Same here */ } } //Detect HeroChat Messages @@ -324,6 +326,7 @@ namespace MinecraftClient return IsValidName(sender); } catch (IndexOutOfRangeException) { /* Not a herochat message */ } + catch (ArgumentOutOfRangeException) { /* Same here */ } } //Detect (Unknown Plugin) Messages @@ -351,6 +354,7 @@ namespace MinecraftClient } } 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/131] 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 29a980cb..340bddc8 100644 --- a/MinecraftClient/CSharpRunner.cs +++ b/MinecraftClient/CSharpRunner.cs @@ -69,6 +69,7 @@ namespace MinecraftClient { "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/131] 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 152e36c7..816e9978 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -492,7 +492,7 @@ namespace MinecraftClient 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 90b3689d..36ce2f87 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -55,6 +55,7 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient 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 @@ namespace MinecraftClient + "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" From 207732cd866780e2e748a9760fb71cff883f88eb Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 29 Jan 2016 16:11:26 -0800 Subject: [PATCH 103/131] Add an option to add a delay between bot message Because bots can send several messages quickly, this adds an option to slow down the rate at which messages are produced (to avoid issues with antispam plugins). This should help solve part of the troubles @mobdon was having in #105. Right now the default time is 2 seconds per message. However, messages are sent imediately if the bot doesn't need to delay (so if it's a bot that only outputs one or two messages, those will still happen imediately). Also, note that it's limited per-bot right now. I also added an optional parameter to the SendText method so that bots can avoid this behavior if they need to. In some cases, they'll want to send multiple messages. --- MinecraftClient/ChatBot.cs | 43 +++++++++++++++++++++++++++++++++- MinecraftClient/McTcpClient.cs | 1 + MinecraftClient/Settings.cs | 3 +++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 816e9978..0d3fa75e 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -41,6 +41,34 @@ namespace MinecraftClient private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } } private McTcpClient _handler = null; private ChatBot master = null; + private Queue chatQueue = new Queue(); + private DateTime? lastMessageSentTime = null; + private bool CanSendTextNow + { + get + { + return lastMessageSentTime != null + ? DateTime.Now > lastMessageSentTime.Value + Settings.botMessageDelay + : true; + } + } + + /// + /// Processes the current chat message queue, displaying a message after enough time passes. + /// + internal void ProcessQueuedText() + { + if (chatQueue.Count > 0) + { + if (CanSendTextNow) + { + string text = chatQueue.Dequeue(); + LogToConsole("Sending '" + text + "'"); + lastMessageSentTime = DateTime.Now; + Handler.SendText(text); + } + } + } /* ================================================== */ /* Main methods to override for creating your bot */ @@ -84,11 +112,24 @@ namespace MinecraftClient /// Send text to the server. Can be anything such as chat messages or commands /// /// Text to send to the server + /// Whether the message should be sent immediately rather than being queued to avoid chat spam /// True if the text was sent with no error - protected bool SendText(string text) + protected bool SendText(string text, bool sendImmediately = false) { + if (Settings.botMessageDelay.TotalSeconds > 0 && !sendImmediately) + { + if (!CanSendTextNow) + { + chatQueue.Enqueue(text); + // TODO: We don't know whether there was an error at this point, so we assume there isn't. + // Might not be the best idea. + return true; + } + } + LogToConsole("Sending '" + text + "'"); + lastMessageSentTime = DateTime.Now; return Handler.SendText(text); } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 6423460a..b45d0a85 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -459,6 +459,7 @@ namespace MinecraftClient try { bots[i].Update(); + bots[i].ProcessQueuedText(); } catch (Exception e) { diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 36ce2f87..3b0a65ca 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -44,6 +44,7 @@ namespace MinecraftClient public static string TranslationsFile_Website_Download = "http://resources.download.minecraft.net"; public static TimeSpan splitMessageDelay = TimeSpan.FromSeconds(2); public static List Bots_Owners = new List(); + public static TimeSpan botMessageDelay = TimeSpan.FromSeconds(2); public static string Language = "en_GB"; public static bool chatTimeStamps = false; public static bool interactiveMode = true; @@ -182,6 +183,7 @@ namespace MinecraftClient case "showxpbarmessages": DisplayXPBarMessages = str2bool(argValue); break; case "terrainandmovements": TerrainAndMovements = str2bool(argValue); break; case "privatemsgscmdname": PrivateMsgsCmdName = argValue.ToLower().Trim(); break; + case "botmessagedelay": botMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break; case "botowners": Bots_Owners.Clear(); @@ -406,6 +408,7 @@ namespace MinecraftClient + "consoletitle=%username%@%serverip% - Minecraft Console Client\r\n" + "internalcmdchar=slash #use 'none', 'slash' or 'backslash'\r\n" + "splitmessagedelay=2 #seconds between each part of a long message\r\n" + + "botmessagedelay=2 #seconds to delay between message a bot makes to avoid accidental spam\n\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" From 8ce685892cd242fcbf48aa955563fe9c6fa5fc8a Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 30 Jan 2016 22:14:53 -0800 Subject: [PATCH 104/131] Update to 1.8's language files. I ocasionally see untranslated messages; this should resolve all of them. The changes to Settings.cs, the core changes, just update the index file used and the normal hash. The changes in ChatParser.cs primarilly fix the name: 1.7.4 used 'lang/en-GB.lang' but 1.7.10 and 1.8 use 'minecraft/lang/en-GB.lang' and 'realms/lang/en-GB.lang' (and realms comes first), meaning that the wrong language file is selected. The name is updated to make sure the right file is used. I also corrected the indentation in that block. --- MinecraftClient/Protocol/Handlers/ChatParser.cs | 13 +++++++------ MinecraftClient/Settings.cs | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/ChatParser.cs b/MinecraftClient/Protocol/Handlers/ChatParser.cs index 937cb72b..0c18fed7 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -85,12 +85,13 @@ namespace MinecraftClient.Protocol.Handlers ConsoleIO.WriteLine("Downloading '" + Settings.Language + ".lang' from Mojang servers..."); try { - string assets_index = downloadString(Settings.TranslationsFile_Website_Index); - string[] tmp = assets_index.Split(new string[] { "lang/" + Settings.Language + ".lang" }, StringSplitOptions.None); - tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None); - string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers - System.IO.File.WriteAllText(Language_File, downloadString(Settings.TranslationsFile_Website_Download + '/' + hash.Substring(0, 2) + '/' + hash)); - ConsoleIO.WriteLine("Done. File saved as '" + Language_File + '\''); + string assets_index = downloadString(Settings.TranslationsFile_Website_Index); + string[] tmp = assets_index.Split(new string[] { "minecraft/lang/" + Settings.Language + ".lang" }, StringSplitOptions.None); + tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None); + string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers + System.IO.File.WriteAllText(Language_File, downloadString(Settings.TranslationsFile_Website_Download + '/' + hash.Substring(0, 2) + '/' + hash)); + Console.WriteLine(hash); + ConsoleIO.WriteLine("Done. File saved as '" + Language_File + '\''); } catch { diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 3b0a65ca..5cb3402d 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -39,8 +39,8 @@ namespace MinecraftClient public static string ProxyPassword = ""; //Other Settings - public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\9e\9e2fdc43fc1c7024ff5922b998fadb2971a64ee0"; //MC 1.7.4 en_GB.lang - public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.7.4.json"; + public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\03\03f31164d234f10a3230611656332f1756e570a9"; //MC 1.8 en_GB.lang + public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.8.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(); From bdbd8ab0b8e7a9441e4051bf21f0a3c62832b048 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sun, 31 Jan 2016 19:09:03 +0100 Subject: [PATCH 105/131] Remove debug printing line in ChatParser See #107 --- MinecraftClient/ChatBot.cs | 6 ++---- MinecraftClient/Protocol/Handlers/ChatParser.cs | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 0d3fa75e..ce65a3a9 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -42,14 +42,12 @@ namespace MinecraftClient private McTcpClient _handler = null; private ChatBot master = null; private Queue chatQueue = new Queue(); - private DateTime? lastMessageSentTime = null; + private DateTime? lastMessageSentTime = DateTime.MinValue; private bool CanSendTextNow { get { - return lastMessageSentTime != null - ? DateTime.Now > lastMessageSentTime.Value + Settings.botMessageDelay - : true; + return DateTime.Now > lastMessageSentTime.Value + Settings.botMessageDelay; } } diff --git a/MinecraftClient/Protocol/Handlers/ChatParser.cs b/MinecraftClient/Protocol/Handlers/ChatParser.cs index 0c18fed7..5d2e785b 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -90,7 +90,6 @@ namespace MinecraftClient.Protocol.Handlers tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None); string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers System.IO.File.WriteAllText(Language_File, downloadString(Settings.TranslationsFile_Website_Download + '/' + hash.Substring(0, 2) + '/' + hash)); - Console.WriteLine(hash); ConsoleIO.WriteLine("Done. File saved as '" + Language_File + '\''); } catch From 1ea8f119d91e3fa4e2db812998fdaabd4825b005 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 7 Feb 2016 14:24:01 -0800 Subject: [PATCH 106/131] Give ChatBots access to plugin channels. Chatbots may find it useful to send messages over plugin channels. This allows REGISTERing, UNREGISTERing, and sending over plugin channels, with built-in checking if the server also registered the channel (which can be disabled by the bot if needed). Unused channels are UNREGISTERed when a bot is disabled. --- MinecraftClient/ChatBot.cs | 55 ++++++++ MinecraftClient/McTcpClient.cs | 117 +++++++++++++++++- .../Protocol/Handlers/Protocol16.cs | 5 +- .../Protocol/Handlers/Protocol18.cs | 4 + .../Protocol/IMinecraftComHandler.cs | 37 +++++- 5 files changed, 215 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index ce65a3a9..c84ff57d 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -41,6 +41,7 @@ namespace MinecraftClient private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } } private McTcpClient _handler = null; private ChatBot master = null; + private List registeredPluginChannels = new List(); private Queue chatQueue = new Queue(); private DateTime? lastMessageSentTime = DateTime.MinValue; private bool CanSendTextNow @@ -100,6 +101,17 @@ namespace MinecraftClient public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; } + /// + /// Called when a plugin channel message is received. + /// The given channel must have previously been registered with RegisterPluginChannel. + /// This can be used to communicate with server mods or plugins. See wiki.vg for more + /// information about plugin channels: http://wiki.vg/Plugin_channel + /// + /// The name of the channel + /// The payload for the message + + public virtual void OnPluginMessage(string channel, byte[] data) { } + /* =================================================================== */ /* ToolBox - Methods below might be useful while creating your bot. */ /* You should not need to interact with other classes of the program. */ @@ -596,5 +608,48 @@ namespace MinecraftClient return new string[0]; } } + + /// + /// Registers the given plugin channel for use by this chatbot. + /// + /// The name of the channel to register + + protected void RegisterPluginChannel(string channel) + { + this.registeredPluginChannels.Add(channel); + Handler.RegisterPluginChannel(channel, this); + } + + /// + /// Unregisters the given plugin channel, meaning this chatbot can no longer use it. + /// + /// The name of the channel to unregister + + protected void UnregisterPluginChannel(string channel) + { + this.registeredPluginChannels.RemoveAll(chan => chan == channel); + Handler.UnregisterPluginChannel(channel, this); + } + + /// + /// Sends the given plugin channel message to the server, if the channel has been registered. + /// See http://wiki.vg/Plugin_channel for more information about plugin channels. + /// + /// The channel to send the message on. + /// The data to send. + /// Should the message be sent even if it hasn't been registered by the server or this bot? (Some Minecraft channels aren't registered) + /// Whether the message was successfully sent. False if there was a network error or if the channel wasn't registered. + + protected bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false) + { + if (!sendEvenIfNotRegistered) + { + if (!this.registeredPluginChannels.Contains(channel)) + { + return false; + } + } + return Handler.SendPluginChannelMessage(channel, data, sendEvenIfNotRegistered); + } } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index b45d0a85..7e9c95b7 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -28,9 +28,21 @@ namespace MinecraftClient 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 BotUnLoad(ChatBot b) { + bots.RemoveAll(item => object.ReferenceEquals(item, b)); + + // ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon. + var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList(); + foreach (var entry in botRegistrations) + { + UnregisterPluginChannel(entry.Key, b); + } + } public void BotClear() { bots.Clear(); } + private readonly Dictionary> registeredBotPluginChannels = new Dictionary>(); + private readonly List registeredServerPluginChannels = new List(); + private object locationLock = new object(); private bool locationReceived = false; private World world = new World(); @@ -573,5 +585,108 @@ namespace MinecraftClient return onlinePlayers.Values.Distinct().ToArray(); } } + + /// + /// Registers the given plugin channel for the given bot. + /// + /// The channel to register. + /// The bot to register the channel for. + + public void RegisterPluginChannel(string channel, ChatBot bot) + { + if (registeredBotPluginChannels.ContainsKey(channel)) + { + registeredBotPluginChannels[channel].Add(bot); + } + else + { + List bots = new List(); + bots.Add(bot); + registeredBotPluginChannels[channel] = bots; + SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true); + } + } + + /// + /// Unregisters the given plugin channel for the given bot. + /// + /// The channel to unregister. + /// The bot to unregister the channel for. + + public void UnregisterPluginChannel(string channel, ChatBot bot) + { + if (registeredBotPluginChannels.ContainsKey(channel)) + { + List registeredBots = registeredBotPluginChannels[channel]; + registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot)); + if (registeredBots.Count == 0) + { + registeredBotPluginChannels.Remove(channel); + SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true); + } + } + } + + /// + /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information + /// about plugin channels. + /// + /// The channel to send the packet on. + /// The payload for the packet. + /// Whether the packet should be sent even if the server or the client hasn't registered it yet. + /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered. + + public bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false) + { + if (!sendEvenIfNotRegistered) + { + if (!registeredBotPluginChannels.ContainsKey(channel)) + { + return false; + } + if (!registeredServerPluginChannels.Contains(channel)) + { + return false; + } + } + return handler.SendPluginChannelPacket(channel, data); + } + + /// + /// Called when a plugin channel message was sent from the server. + /// + /// The channel the message was sent on + /// The data from the channel + + public void OnPluginChannelMessage(string channel, byte[] data) + { + if (channel == "REGISTER") + { + string[] channels = Encoding.UTF8.GetString(data).Split('\0'); + foreach (string chan in channels) + { + if (!registeredServerPluginChannels.Contains(chan)) + { + registeredServerPluginChannels.Add(chan); + } + } + } + if (channel == "UNREGISTER") + { + string[] channels = Encoding.UTF8.GetString(data).Split('\0'); + foreach (string chan in channels) + { + registeredServerPluginChannels.Remove(chan); + } + } + + if (registeredBotPluginChannels.ContainsKey(channel)) + { + foreach (ChatBot bot in registeredBotPluginChannels[channel]) + { + bot.OnPluginMessage(channel, data); + } + } + } } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 07ee63dd..905ee97f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -164,7 +164,10 @@ namespace MinecraftClient.Protocol.Handlers case 0xCF: if (protocolversion > 51) { readNextString(); readData(1); readNextString(); } readData(4); break; case 0xD0: if (protocolversion > 51) { readData(1); readNextString(); } break; case 0xD1: if (protocolversion > 51) { readNextTeamData(); } break; - case 0xFA: readNextString(); nbr = readNextShort(); readData(nbr); break; + case 0xFA: string channel = readNextString(); + byte[] payload = readNextByteArray(); + handler.OnPluginChannelMessage(channel, payload); + break; case 0xFF: string reason = readNextString(); handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, reason); break; default: return false; //unknown packet! diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 5bbd7da5..d6e8f880 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -303,6 +303,10 @@ namespace MinecraftClient.Protocol.Handlers readNextVarShort(packetData); } } + + // The remaining data in the array is the entire payload of the packet. + handler.OnPluginChannelMessage(channel, packetData.ToArray()); + if (forgeInfo != null) { if (channel == "FML|HS") diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index b9de09d8..7ca8cea6 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -14,7 +14,7 @@ namespace MinecraftClient.Protocol public interface IMinecraftComHandler { - /* The MinecraftCom Hanler must + /* The MinecraftCom Handler must * provide these getters */ int GetServerPort(); @@ -72,5 +72,40 @@ namespace MinecraftClient.Protocol /// void OnUpdate(); + + /// + /// Registers the given plugin channel for the given bot. + /// + /// The channel to register. + /// The bot to register the channel for. + + void RegisterPluginChannel(string channel, ChatBot bot); + + /// + /// Unregisters the given plugin channel for the given bot. + /// + /// The channel to unregister. + /// The bot to unregister the channel for. + + void UnregisterPluginChannel(string channel, ChatBot bot); + + /// + /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information + /// about plugin channels. + /// + /// The channel to send the packet on. + /// The payload for the packet. + /// Whether the packet should be sent even if the server or the client hasn't registered it yet. + /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered. + + bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false); + + /// + /// Called when a plugin channel message was sent from the server. + /// + /// The channel the message was sent on + /// The data from the channel + + void OnPluginChannelMessage(string channel, byte[] data); } } From 6d4ec86619d6a9b1129c7275e71adc3b459a7208 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 7 Feb 2016 14:47:03 -0800 Subject: [PATCH 107/131] Add an "AfterGameJoined" method to ChatBot.cs. Since messages can't be sent in Initialize(), a method that's called when the chat bot can first send messages is needed. This method is called when the login completes or when the bot is loaded if the login is already been completed. --- MinecraftClient/ChatBot.cs | 12 ++++++++++++ MinecraftClient/McTcpClient.cs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index c84ff57d..fd58719a 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -75,10 +75,22 @@ namespace MinecraftClient /// /// Anything you want to initialize your bot, will be called on load by MinecraftCom + /// + /// NOTE: Chat messages cannot be sent at this point in the login process. If you want to send + /// a message when the bot is loaded, use AfterGameJoined. /// public virtual void Initialize() { } + /// + /// Called after the server has been joined successfully and chat messages are able to be sent. + /// + /// NOTE: This is not always right after joining the server - if the bot was loaded after logging + /// in this is still called. + /// + + public virtual void AfterGameJoined() { } + /// /// Will be called every ~100ms (10fps) if loaded in MinecraftCom /// diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 7e9c95b7..35eaa124 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -27,7 +27,16 @@ namespace MinecraftClient 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 BotLoad(ChatBot b) { + b.SetHandler(this); + bots.Add(b); + b.Initialize(); + if (this.handler != null) + { + b.AfterGameJoined(); + } + Settings.SingleCommand = ""; + } public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); @@ -351,6 +360,8 @@ namespace MinecraftClient { if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) handler.SendBrandInfo(Settings.BrandInfo.Trim()); + foreach (ChatBot bot in bots) + bot.AfterGameJoined(); } /// From aed891e3c87c82359f887d2ec6a1ff48a47f84c7 Mon Sep 17 00:00:00 2001 From: initsuj Date: Tue, 23 Feb 2016 11:19:14 -0700 Subject: [PATCH 108/131] Fixed IndexOutOfRangeException when bot is unloaded on update --- MinecraftClient/McTcpClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 35eaa124..f50baecd 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -477,18 +477,18 @@ namespace MinecraftClient public void OnUpdate() { - for (int i = 0; i < bots.Count; i++) + foreach (var bot in bots.ToArray()) { try { - bots[i].Update(); - bots[i].ProcessQueuedText(); + bot.Update(); + bot.ProcessQueuedText(); } catch (Exception e) { if (!(e is ThreadAbortException)) { - ConsoleIO.WriteLineFormatted("§8Update: Got error from " + bots[i].ToString() + ": " + e.ToString()); + ConsoleIO.WriteLineFormatted("§8Update: Got error from " + bot.ToString() + ": " + e.ToString()); } else throw; //ThreadAbortException should not be caught } From 061762957067c7570ddafa2ac7f53ebc445a4450 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 26 Feb 2016 17:57:05 -0800 Subject: [PATCH 109/131] Fix two bugs with 1.7.10 protocol The first bug is that the list isn't cleared in some cases, meaning new packets get data from the previous packet if it isn't read fully. Most commonly, this happens with a plugin channel message. The second bug happens because lists don't throw IndexOutOfRangeExceptions, but instead throw ArgumentOutOfRangeExceptions. This caused the catch for ignoring message types to not occur, instead causing the client to crash. This only happens in 1.7.10, where the message type is not included. Most likely, these changes will fix the bugs seen in #114 and #117, although they may be caused by other bugs. --- 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 d6e8f880..3601b052 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -104,6 +104,7 @@ namespace MinecraftClient.Protocol.Handlers private void readNextPacket(ref int packetID, List packetData) { + packetData.Clear(); int size = readNextVarIntRAW(); //Packet size packetData.AddRange(readDataRAW(size)); //Packet contents @@ -165,7 +166,7 @@ namespace MinecraftClient.Protocol.Handlers || (messageType == 2 && !Settings.DisplayXPBarMessages)) break; } - catch (IndexOutOfRangeException) { /* No message type */ } + catch (ArgumentOutOfRangeException) { /* No message type */ } handler.OnTextReceived(ChatParser.ParseText(message)); break; case 0x08: //Player Position and Look From 5c8f66dbd08a23f5e12ce88e3f19930e92e23fb6 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 26 Feb 2016 18:00:53 -0800 Subject: [PATCH 110/131] Fix broken new line in settings Fixes #115. --- MinecraftClient/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 5cb3402d..7b0ffe88 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -408,7 +408,7 @@ namespace MinecraftClient + "consoletitle=%username%@%serverip% - Minecraft Console Client\r\n" + "internalcmdchar=slash #use 'none', 'slash' or 'backslash'\r\n" + "splitmessagedelay=2 #seconds between each part of a long message\r\n" - + "botmessagedelay=2 #seconds to delay between message a bot makes to avoid accidental spam\n\n" + + "botmessagedelay=2 #seconds to delay between message a bot makes to avoid accidental spam\r\n" + "mcversion=auto #use 'auto' or '1.X.X' values\r\n" + "brandinfo=mcc #use 'mcc','vanilla', or 'none'\r\n" + "chatbotlogfile= #leave empty for no logfile\r\n" From 7ff9d3bb3740dde3e764e5cc461ff64161107851 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 27 Feb 2016 17:56:47 +0100 Subject: [PATCH 111/131] IsPrivateMessage(): Case insensitive 'me' for PM --- MinecraftClient/ChatBot.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index fd58719a..c36c9b28 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -258,7 +258,7 @@ namespace MinecraftClient //[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 + && (tmp[2].ToLower() == "me]" || tmp[2].ToLower() == "moi]")) //'me' is replaced by 'moi' in french servers { message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1); sender = tmp[0].Substring(1); @@ -269,7 +269,7 @@ namespace MinecraftClient //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 + && (tmp[2].ToLower() == "me]" || tmp[2].ToLower() == "moi]")) //'me' is replaced by 'moi' in french servers { message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); sender = tmp[0].Substring(1); @@ -282,7 +282,7 @@ namespace MinecraftClient //[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]")) + && (tmp[3].ToLower() == "me]" || tmp[3].ToLower() == "moi]")) { message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1); sender = tmp[1].Substring(1); @@ -294,7 +294,7 @@ namespace MinecraftClient //[Someone [rank] -> me] message //[~Someone [rank] -> me] message else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->" - && (tmp[3] == "me]" || tmp[3] == "moi]")) + && (tmp[3].ToLower() == "me]" || tmp[3].ToLower() == "moi]")) { message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); sender = tmp[0].Substring(1); From a52fb6135817521c6749b3abeb38563947f807b2 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 27 Feb 2016 17:59:08 +0100 Subject: [PATCH 112/131] lastMessageSentTime does not needs to be optional --- MinecraftClient/ChatBot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index c36c9b28..020951e3 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -43,12 +43,12 @@ namespace MinecraftClient private ChatBot master = null; private List registeredPluginChannels = new List(); private Queue chatQueue = new Queue(); - private DateTime? lastMessageSentTime = DateTime.MinValue; + private DateTime lastMessageSentTime = DateTime.MinValue; private bool CanSendTextNow { get { - return DateTime.Now > lastMessageSentTime.Value + Settings.botMessageDelay; + return DateTime.Now > lastMessageSentTime + Settings.botMessageDelay; } } From b10e3e8521203476760b972ecea88fe2694b7fee Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 27 Feb 2016 18:01:52 +0100 Subject: [PATCH 113/131] Ignore Forge messages once Forge login is complete In an attempt at fixing #117 --- 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 3601b052..bd8af932 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -308,7 +308,7 @@ namespace MinecraftClient.Protocol.Handlers // The remaining data in the array is the entire payload of the packet. handler.OnPluginChannelMessage(channel, packetData.ToArray()); - if (forgeInfo != null) + if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE) { if (channel == "FML|HS") { From 1a41c42ba99f2798c02fa91c2408a260821c5fed Mon Sep 17 00:00:00 2001 From: initsuj Date: Tue, 1 Mar 2016 19:20:05 -0700 Subject: [PATCH 114/131] Cache settings are written and parsed. --- MinecraftClient/Cache/AuthCacheHandler.cs | 12 ++++++++++++ MinecraftClient/Settings.cs | 10 ++++++++++ 2 files changed, 22 insertions(+) create mode 100644 MinecraftClient/Cache/AuthCacheHandler.cs diff --git a/MinecraftClient/Cache/AuthCacheHandler.cs b/MinecraftClient/Cache/AuthCacheHandler.cs new file mode 100644 index 00000000..8b1748b1 --- /dev/null +++ b/MinecraftClient/Cache/AuthCacheHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Cache +{ + public static class AuthCacheHandler + { + public enum Type { NONE, MEMORY, DISK }; + } +} diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 7b0ffe88..51972956 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -29,6 +29,9 @@ namespace MinecraftClient public static string SingleCommand = ""; public static string ConsoleTitle = ""; + //Cache Settings + public static Cache.AuthCacheHandler.Type CacheType = Cache.AuthCacheHandler.Type.NONE; + //Proxy Settings public static bool ProxyEnabledLogin = false; public static bool ProxyEnabledIngame = false; @@ -200,6 +203,12 @@ namespace MinecraftClient } break; + case "accountcache": + if(argValue == "none") { CacheType = Cache.AuthCacheHandler.Type.NONE; } + else if(argValue == "memory") { CacheType = Cache.AuthCacheHandler.Type.MEMORY; } + else if(argValue == "disk") { CacheType = Cache.AuthCacheHandler.Type.DISK; } + break; + case "accountlist": if (File.Exists(argValue)) { @@ -416,6 +425,7 @@ namespace MinecraftClient + "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" + + "accountcache=none #use 'none', 'memory' or 'disk'\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" From 8c065320c2c600045bce84ad71b130f51119c7f4 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 19:40:54 -0700 Subject: [PATCH 115/131] validates token successully --- MinecraftClient/Protocol/ProtocolHandler.cs | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 6377cd49..d02f557e 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -211,6 +211,39 @@ namespace MinecraftClient.Protocol } } + public enum ValidationResult { Validated, RefreshRequired, Error }; + + /// + /// Validates whether accessToken must be refreshed + /// + /// Will contain the cached access token previously returned by Minecraft.net + /// Returns the status of the token (Valid, Invalid, etc.) + /// + public static ValidationResult GetTokenValidation(string accesstoken) + { + try + { + string result = ""; + string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\" }"; + int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); + if (code == 204) + { + return ValidationResult.Validated; + } + else if (code == 403) + { + return ValidationResult.RefreshRequired; + } + else + { + return ValidationResult.Error; + } + } + catch + { + return ValidationResult.Error; + } + } /// /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server /// From 2861be757fdc291310ba3354398bf62c4ca0603a Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 19:53:35 -0700 Subject: [PATCH 116/131] clienttoken is created (if not passed) and added to auth request, then returned --- MinecraftClient/Protocol/ProtocolHandler.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index d02f557e..77abf9d5 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -151,12 +151,16 @@ namespace MinecraftClient.Protocol /// Will contain the player's UUID, needed for multiplayer /// Returns the status of the login (Success, Failure, etc.) - public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string uuid) + public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string clienttoken, ref string uuid) { try { string result = ""; - string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\" }"; + if (clienttoken == string.Empty) + { + clienttoken = Guid.NewGuid().ToString(); + } + string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); if (code == 200) { From 7230cd726b836f4da800c2c1290082a72d50dc97 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 19:58:04 -0700 Subject: [PATCH 117/131] validates with saved clienttoken --- MinecraftClient/Protocol/ProtocolHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 77abf9d5..ea095ae0 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -221,14 +221,15 @@ namespace MinecraftClient.Protocol /// Validates whether accessToken must be refreshed /// /// Will contain the cached access token previously returned by Minecraft.net + /// Will contain the cached client token created on login /// Returns the status of the token (Valid, Invalid, etc.) /// - public static ValidationResult GetTokenValidation(string accesstoken) + public static ValidationResult GetTokenValidation(string accesstoken, string clienttoken) { try { string result = ""; - string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\" }"; + string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); if (code == 204) { From fa2dbfef1a67fd8d40d323cf1dc194e67868de94 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 20:00:02 -0700 Subject: [PATCH 118/131] clientToken param info added to docs --- MinecraftClient/Protocol/ProtocolHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index ea095ae0..ca1a5d9c 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -148,6 +148,7 @@ namespace MinecraftClient.Protocol /// Login /// Password /// Will contain the access token returned by Minecraft.net, if the login is successful + /// Will contain the client token generated before sending to Minecraft.net /// Will contain the player's UUID, needed for multiplayer /// Returns the status of the login (Success, Failure, etc.) From 64606c128fd39530939701fdc4a8e08cf9b08b1b Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Tue, 1 Mar 2016 21:15:17 -0700 Subject: [PATCH 119/131] new token requests implemented. testing a success response is proving difficult. invaliding the token in a way that it can be refreshed is not documented. --- MinecraftClient/Protocol/ProtocolHandler.cs | 60 +++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index ca1a5d9c..e3e43016 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -6,7 +6,8 @@ using MinecraftClient.Protocol.Handlers; using MinecraftClient.Proxy; using System.Net.Sockets; using System.Net.Security; -using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Protocol.Handlers.Forge; + namespace MinecraftClient.Protocol { @@ -159,7 +160,7 @@ namespace MinecraftClient.Protocol string result = ""; if (clienttoken == string.Empty) { - clienttoken = Guid.NewGuid().ToString(); + clienttoken = Guid.NewGuid().ToString().Replace("-",""); } string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); @@ -216,7 +217,7 @@ namespace MinecraftClient.Protocol } } - public enum ValidationResult { Validated, RefreshRequired, Error }; + public enum ValidationResult { Validated, NewTokenRequired, Error }; /// /// Validates whether accessToken must be refreshed @@ -238,7 +239,7 @@ namespace MinecraftClient.Protocol } else if (code == 403) { - return ValidationResult.RefreshRequired; + return ValidationResult.NewTokenRequired; } else { @@ -250,6 +251,57 @@ namespace MinecraftClient.Protocol return ValidationResult.Error; } } + + public enum NewTokenResult { Success, InvalidToken, NullError, OtherError} + + /// + /// Refreshes invalid token + /// + /// Login + /// Will contain the new access token returned by Minecraft.net, if the refresh is successful + /// Will contain the client token generated before sending to Minecraft.net + /// Will contain the player's UUID, needed for multiplayer + /// Returns the status of the new token request (Success, Failure, etc.) + /// + public static NewTokenResult GetNewToken(ref string user, ref string accesstoken, ref string clienttoken, ref string uuid) + { + try + { + string result = ""; + string json_request = "{ \"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(uuid) + "\", \"name\": \"" + jsonEncode(user) + "\" } }"; + int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result); + if (code == 200) + { + if (result == null) { + return NewTokenResult.NullError; + }else{ + string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { clienttoken = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { user = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; } + return NewTokenResult.Success; + } + } + else if(code == 403 && result.Contains("InvalidToken")) + { + return NewTokenResult.InvalidToken; + } + else + { + ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code); + return NewTokenResult.OtherError; + } + } + catch + { + return NewTokenResult.OtherError; + } + } + /// /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server /// From 75f2f738a2eb482cbe74d2e84d0c925bd943100f Mon Sep 17 00:00:00 2001 From: initsuj Date: Wed, 2 Mar 2016 07:25:09 -0700 Subject: [PATCH 120/131] Added clientID to login call. Updated project file to include Cache namespace. --- MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Program.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index c0a631c9..538e2066 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -72,6 +72,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 12916186..bfac6c20 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -118,6 +118,7 @@ namespace MinecraftClient ProtocolHandler.LoginResult result; Settings.Username = Settings.Login; string sessionID = ""; + string clientID = ""; string UUID = ""; if (Settings.Password == "-") @@ -129,21 +130,21 @@ namespace MinecraftClient else { Console.WriteLine("Connecting to Minecraft.net..."); - result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref UUID); + result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref clientID, ref UUID); } if (result == ProtocolHandler.LoginResult.Success) { if (Settings.ConsoleTitle != "") Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - + if (Settings.playerHeadAsIcon) ConsoleIcon.setPlayerIconAsync(Settings.Username); - + Console.WriteLine("Success. (session ID: " + sessionID + ')'); //ProtocolHandler.RealmsListWorlds(Settings.Username, UUID, sessionID); //TODO REMOVE - + if (Settings.ServerIP == "") { Console.Write("Server IP : "); @@ -267,7 +268,7 @@ namespace MinecraftClient /// Error message to display and optionally pass to AutoRelog bot /// Specify if the error is related to an incompatible or unkown server version /// If set, the error message will be processed by the AutoRelog bot - + public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null) { if (!String.IsNullOrEmpty(errorMessage)) From 57c53be09f2107b1bbe0da209d9a6a2536d9e512 Mon Sep 17 00:00:00 2001 From: initsuj Date: Wed, 2 Mar 2016 17:11:15 -0700 Subject: [PATCH 121/131] caching works. needs documentation and testing --- MinecraftClient/Cache/AuthCacheHandler.cs | 12 -- MinecraftClient/Cache/CacheType.cs | 4 + MinecraftClient/Cache/SessionCache.cs | 139 ++++++++++++++++++++ MinecraftClient/MinecraftClient.csproj | 5 +- MinecraftClient/Program.cs | 83 ++++++++---- MinecraftClient/Protocol/ProtocolHandler.cs | 70 +++++----- MinecraftClient/Protocol/SessionToken.cs | 14 ++ MinecraftClient/Settings.cs | 8 +- 8 files changed, 262 insertions(+), 73 deletions(-) delete mode 100644 MinecraftClient/Cache/AuthCacheHandler.cs create mode 100644 MinecraftClient/Cache/CacheType.cs create mode 100644 MinecraftClient/Cache/SessionCache.cs create mode 100644 MinecraftClient/Protocol/SessionToken.cs diff --git a/MinecraftClient/Cache/AuthCacheHandler.cs b/MinecraftClient/Cache/AuthCacheHandler.cs deleted file mode 100644 index 8b1748b1..00000000 --- a/MinecraftClient/Cache/AuthCacheHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace MinecraftClient.Cache -{ - public static class AuthCacheHandler - { - public enum Type { NONE, MEMORY, DISK }; - } -} diff --git a/MinecraftClient/Cache/CacheType.cs b/MinecraftClient/Cache/CacheType.cs new file mode 100644 index 00000000..9f49cd1c --- /dev/null +++ b/MinecraftClient/Cache/CacheType.cs @@ -0,0 +1,4 @@ +namespace MinecraftClient.Cache +{ + public enum CacheType { NONE, MEMORY, DISK }; +} diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Cache/SessionCache.cs new file mode 100644 index 00000000..3d83be40 --- /dev/null +++ b/MinecraftClient/Cache/SessionCache.cs @@ -0,0 +1,139 @@ +using MinecraftClient.Protocol; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; + +namespace MinecraftClient.Cache +{ + public static class SessionCache + { + const string filename = "cache.bin"; + private static Dictionary sessions = new Dictionary(); + private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); + + private static BinaryFormatter formatter = new BinaryFormatter(); + + public static bool Contains(string login) + { + return sessions.ContainsKey(login); + } + + public static void Store(string login, SessionToken session) + { + if (Contains(login)) + { + sessions[login] = session; + } + else + { + sessions.Add(login, session); + } + + if (Settings.CacheType == CacheType.DISK) + { + SaveToDisk(); + } + } + + public static SessionToken Get(string login) + { + return sessions[login]; + } + + public static bool LoadFromDisk() + { + cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; + cachemonitor.IncludeSubdirectories = false; + cachemonitor.Filter = filename; + cachemonitor.NotifyFilter = NotifyFilters.LastWrite; + cachemonitor.Changed += new FileSystemEventHandler(OnChanged); + cachemonitor.EnableRaisingEvents = true; + + return ReadCacheFile(); + } + + public static void OnChanged(object source, FileSystemEventArgs e) + { + ReadCacheFile(); + } + + private static bool ReadCacheFile() + { + if (File.Exists(filename)) + { + try + { + using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + sessions = (Dictionary)formatter.Deserialize(fs); + return true; + } + } + catch (IOException ex) + { + Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); + } + catch (SerializationException) + { + Console.WriteLine("Error getting sessions from cache file "); + } + } + return false; + } + + public static void SaveToDisk() + { + bool fileexists = File.Exists(filename); + + using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + cachemonitor.EnableRaisingEvents = false; + if (fileexists) + { + fs.SetLength(0); + fs.Flush(); + } + + formatter.Serialize(fs, sessions); + cachemonitor.EnableRaisingEvents = true; + } + + } + + private static byte[] GetHash(FileStream fs, bool resetposition = true) + { + using (var md5 = MD5.Create()) + { + long pos = fs.Position; + byte[] hash = md5.ComputeHash(fs); + + fs.Position = resetposition ? pos : fs.Position; + return hash; + } + } + + private static bool HashesEqual(byte[] hash1, byte[] hash2) + { + if (hash1.Length != hash2.Length) + { + return false; + } + + for (int i = 0; i < hash1.Length; i++) + { + if (hash1[i] != hash2[i]) + { + return false; + } + } + + return true; + } + + } +} diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 538e2066..be23d2ed 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -63,6 +63,7 @@ + @@ -72,7 +73,8 @@ - + + @@ -149,6 +151,7 @@ + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index bfac6c20..083e0d29 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -85,6 +85,12 @@ namespace MinecraftClient Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); } + //Load cached sessions from disk if necessary + if (Settings.CacheType == Cache.CacheType.DISK) + { + ConsoleIO.WriteLineFormatted(Cache.SessionCache.LoadFromDisk() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); + } + //Asking the user to type in missing data such as Username and Password if (Settings.Login == "") @@ -92,58 +98,91 @@ namespace MinecraftClient Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); Settings.Login = Console.ReadLine(); } - if (Settings.Password == "") + if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) { - Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); - Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); - if (Settings.Password == "") { Settings.Password = "-"; } - if (!ConsoleIO.basicIO) - { - //Hide password length - Console.CursorTop--; Console.Write("Password : <******>"); - for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } - } + RequestPassword(); } startupargs = args; InitializeClient(); } + /// + /// Reduest user to submit password. + /// + private static void RequestPassword() + { + Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); + Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); + if (Settings.Password == "") { Settings.Password = "-"; } + if (!ConsoleIO.basicIO) + { + //Hide password length + Console.CursorTop--; Console.Write("Password : <******>"); + for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } + } + } + /// /// Start a new Client /// private static void InitializeClient() { - ProtocolHandler.LoginResult result; - Settings.Username = Settings.Login; - string sessionID = ""; - string clientID = ""; - string UUID = ""; + SessionToken session = new SessionToken(); + + ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired; if (Settings.Password == "-") { ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode."); result = ProtocolHandler.LoginResult.Success; - sessionID = "0"; + session.PlayerID = "0"; + session.PlayerName = Settings.Login; } else { - Console.WriteLine("Connecting to Minecraft.net..."); - result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref clientID, ref UUID); + // Validate cached session or login new session. + if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) + { + session = Cache.SessionCache.Get(Settings.Login); + result = ProtocolHandler.GetTokenValidation(session); + + if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") + { + RequestPassword(); + } + + Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); + + } + + if (result != ProtocolHandler.LoginResult.Success) + { + Console.WriteLine("Connecting to Minecraft.net..."); + result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); + + if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) + { + Cache.SessionCache.Store(Settings.Login, session); + } + } + } if (result == ProtocolHandler.LoginResult.Success) { + Settings.Username = session.PlayerName; + if (Settings.ConsoleTitle != "") Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); if (Settings.playerHeadAsIcon) ConsoleIcon.setPlayerIconAsync(Settings.Username); - Console.WriteLine("Success. (session ID: " + sessionID + ')'); + Console.WriteLine("Success. (session ID: " + session.ID + ')'); - //ProtocolHandler.RealmsListWorlds(Settings.Username, UUID, sessionID); //TODO REMOVE + //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE if (Settings.ServerIP == "") { @@ -194,9 +233,9 @@ namespace MinecraftClient //Start the main TCP client if (Settings.SingleCommand != "") { - Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); + Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); } - else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); + else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); //Update console title if (Settings.ConsoleTitle != "") diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index e3e43016..6fa2a5e4 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -141,7 +141,7 @@ namespace MinecraftClient.Protocol } } - public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium }; + public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, NullError }; /// /// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme. @@ -150,19 +150,18 @@ namespace MinecraftClient.Protocol /// Password /// Will contain the access token returned by Minecraft.net, if the login is successful /// Will contain the client token generated before sending to Minecraft.net - /// Will contain the player's UUID, needed for multiplayer + /// Will contain the player's PlayerID, needed for multiplayer /// Returns the status of the login (Success, Failure, etc.) - public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string clienttoken, ref string uuid) + public static LoginResult GetLogin(string user, string pass, out SessionToken session) { + session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") }; + try { - string result = ""; - if (clienttoken == string.Empty) - { - clienttoken = Guid.NewGuid().ToString().Replace("-",""); - } - string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; + string result = ""; + + string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); if (code == 200) { @@ -173,11 +172,11 @@ namespace MinecraftClient.Protocol else { string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { session.ID = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { user = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { session.PlayerName = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { session.PlayerID = temp[1].Split('"')[0]; } return LoginResult.Success; } } @@ -226,33 +225,33 @@ namespace MinecraftClient.Protocol /// Will contain the cached client token created on login /// Returns the status of the token (Valid, Invalid, etc.) /// - public static ValidationResult GetTokenValidation(string accesstoken, string clienttoken) + public static LoginResult GetTokenValidation(SessionToken session) { try { string result = ""; - string json_request = "{\"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\" }"; + string json_request = "{\"accessToken\": \"" + jsonEncode(session.ID) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); if (code == 204) { - return ValidationResult.Validated; + return LoginResult.Success; } else if (code == 403) { - return ValidationResult.NewTokenRequired; + return LoginResult.LoginRequired; } else { - return ValidationResult.Error; + return LoginResult.OtherError; } } catch { - return ValidationResult.Error; + return LoginResult.OtherError; } } - public enum NewTokenResult { Success, InvalidToken, NullError, OtherError} + public enum NewTokenResult { Success, InvalidToken, NullError, OtherError } /// /// Refreshes invalid token @@ -260,45 +259,48 @@ namespace MinecraftClient.Protocol /// Login /// Will contain the new access token returned by Minecraft.net, if the refresh is successful /// Will contain the client token generated before sending to Minecraft.net - /// Will contain the player's UUID, needed for multiplayer + /// Will contain the player's PlayerID, needed for multiplayer /// Returns the status of the new token request (Success, Failure, etc.) /// - public static NewTokenResult GetNewToken(ref string user, ref string accesstoken, ref string clienttoken, ref string uuid) + public static LoginResult GetNewToken(SessionToken currentsession, out SessionToken newsession) { + newsession = new SessionToken(); try { string result = ""; - string json_request = "{ \"accessToken\": \"" + jsonEncode(accesstoken) + "\", \"clientToken\": \"" + jsonEncode(clienttoken) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(uuid) + "\", \"name\": \"" + jsonEncode(user) + "\" } }"; + string json_request = "{ \"accessToken\": \"" + jsonEncode(currentsession.ID) + "\", \"clientToken\": \"" + jsonEncode(currentsession.ClientID) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(currentsession.PlayerID) + "\", \"name\": \"" + jsonEncode(currentsession.PlayerName) + "\" } }"; int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result); if (code == 200) { - if (result == null) { - return NewTokenResult.NullError; - }else{ + if (result == null) + { + return LoginResult.NullError; + } + else { string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { newsession.ID = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { clienttoken = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { newsession.ClientID = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { user = temp[1].Split('"')[0]; } + if (temp.Length >= 2) { newsession.PlayerName = temp[1].Split('"')[0]; } temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; } - return NewTokenResult.Success; + if (temp.Length >= 2) { newsession.PlayerID = temp[1].Split('"')[0]; } + return LoginResult.Success; } } - else if(code == 403 && result.Contains("InvalidToken")) + else if (code == 403 && result.Contains("InvalidToken")) { - return NewTokenResult.InvalidToken; + return LoginResult.InvalidToken; } else { ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code); - return NewTokenResult.OtherError; + return LoginResult.OtherError; } } catch { - return NewTokenResult.OtherError; + return LoginResult.OtherError; } } diff --git a/MinecraftClient/Protocol/SessionToken.cs b/MinecraftClient/Protocol/SessionToken.cs new file mode 100644 index 00000000..2d48a704 --- /dev/null +++ b/MinecraftClient/Protocol/SessionToken.cs @@ -0,0 +1,14 @@ +using System; + + +namespace MinecraftClient.Protocol +{ + [Serializable] + public class SessionToken + { + public string ID { get; set; } = string.Empty; + public string PlayerName { get; set; } = string.Empty; + public string PlayerID { get; set; } = string.Empty; + public string ClientID { get; set; } = string.Empty; + } +} diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 51972956..0d86cdf2 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -30,7 +30,7 @@ namespace MinecraftClient public static string ConsoleTitle = ""; //Cache Settings - public static Cache.AuthCacheHandler.Type CacheType = Cache.AuthCacheHandler.Type.NONE; + public static Cache.CacheType CacheType = Cache.CacheType.NONE; //Proxy Settings public static bool ProxyEnabledLogin = false; @@ -204,9 +204,9 @@ namespace MinecraftClient break; case "accountcache": - if(argValue == "none") { CacheType = Cache.AuthCacheHandler.Type.NONE; } - else if(argValue == "memory") { CacheType = Cache.AuthCacheHandler.Type.MEMORY; } - else if(argValue == "disk") { CacheType = Cache.AuthCacheHandler.Type.DISK; } + if (argValue == "none") { CacheType = Cache.CacheType.NONE; } + else if (argValue == "memory") { CacheType = Cache.CacheType.MEMORY; } + else if (argValue == "disk") { CacheType = Cache.CacheType.DISK; } break; case "accountlist": From fec1687cb7e2a0c7c056e78f20e16665ef6627d5 Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Wed, 2 Mar 2016 18:16:19 -0700 Subject: [PATCH 122/131] Updated docs and cleaned up. --- MinecraftClient/Cache/SessionCache.cs | 288 +++---- MinecraftClient/Program.cs | 828 ++++++++++---------- MinecraftClient/Protocol/ProtocolHandler.cs | 4 - 3 files changed, 563 insertions(+), 557 deletions(-) diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Cache/SessionCache.cs index 3d83be40..9064d232 100644 --- a/MinecraftClient/Cache/SessionCache.cs +++ b/MinecraftClient/Cache/SessionCache.cs @@ -1,139 +1,149 @@ -using MinecraftClient.Protocol; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security.Cryptography; -using System.Text; - -namespace MinecraftClient.Cache -{ - public static class SessionCache - { - const string filename = "cache.bin"; - private static Dictionary sessions = new Dictionary(); - private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); - - private static BinaryFormatter formatter = new BinaryFormatter(); - - public static bool Contains(string login) - { - return sessions.ContainsKey(login); - } - - public static void Store(string login, SessionToken session) - { - if (Contains(login)) - { - sessions[login] = session; - } - else - { - sessions.Add(login, session); - } - - if (Settings.CacheType == CacheType.DISK) - { - SaveToDisk(); - } - } - - public static SessionToken Get(string login) - { - return sessions[login]; - } - - public static bool LoadFromDisk() - { - cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; - cachemonitor.IncludeSubdirectories = false; - cachemonitor.Filter = filename; - cachemonitor.NotifyFilter = NotifyFilters.LastWrite; - cachemonitor.Changed += new FileSystemEventHandler(OnChanged); - cachemonitor.EnableRaisingEvents = true; - - return ReadCacheFile(); - } - - public static void OnChanged(object source, FileSystemEventArgs e) - { - ReadCacheFile(); - } - - private static bool ReadCacheFile() - { - if (File.Exists(filename)) - { - try - { - using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - sessions = (Dictionary)formatter.Deserialize(fs); - return true; - } - } - catch (IOException ex) - { - Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); - } - catch (SerializationException) - { - Console.WriteLine("Error getting sessions from cache file "); - } - } - return false; - } - - public static void SaveToDisk() - { - bool fileexists = File.Exists(filename); - - using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) - { - cachemonitor.EnableRaisingEvents = false; - if (fileexists) - { - fs.SetLength(0); - fs.Flush(); - } - - formatter.Serialize(fs, sessions); - cachemonitor.EnableRaisingEvents = true; - } - - } - - private static byte[] GetHash(FileStream fs, bool resetposition = true) - { - using (var md5 = MD5.Create()) - { - long pos = fs.Position; - byte[] hash = md5.ComputeHash(fs); - - fs.Position = resetposition ? pos : fs.Position; - return hash; - } - } - - private static bool HashesEqual(byte[] hash1, byte[] hash2) - { - if (hash1.Length != hash2.Length) - { - return false; - } - - for (int i = 0; i < hash1.Length; i++) - { - if (hash1[i] != hash2[i]) - { - return false; - } - } - - return true; - } - - } -} +using MinecraftClient.Protocol; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace MinecraftClient.Cache +{ + /// + /// Handle sessions caching and storage. + /// + + public static class SessionCache + { + const string filename = "cache.bin"; + private static Dictionary sessions = new Dictionary(); + private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); + + private static BinaryFormatter formatter = new BinaryFormatter(); + + /// + /// Retrieve whether SessionCache contains a session for the given login. + /// + /// User login used with Minecraft.net + /// TRUE if session is available + + public static bool Contains(string login) + { + return sessions.ContainsKey(login); + } + + /// + /// Store a session and save it to disk if required. + /// + /// User login used with Minecraft.net + /// User session token used with Minecraft.net + + public static void Store(string login, SessionToken session) + { + if (Contains(login)) + { + sessions[login] = session; + } + else + { + sessions.Add(login, session); + } + + if (Settings.CacheType == CacheType.DISK) + { + SaveToDisk(); + } + } + + /// + /// Retrieve a session token for the given login. + /// + /// User login used with Minecraft.net + /// SessionToken for given login + + public static SessionToken Get(string login) + { + return sessions[login]; + } + + /// + /// Initialize cache monitoring to keep cache updated with external changes. + /// + /// TRUE if session tokens are seeded from file + + public static bool InitializeDiskCache() + { + cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; + cachemonitor.IncludeSubdirectories = false; + cachemonitor.Filter = filename; + cachemonitor.NotifyFilter = NotifyFilters.LastWrite; + cachemonitor.Changed += new FileSystemEventHandler(OnChanged); + cachemonitor.EnableRaisingEvents = true; + + return LoadFromDisk(); + } + + /// + /// Reloads cache on external cache file change. + /// + /// Sender + /// Event data + + private static void OnChanged(object source, FileSystemEventArgs e) + { + LoadFromDisk(); + } + + /// + /// Reads cache file and loads SessionTokens into SessionCache. + /// + /// True if data is successfully loaded + + private static bool LoadFromDisk() + { + if (File.Exists(filename)) + { + try + { + using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + sessions = (Dictionary)formatter.Deserialize(fs); + return true; + } + } + catch (IOException ex) + { + Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); + } + catch (SerializationException) + { + Console.WriteLine("Malformed sessions from cache file "); + } + } + return false; + } + + /// + /// Saves SessionToken's from SessionCache into cache file. + /// + + private static void SaveToDisk() + { + bool fileexists = File.Exists(filename); + + using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + cachemonitor.EnableRaisingEvents = false; + + // delete existing file contents + if (fileexists) + { + fs.SetLength(0); + fs.Flush(); + } + + formatter.Serialize(fs, sessions); + cachemonitor.EnableRaisingEvents = true; + } + + } + } +} diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 083e0d29..c8d15486 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -1,414 +1,414 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using MinecraftClient.Protocol; -using System.Reflection; -using System.Threading; -using MinecraftClient.Protocol.Handlers.Forge; - -namespace MinecraftClient -{ - /// - /// Minecraft Console Client by ORelio (c) 2012-2014. - /// Allows to connect to any Minecraft server, send and receive text, automated scripts. - /// This source code is released under the CDDL 1.0 License. - /// - - static class Program - { - private static McTcpClient Client; - public static string[] startupargs; - - public const string Version = "1.8.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; - - /// - /// The main entry point of Minecraft Console Client - /// - - static void Main(string[] args) - { - Console.WriteLine("Console Client for MC {0} to {1} - v{2} - By ORelio & Contributors", MCLowestVersion, MCHighestVersion, Version); - - //Basic Input/Output ? - if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") - { - ConsoleIO.basicIO = true; - Console.OutputEncoding = Console.InputEncoding = Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage); - args = args.Where(o => !Object.ReferenceEquals(o, args[args.Length - 1])).ToArray(); - } - - //Process ini configuration file - if (args.Length >= 1 && System.IO.File.Exists(args[0]) && System.IO.Path.GetExtension(args[0]).ToLower() == ".ini") - { - Settings.LoadSettings(args[0]); - - //remove ini configuration file from arguments array - List args_tmp = args.ToList(); - args_tmp.RemoveAt(0); - args = args_tmp.ToArray(); - } - else if (System.IO.File.Exists("MinecraftClient.ini")) - { - Settings.LoadSettings("MinecraftClient.ini"); - } - else Settings.WriteDefaultSettings("MinecraftClient.ini"); - - //Other command-line arguments - if (args.Length >= 1) - { - Settings.Login = args[0]; - if (args.Length >= 2) - { - Settings.Password = args[1]; - if (args.Length >= 3) - { - Settings.SetServerIP(args[2]); - - //Single command? - if (args.Length >= 4) - { - Settings.SingleCommand = args[3]; - } - } - } - } - - if (Settings.ConsoleTitle != "") - { - Settings.Username = "New Window"; - Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - } - - //Load cached sessions from disk if necessary - if (Settings.CacheType == Cache.CacheType.DISK) - { - ConsoleIO.WriteLineFormatted(Cache.SessionCache.LoadFromDisk() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); - } - - //Asking the user to type in missing data such as Username and Password - - if (Settings.Login == "") - { - Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); - Settings.Login = Console.ReadLine(); - } - if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) - { - RequestPassword(); - } - - startupargs = args; - InitializeClient(); - } - - /// - /// Reduest user to submit password. - /// - private static void RequestPassword() - { - Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); - Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); - if (Settings.Password == "") { Settings.Password = "-"; } - if (!ConsoleIO.basicIO) - { - //Hide password length - Console.CursorTop--; Console.Write("Password : <******>"); - for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } - } - } - - /// - /// Start a new Client - /// - - private static void InitializeClient() - { - SessionToken session = new SessionToken(); - - ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired; - - if (Settings.Password == "-") - { - ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode."); - result = ProtocolHandler.LoginResult.Success; - session.PlayerID = "0"; - session.PlayerName = Settings.Login; - } - else - { - // Validate cached session or login new session. - if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) - { - session = Cache.SessionCache.Get(Settings.Login); - result = ProtocolHandler.GetTokenValidation(session); - - if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") - { - RequestPassword(); - } - - Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); - - } - - if (result != ProtocolHandler.LoginResult.Success) - { - Console.WriteLine("Connecting to Minecraft.net..."); - result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); - - if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) - { - Cache.SessionCache.Store(Settings.Login, session); - } - } - - } - - if (result == ProtocolHandler.LoginResult.Success) - { - Settings.Username = session.PlayerName; - - if (Settings.ConsoleTitle != "") - Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - - if (Settings.playerHeadAsIcon) - ConsoleIcon.setPlayerIconAsync(Settings.Username); - - Console.WriteLine("Success. (session ID: " + session.ID + ')'); - - //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE - - if (Settings.ServerIP == "") - { - Console.Write("Server IP : "); - Settings.SetServerIP(Console.ReadLine()); - } - - //Get server version - int protocolversion = 0; - ForgeInfo forgeInfo = null; - - if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto") - { - protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion); - - if (protocolversion != 0) - { - ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')'); - } - else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode."); - - if (useMcVersionOnce) - { - useMcVersionOnce = false; - Settings.ServerVersion = ""; - } - } - - if (protocolversion == 0) - { - Console.WriteLine("Retrieving Server Info..."); - if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo)) - { - HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); - return; - } - } - - if (forgeInfo != null && !forgeInfo.Mods.Any()) - { - forgeInfo = null; - } - - if (protocolversion != 0) - { - try - { - //Start the main TCP client - if (Settings.SingleCommand != "") - { - Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); - } - else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); - - //Update console title - if (Settings.ConsoleTitle != "") - Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); - } - catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); } - } - else HandleFailure("Failed to determine server version.", true); - } - else - { - Console.ForegroundColor = ConsoleColor.Gray; - string failureMessage = "Minecraft Login failed : "; - switch (result) - { - case ProtocolHandler.LoginResult.AccountMigrated: failureMessage += "Account migrated, use e-mail as username."; break; - case ProtocolHandler.LoginResult.ServiceUnavailable: failureMessage += "Login servers are unavailable. Please try again later."; break; - case ProtocolHandler.LoginResult.WrongPassword: failureMessage += "Incorrect password."; break; - case ProtocolHandler.LoginResult.NotPremium: failureMessage += "User not premium."; break; - case ProtocolHandler.LoginResult.OtherError: failureMessage += "Network error."; break; - case ProtocolHandler.LoginResult.SSLError: failureMessage += "SSL Error."; break; - default: failureMessage += "Unknown Error."; break; - } - if (result == ProtocolHandler.LoginResult.SSLError && isUsingMono) - { - ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program." - + '\n' + "The first time, you have to import HTTPS certificates using:" - + '\n' + "mozroots --import --ask-remove"); - return; - } - HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); - } - } - - /// - /// Disconnect the current client from the server and restart it - /// - - public static void Restart() - { - new Thread(new ThreadStart(delegate - { - if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } - if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } - Console.WriteLine("Restarting Minecraft Console Client..."); - InitializeClient(); - })).Start(); - } - - /// - /// Disconnect the current client from the server and exit the app - /// - - public static void Exit() - { - new Thread(new ThreadStart(delegate - { - if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } - if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } - if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); } - Environment.Exit(0); - })).Start(); - } - - /// - /// Handle fatal errors such as ping failure, login failure, server disconnection, and so on. - /// Allows AutoRelog to perform on fatal errors, prompt for server version, and offline commands. - /// - /// Error message to display and optionally pass to AutoRelog bot - /// Specify if the error is related to an incompatible or unkown server version - /// If set, the error message will be processed by the AutoRelog bot - - public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null) - { - if (!String.IsNullOrEmpty(errorMessage)) - { - ConsoleIO.Reset(); - while (Console.KeyAvailable) - Console.ReadKey(true); - Console.WriteLine(errorMessage); - - if (disconnectReason.HasValue) - { - if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) - return; //AutoRelog is triggering a restart of the client - } - } - - if (Settings.interactiveMode) - { - if (versionError) - { - Console.Write("Server version : "); - Settings.ServerVersion = Console.ReadLine(); - if (Settings.ServerVersion != "") - { - useMcVersionOnce = true; - Restart(); - return; - } - } - - if (offlinePrompt == null) - { - offlinePrompt = new Thread(new ThreadStart(delegate - { - string command = " "; - ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."); - ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client."); - while (command.Length > 0) - { - if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); } - command = Console.ReadLine().Trim(); - if (command.Length > 0) - { - string message = ""; - - if (Settings.internalCmdChar != ' ' - && command[0] == Settings.internalCmdChar) - command = command.Substring(1); - - if (command.StartsWith("reco")) - { - message = new Commands.Reco().Run(null, Settings.ExpandVars(command)); - } - else if (command.StartsWith("connect")) - { - message = new Commands.Connect().Run(null, Settings.ExpandVars(command)); - } - else if (command.StartsWith("exit") || command.StartsWith("quit")) - { - message = new Commands.Exit().Run(null, Settings.ExpandVars(command)); - } - else if (command.StartsWith("help")) - { - ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc); - ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc); - } - else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'."); - - if (message != "") - ConsoleIO.WriteLineFormatted("§8MCC: " + message); - } - } - })); - offlinePrompt.Start(); - } - } - } - - /// - /// Detect if the user is running Minecraft Console Client through Mono - /// - - public static bool isUsingMono - { - get - { - return Type.GetType("Mono.Runtime") != null; - } - } - - /// - /// Enumerate types in namespace through reflection - /// - /// Namespace to process - /// Assembly to use. Default is Assembly.GetExecutingAssembly() - /// - - public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null) - { - if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); } - return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MinecraftClient.Protocol; +using System.Reflection; +using System.Threading; +using MinecraftClient.Protocol.Handlers.Forge; + +namespace MinecraftClient +{ + /// + /// Minecraft Console Client by ORelio (c) 2012-2014. + /// Allows to connect to any Minecraft server, send and receive text, automated scripts. + /// This source code is released under the CDDL 1.0 License. + /// + + static class Program + { + private static McTcpClient Client; + public static string[] startupargs; + + public const string Version = "1.8.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; + + /// + /// The main entry point of Minecraft Console Client + /// + + static void Main(string[] args) + { + Console.WriteLine("Console Client for MC {0} to {1} - v{2} - By ORelio & Contributors", MCLowestVersion, MCHighestVersion, Version); + + //Basic Input/Output ? + if (args.Length >= 1 && args[args.Length - 1] == "BasicIO") + { + ConsoleIO.basicIO = true; + Console.OutputEncoding = Console.InputEncoding = Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage); + args = args.Where(o => !Object.ReferenceEquals(o, args[args.Length - 1])).ToArray(); + } + + //Process ini configuration file + if (args.Length >= 1 && System.IO.File.Exists(args[0]) && System.IO.Path.GetExtension(args[0]).ToLower() == ".ini") + { + Settings.LoadSettings(args[0]); + + //remove ini configuration file from arguments array + List args_tmp = args.ToList(); + args_tmp.RemoveAt(0); + args = args_tmp.ToArray(); + } + else if (System.IO.File.Exists("MinecraftClient.ini")) + { + Settings.LoadSettings("MinecraftClient.ini"); + } + else Settings.WriteDefaultSettings("MinecraftClient.ini"); + + //Other command-line arguments + if (args.Length >= 1) + { + Settings.Login = args[0]; + if (args.Length >= 2) + { + Settings.Password = args[1]; + if (args.Length >= 3) + { + Settings.SetServerIP(args[2]); + + //Single command? + if (args.Length >= 4) + { + Settings.SingleCommand = args[3]; + } + } + } + } + + if (Settings.ConsoleTitle != "") + { + Settings.Username = "New Window"; + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); + } + + //Load cached sessions from disk if necessary + if (Settings.CacheType == Cache.CacheType.DISK) + { + Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); + } + + //Asking the user to type in missing data such as Username and Password + + if (Settings.Login == "") + { + Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); + Settings.Login = Console.ReadLine(); + } + if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) + { + RequestPassword(); + } + + startupargs = args; + InitializeClient(); + } + + /// + /// Reduest user to submit password. + /// + private static void RequestPassword() + { + Console.Write(ConsoleIO.basicIO ? "Please type the password for " + Settings.Login + ".\n" : "Password : "); + Settings.Password = ConsoleIO.basicIO ? Console.ReadLine() : ConsoleIO.ReadPassword(); + if (Settings.Password == "") { Settings.Password = "-"; } + if (!ConsoleIO.basicIO) + { + //Hide password length + Console.CursorTop--; Console.Write("Password : <******>"); + for (int i = 19; i < Console.BufferWidth; i++) { Console.Write(' '); } + } + } + + /// + /// Start a new Client + /// + + private static void InitializeClient() + { + SessionToken session = new SessionToken(); + + ProtocolHandler.LoginResult result = ProtocolHandler.LoginResult.LoginRequired; + + if (Settings.Password == "-") + { + ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode."); + result = ProtocolHandler.LoginResult.Success; + session.PlayerID = "0"; + session.PlayerName = Settings.Login; + } + else + { + // Validate cached session or login new session. + if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) + { + session = Cache.SessionCache.Get(Settings.Login); + result = ProtocolHandler.GetTokenValidation(session); + + if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") + { + RequestPassword(); + } + + Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); + + } + + if (result != ProtocolHandler.LoginResult.Success) + { + Console.WriteLine("Connecting to Minecraft.net..."); + result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); + + if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) + { + Cache.SessionCache.Store(Settings.Login, session); + } + } + + } + + if (result == ProtocolHandler.LoginResult.Success) + { + Settings.Username = session.PlayerName; + + if (Settings.ConsoleTitle != "") + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); + + if (Settings.playerHeadAsIcon) + ConsoleIcon.setPlayerIconAsync(Settings.Username); + + Console.WriteLine("Success. (session ID: " + session.ID + ')'); + + //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE + + if (Settings.ServerIP == "") + { + Console.Write("Server IP : "); + Settings.SetServerIP(Console.ReadLine()); + } + + //Get server version + int protocolversion = 0; + ForgeInfo forgeInfo = null; + + if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto") + { + protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion); + + if (protocolversion != 0) + { + ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')'); + } + else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode."); + + if (useMcVersionOnce) + { + useMcVersionOnce = false; + Settings.ServerVersion = ""; + } + } + + if (protocolversion == 0) + { + Console.WriteLine("Retrieving Server Info..."); + if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion, ref forgeInfo)) + { + HandleFailure("Failed to ping this IP.", true, ChatBots.AutoRelog.DisconnectReason.ConnectionLost); + return; + } + } + + if (forgeInfo != null && !forgeInfo.Mods.Any()) + { + forgeInfo = null; + } + + if (protocolversion != 0) + { + try + { + //Start the main TCP client + if (Settings.SingleCommand != "") + { + Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, Settings.ServerIP, Settings.ServerPort, protocolversion, forgeInfo, Settings.SingleCommand); + } + else Client = new McTcpClient(session.PlayerName, session.PlayerID, session.ID, protocolversion, forgeInfo, Settings.ServerIP, Settings.ServerPort); + + //Update console title + if (Settings.ConsoleTitle != "") + Console.Title = Settings.ExpandVars(Settings.ConsoleTitle); + } + catch (NotSupportedException) { HandleFailure("Cannot connect to the server : This version is not supported !", true); } + } + else HandleFailure("Failed to determine server version.", true); + } + else + { + Console.ForegroundColor = ConsoleColor.Gray; + string failureMessage = "Minecraft Login failed : "; + switch (result) + { + case ProtocolHandler.LoginResult.AccountMigrated: failureMessage += "Account migrated, use e-mail as username."; break; + case ProtocolHandler.LoginResult.ServiceUnavailable: failureMessage += "Login servers are unavailable. Please try again later."; break; + case ProtocolHandler.LoginResult.WrongPassword: failureMessage += "Incorrect password."; break; + case ProtocolHandler.LoginResult.NotPremium: failureMessage += "User not premium."; break; + case ProtocolHandler.LoginResult.OtherError: failureMessage += "Network error."; break; + case ProtocolHandler.LoginResult.SSLError: failureMessage += "SSL Error."; break; + default: failureMessage += "Unknown Error."; break; + } + if (result == ProtocolHandler.LoginResult.SSLError && isUsingMono) + { + ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program." + + '\n' + "The first time, you have to import HTTPS certificates using:" + + '\n' + "mozroots --import --ask-remove"); + return; + } + HandleFailure(failureMessage, false, ChatBot.DisconnectReason.LoginRejected); + } + } + + /// + /// Disconnect the current client from the server and restart it + /// + + public static void Restart() + { + new Thread(new ThreadStart(delegate + { + if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } + if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } + Console.WriteLine("Restarting Minecraft Console Client..."); + InitializeClient(); + })).Start(); + } + + /// + /// Disconnect the current client from the server and exit the app + /// + + public static void Exit() + { + new Thread(new ThreadStart(delegate + { + if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); } + if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); } + if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); } + Environment.Exit(0); + })).Start(); + } + + /// + /// Handle fatal errors such as ping failure, login failure, server disconnection, and so on. + /// Allows AutoRelog to perform on fatal errors, prompt for server version, and offline commands. + /// + /// Error message to display and optionally pass to AutoRelog bot + /// Specify if the error is related to an incompatible or unkown server version + /// If set, the error message will be processed by the AutoRelog bot + + public static void HandleFailure(string errorMessage = null, bool versionError = false, ChatBots.AutoRelog.DisconnectReason? disconnectReason = null) + { + if (!String.IsNullOrEmpty(errorMessage)) + { + ConsoleIO.Reset(); + while (Console.KeyAvailable) + Console.ReadKey(true); + Console.WriteLine(errorMessage); + + if (disconnectReason.HasValue) + { + if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) + return; //AutoRelog is triggering a restart of the client + } + } + + if (Settings.interactiveMode) + { + if (versionError) + { + Console.Write("Server version : "); + Settings.ServerVersion = Console.ReadLine(); + if (Settings.ServerVersion != "") + { + useMcVersionOnce = true; + Restart(); + return; + } + } + + if (offlinePrompt == null) + { + offlinePrompt = new Thread(new ThreadStart(delegate + { + string command = " "; + ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."); + ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client."); + while (command.Length > 0) + { + if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); } + command = Console.ReadLine().Trim(); + if (command.Length > 0) + { + string message = ""; + + if (Settings.internalCmdChar != ' ' + && command[0] == Settings.internalCmdChar) + command = command.Substring(1); + + if (command.StartsWith("reco")) + { + message = new Commands.Reco().Run(null, Settings.ExpandVars(command)); + } + else if (command.StartsWith("connect")) + { + message = new Commands.Connect().Run(null, Settings.ExpandVars(command)); + } + else if (command.StartsWith("exit") || command.StartsWith("quit")) + { + message = new Commands.Exit().Run(null, Settings.ExpandVars(command)); + } + else if (command.StartsWith("help")) + { + ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc); + ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc); + } + else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'."); + + if (message != "") + ConsoleIO.WriteLineFormatted("§8MCC: " + message); + } + } + })); + offlinePrompt.Start(); + } + } + } + + /// + /// Detect if the user is running Minecraft Console Client through Mono + /// + + public static bool isUsingMono + { + get + { + return Type.GetType("Mono.Runtime") != null; + } + } + + /// + /// Enumerate types in namespace through reflection + /// + /// Namespace to process + /// Assembly to use. Default is Assembly.GetExecutingAssembly() + /// + + public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null) + { + if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); } + return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray(); + } + } +} diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 6fa2a5e4..6adad9ed 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -216,8 +216,6 @@ namespace MinecraftClient.Protocol } } - public enum ValidationResult { Validated, NewTokenRequired, Error }; - /// /// Validates whether accessToken must be refreshed /// @@ -250,8 +248,6 @@ namespace MinecraftClient.Protocol return LoginResult.OtherError; } } - - public enum NewTokenResult { Success, InvalidToken, NullError, OtherError } /// /// Refreshes invalid token From 98b3ce7304e1b059056f7f615933ace26f5da21d Mon Sep 17 00:00:00 2001 From: Justin Slauson Date: Wed, 2 Mar 2016 19:08:24 -0700 Subject: [PATCH 123/131] added timer to reduce file access collisions and cleaned some text up. --- MinecraftClient/Cache/SessionCache.cs | 31 ++++++++++++++++++++++++--- MinecraftClient/Program.cs | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Cache/SessionCache.cs index 9064d232..a943958b 100644 --- a/MinecraftClient/Cache/SessionCache.cs +++ b/MinecraftClient/Cache/SessionCache.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; +using System.Timers; namespace MinecraftClient.Cache { @@ -16,6 +17,8 @@ namespace MinecraftClient.Cache const string filename = "cache.bin"; private static Dictionary sessions = new Dictionary(); private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); + private static Timer updatetimer = new Timer(100); + private static List> pendingadds = new List>(); private static BinaryFormatter formatter = new BinaryFormatter(); @@ -47,7 +50,9 @@ namespace MinecraftClient.Cache sessions.Add(login, session); } - if (Settings.CacheType == CacheType.DISK) + if (Settings.CacheType == CacheType.DISK && updatetimer.Enabled == true) { + pendingadds.Add(new KeyValuePair(login, session)); + }else if (Settings.CacheType == CacheType.DISK) { SaveToDisk(); } @@ -78,18 +83,38 @@ namespace MinecraftClient.Cache cachemonitor.Changed += new FileSystemEventHandler(OnChanged); cachemonitor.EnableRaisingEvents = true; + updatetimer.Elapsed += HandlePending; + return LoadFromDisk(); } /// /// Reloads cache on external cache file change. /// - /// Sender + /// Sender /// Event data - private static void OnChanged(object source, FileSystemEventArgs e) + private static void OnChanged(object sender, FileSystemEventArgs e) + { + updatetimer.Stop(); + updatetimer.Start(); + } + + /// + /// Called after timer elapsed. Reads disk cache and adds new/modified sessions back. + /// + /// Sender + /// Event data + + private static void HandlePending(object sender, ElapsedEventArgs e) { LoadFromDisk(); + + foreach(KeyValuePair pending in pendingadds.ToArray()) + { + Store(pending.Key, pending.Value); + pendingadds.Remove(pending); + } } /// diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index c8d15486..402046e4 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -88,7 +88,7 @@ namespace MinecraftClient //Load cached sessions from disk if necessary if (Settings.CacheType == Cache.CacheType.DISK) { - Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "§8Cached sessions could not be loaded from disk"); + Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "Cached sessions could not be loaded from disk"); } //Asking the user to type in missing data such as Username and Password From fc9adf902eef02e63b992245ca4b771a01126a2c Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 3 Mar 2016 12:07:18 +0100 Subject: [PATCH 124/131] Add sample script with world access See #123 --- .../config/sample-script-with-world-access.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 MinecraftClient/config/sample-script-with-world-access.cs diff --git a/MinecraftClient/config/sample-script-with-world-access.cs b/MinecraftClient/config/sample-script-with-world-access.cs new file mode 100644 index 00000000..cfa5f915 --- /dev/null +++ b/MinecraftClient/config/sample-script-with-world-access.cs @@ -0,0 +1,61 @@ +//MCCScript 1.0 + +MCC.LoadBot(new WatchLamp()); + +//MCCScript Extensions + +/* The ChatBot will access the world on a regular basis to watch for a lamp. + * This is an example of how the world around the player can be accessed from a C# script. */ + +class WatchLamp : ChatBot +{ + /* == CONFIG == */ + + int lampX = 0; + int lampY = 64; + int lampZ = 0; + + /* == CODE == */ + + int checkCount = 0; + Location lampLoc; + + public WatchLamp() + { + if (!Settings.TerrainAndMovements) + { + LogToConsole("WARNING: Terrain handling is disabled in INI file."); + LogToConsole("WARNING: This means this bot cannot watch for lamps."); + UnloadBot(); + } + else + { + lampLoc = new Location(lampX, lampY, lampZ); + LogToConsole("Watching lamp at " + lampLoc); + } + } + + public override void Update() + { + if (checkCount > 10) + { + checkCount = 0; + Material blockType = GetWorld().GetBlock(lampLoc).Type; + switch (blockType) + { + case Material.RedstoneLampOn: + //Lamp is on. All right. Nothing to say here. + break; + case Material.RedstoneLampOff: + LogToConsole("Lamp at " + lampLoc + " is currently turned OFF !!!"); + for (int i = 0; i < 3; i++) + Console.Beep(); + break; + default: + LogToConsole("Block at " + lampLoc + " is not a lamp: " + blockType + "..."); + break; + } + } + else checkCount++; + } +} \ No newline at end of file From 578a6170efdd1e5930268ec80f0e3a946d010b4e Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 5 Mar 2016 19:10:13 +0100 Subject: [PATCH 125/131] Lower .NET requirement for Session Token Changing constructs that weren't .NET 4.0 compliant. Also fix \n to \r\n line returns in ProtocolHandler.cs --- MinecraftClient/Protocol/ProtocolHandler.cs | 844 ++++++++++---------- MinecraftClient/Protocol/SessionToken.cs | 17 +- 2 files changed, 434 insertions(+), 427 deletions(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 6adad9ed..d36834a5 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -1,437 +1,437 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using MinecraftClient.Protocol.Handlers; -using MinecraftClient.Proxy; -using System.Net.Sockets; -using System.Net.Security; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MinecraftClient.Protocol.Handlers; +using MinecraftClient.Proxy; +using System.Net.Sockets; +using System.Net.Security; using MinecraftClient.Protocol.Handlers.Forge; - - -namespace MinecraftClient.Protocol -{ - /// - /// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server. - /// - - public static class ProtocolHandler - { - /// - /// Retrieve information about a Minecraft server - /// - /// Server IP to ping - /// Server Port to ping - /// Will contain protocol version, if ping successful - /// TRUE if ping was successful - - public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion, ref ForgeInfo forgeInfo) - { - bool success = false; - int protocolversionTmp = 0; - ForgeInfo forgeInfoTmp = null; - if (AutoTimeout.Perform(() => - { - try - { - if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp) - || Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp, ref forgeInfoTmp)) - { - success = true; - } - else ConsoleIO.WriteLineFormatted("§8Unexpected response from the server (is that a Minecraft server?)"); - } - catch (Exception e) - { - ConsoleIO.WriteLineFormatted(String.Format("§8{0}: {1}", e.GetType().FullName, e.Message)); - } - }, TimeSpan.FromSeconds(30))) - { - protocolversion = protocolversionTmp; - forgeInfo = forgeInfoTmp; - return success; - } - else - { - ConsoleIO.WriteLineFormatted("§8A timeout occured while attempting to connect to this IP."); - return false; - } - } - - /// - /// Get a protocol handler for the specified Minecraft version - /// - /// Tcp Client connected to the server - /// Protocol version to handle - /// Handler with the appropriate callbacks - /// - - public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, ForgeInfo forgeInfo, IMinecraftComHandler Handler) - { - int[] supportedVersions_Protocol16 = { 51, 60, 61, 72, 73, 74, 78 }; - if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1) - return new Protocol16Handler(Client, ProtocolVersion, Handler); - int[] supportedVersions_Protocol18 = { 4, 5, 47 }; - if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1) - return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo); - throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported."); - } - - /// - /// Convert a human-readable Minecraft version number to network protocol version number - /// - /// The Minecraft version number - /// The protocol version number or 0 if could not determine protocol version: error, unknown, not supported - - public static int MCVer2ProtocolVersion(string MCVersion) - { - if (MCVersion.Contains('.')) - { - switch (MCVersion.Split(' ')[0].Trim()) - { - case "1.4.6": - case "1.4.7": - return 51; - case "1.5.1": - return 60; - case "1.5.2": - return 61; - case "1.6.0": - return 72; - case "1.6.1": - case "1.6.2": - case "1.6.3": - case "1.6.4": - return 73; - case "1.7.2": - case "1.7.3": - case "1.7.4": - case "1.7.5": - return 4; - case "1.7.6": - case "1.7.7": - case "1.7.8": - case "1.7.9": - case "1.7.10": - return 5; - case "1.8.0": - case "1.8.1": - case "1.8.2": - case "1.8.3": - case "1.8.4": - case "1.8.5": - case "1.8.6": - case "1.8.7": - case "1.8.8": - return 47; - default: - return 0; - } - } - else - { - try - { - return Int32.Parse(MCVersion); - } - catch - { - return 0; - } - } - } - - public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, NullError }; - - /// - /// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme. - /// - /// Login - /// Password - /// Will contain the access token returned by Minecraft.net, if the login is successful - /// Will contain the client token generated before sending to Minecraft.net - /// Will contain the player's PlayerID, needed for multiplayer - /// Returns the status of the login (Success, Failure, etc.) - - public static LoginResult GetLogin(string user, string pass, out SessionToken session) - { - session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") }; - - try - { + + +namespace MinecraftClient.Protocol +{ + /// + /// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server. + /// + + public static class ProtocolHandler + { + /// + /// Retrieve information about a Minecraft server + /// + /// Server IP to ping + /// Server Port to ping + /// Will contain protocol version, if ping successful + /// TRUE if ping was successful + + public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion, ref ForgeInfo forgeInfo) + { + bool success = false; + int protocolversionTmp = 0; + ForgeInfo forgeInfoTmp = null; + if (AutoTimeout.Perform(() => + { + try + { + if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversionTmp) + || Protocol18Handler.doPing(serverIP, serverPort, ref protocolversionTmp, ref forgeInfoTmp)) + { + success = true; + } + else ConsoleIO.WriteLineFormatted("§8Unexpected response from the server (is that a Minecraft server?)"); + } + catch (Exception e) + { + ConsoleIO.WriteLineFormatted(String.Format("§8{0}: {1}", e.GetType().FullName, e.Message)); + } + }, TimeSpan.FromSeconds(30))) + { + protocolversion = protocolversionTmp; + forgeInfo = forgeInfoTmp; + return success; + } + else + { + ConsoleIO.WriteLineFormatted("§8A timeout occured while attempting to connect to this IP."); + return false; + } + } + + /// + /// Get a protocol handler for the specified Minecraft version + /// + /// Tcp Client connected to the server + /// Protocol version to handle + /// Handler with the appropriate callbacks + /// + + public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, ForgeInfo forgeInfo, IMinecraftComHandler Handler) + { + int[] supportedVersions_Protocol16 = { 51, 60, 61, 72, 73, 74, 78 }; + if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1) + return new Protocol16Handler(Client, ProtocolVersion, Handler); + int[] supportedVersions_Protocol18 = { 4, 5, 47 }; + if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1) + return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo); + throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported."); + } + + /// + /// Convert a human-readable Minecraft version number to network protocol version number + /// + /// The Minecraft version number + /// The protocol version number or 0 if could not determine protocol version: error, unknown, not supported + + public static int MCVer2ProtocolVersion(string MCVersion) + { + if (MCVersion.Contains('.')) + { + switch (MCVersion.Split(' ')[0].Trim()) + { + case "1.4.6": + case "1.4.7": + return 51; + case "1.5.1": + return 60; + case "1.5.2": + return 61; + case "1.6.0": + return 72; + case "1.6.1": + case "1.6.2": + case "1.6.3": + case "1.6.4": + return 73; + case "1.7.2": + case "1.7.3": + case "1.7.4": + case "1.7.5": + return 4; + case "1.7.6": + case "1.7.7": + case "1.7.8": + case "1.7.9": + case "1.7.10": + return 5; + case "1.8.0": + case "1.8.1": + case "1.8.2": + case "1.8.3": + case "1.8.4": + case "1.8.5": + case "1.8.6": + case "1.8.7": + case "1.8.8": + return 47; + default: + return 0; + } + } + else + { + try + { + return Int32.Parse(MCVersion); + } + catch + { + return 0; + } + } + } + + public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, NullError }; + + /// + /// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme. + /// + /// Login + /// Password + /// Will contain the access token returned by Minecraft.net, if the login is successful + /// Will contain the client token generated before sending to Minecraft.net + /// Will contain the player's PlayerID, needed for multiplayer + /// Returns the status of the login (Success, Failure, etc.) + + public static LoginResult GetLogin(string user, string pass, out SessionToken session) + { + session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") }; + + try + { string result = ""; - string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; - int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); - if (code == 200) - { - if (result.Contains("availableProfiles\":[]}")) - { - return LoginResult.NotPremium; - } - else - { - string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { session.ID = temp[1].Split('"')[0]; } - temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { session.PlayerName = temp[1].Split('"')[0]; } - temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { session.PlayerID = temp[1].Split('"')[0]; } - return LoginResult.Success; - } - } - else if (code == 403) - { - if (result.Contains("UserMigratedException")) - { - return LoginResult.AccountMigrated; - } - else return LoginResult.WrongPassword; - } - else if (code == 503) - { - return LoginResult.ServiceUnavailable; - } - else - { - ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code); - return LoginResult.OtherError; - } - } - catch (System.Security.Authentication.AuthenticationException) - { - return LoginResult.SSLError; - } - catch (System.IO.IOException e) - { - if (e.Message.Contains("authentication")) - { - return LoginResult.SSLError; - } - else return LoginResult.OtherError; - } - catch - { - return LoginResult.OtherError; - } - } - - /// - /// Validates whether accessToken must be refreshed - /// - /// Will contain the cached access token previously returned by Minecraft.net - /// Will contain the cached client token created on login - /// Returns the status of the token (Valid, Invalid, etc.) - /// + string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; + int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result); + if (code == 200) + { + if (result.Contains("availableProfiles\":[]}")) + { + return LoginResult.NotPremium; + } + else + { + string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { session.ID = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { session.PlayerName = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { session.PlayerID = temp[1].Split('"')[0]; } + return LoginResult.Success; + } + } + else if (code == 403) + { + if (result.Contains("UserMigratedException")) + { + return LoginResult.AccountMigrated; + } + else return LoginResult.WrongPassword; + } + else if (code == 503) + { + return LoginResult.ServiceUnavailable; + } + else + { + ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code); + return LoginResult.OtherError; + } + } + catch (System.Security.Authentication.AuthenticationException) + { + return LoginResult.SSLError; + } + catch (System.IO.IOException e) + { + if (e.Message.Contains("authentication")) + { + return LoginResult.SSLError; + } + else return LoginResult.OtherError; + } + catch + { + return LoginResult.OtherError; + } + } + + /// + /// Validates whether accessToken must be refreshed + /// + /// Will contain the cached access token previously returned by Minecraft.net + /// Will contain the cached client token created on login + /// Returns the status of the token (Valid, Invalid, etc.) + /// public static LoginResult GetTokenValidation(SessionToken session) { - try - { - string result = ""; - string json_request = "{\"accessToken\": \"" + jsonEncode(session.ID) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; - int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); - if (code == 204) - { - return LoginResult.Success; - } - else if (code == 403) - { - return LoginResult.LoginRequired; + try + { + string result = ""; + string json_request = "{\"accessToken\": \"" + jsonEncode(session.ID) + "\", \"clientToken\": \"" + jsonEncode(session.ClientID) + "\" }"; + int code = doHTTPSPost("authserver.mojang.com", "/validate", json_request, ref result); + if (code == 204) + { + return LoginResult.Success; + } + else if (code == 403) + { + return LoginResult.LoginRequired; } else { return LoginResult.OtherError; - } - } - catch - { - return LoginResult.OtherError; - } - } - - /// - /// Refreshes invalid token - /// - /// Login - /// Will contain the new access token returned by Minecraft.net, if the refresh is successful - /// Will contain the client token generated before sending to Minecraft.net - /// Will contain the player's PlayerID, needed for multiplayer - /// Returns the status of the new token request (Success, Failure, etc.) - /// - public static LoginResult GetNewToken(SessionToken currentsession, out SessionToken newsession) - { - newsession = new SessionToken(); - try - { - string result = ""; - string json_request = "{ \"accessToken\": \"" + jsonEncode(currentsession.ID) + "\", \"clientToken\": \"" + jsonEncode(currentsession.ClientID) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(currentsession.PlayerID) + "\", \"name\": \"" + jsonEncode(currentsession.PlayerName) + "\" } }"; - int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result); - if (code == 200) - { - if (result == null) - { - return LoginResult.NullError; - } - else { - string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { newsession.ID = temp[1].Split('"')[0]; } - temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { newsession.ClientID = temp[1].Split('"')[0]; } - temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { newsession.PlayerName = temp[1].Split('"')[0]; } - temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); - if (temp.Length >= 2) { newsession.PlayerID = temp[1].Split('"')[0]; } - return LoginResult.Success; - } - } - else if (code == 403 && result.Contains("InvalidToken")) - { - return LoginResult.InvalidToken; - } - else - { - ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code); - return LoginResult.OtherError; } } catch { return LoginResult.OtherError; } - } - - /// - /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server - /// - /// Username - /// Session ID - /// Server ID - /// TRUE if session was successfully checked - - public static bool SessionCheck(string uuid, string accesstoken, string serverhash) - { - try - { - string result = ""; - string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}"; - int code = doHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request, ref result); - return (result == ""); - } - catch { return false; } - } - - public static void RealmsListWorlds(string username, string uuid, string accesstoken) - { - string result = ""; - string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username, Program.MCHighestVersion); - doHTTPSGet("mcoapi.minecraft.net", "/worlds", cookies, ref result); - Console.WriteLine(result); - } - - /// - /// Make a HTTPS GET request to the specified endpoint of the Mojang API - /// - /// Host to connect to - /// Endpoint for making the request - /// Cookies for making the request - /// Request result - /// HTTP Status code - - private static int doHTTPSGet(string host, string endpoint, string cookies, ref string result) - { - List http_request = new List(); - http_request.Add("GET " + endpoint + " HTTP/1.1"); - http_request.Add("Cookie: " + cookies); - http_request.Add("Cache-Control: no-cache"); - http_request.Add("Pragma: no-cache"); - http_request.Add("Host: " + host); - http_request.Add("User-Agent: Java/1.6.0_27"); - http_request.Add("Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7"); - http_request.Add("Connection: close"); - http_request.Add(""); - return doHTTPSRequest(http_request, host, ref result); - } - - /// - /// Make a HTTPS POST request to the specified endpoint of the Mojang API - /// - /// Host to connect to - /// Endpoint for making the request - /// Request payload - /// Request result - /// HTTP Status code - - private static int doHTTPSPost(string host, string endpoint, string request, ref string result) - { - List http_request = new List(); - http_request.Add("POST " + endpoint + " HTTP/1.1"); - http_request.Add("Host: " + host); - http_request.Add("User-Agent: MCC/" + Program.Version); - http_request.Add("Content-Type: application/json"); - http_request.Add("Content-Length: " + Encoding.ASCII.GetBytes(request).Length); - http_request.Add("Connection: close"); - http_request.Add(""); - http_request.Add(request); - return doHTTPSRequest(http_request, host, ref result); - } - - /// - /// Manual HTTPS request since we must directly use a TcpClient because of the proxy. - /// This method connects to the server, enables SSL, do the request and read the response. - /// - /// Request headers and optional body (POST) - /// Host to connect to - /// Request result - /// HTTP Status code - - private static int doHTTPSRequest(List headers, string host, ref string result) - { - string postResult = null; - int statusCode = 520; - AutoTimeout.Perform(() => - { - TcpClient client = ProxyHandler.newTcpClient(host, 443, true); - SslStream stream = new SslStream(client.GetStream()); - stream.AuthenticateAsClient(host); - stream.Write(Encoding.ASCII.GetBytes(String.Join("\r\n", headers.ToArray()))); - System.IO.StreamReader sr = new System.IO.StreamReader(stream); - string raw_result = sr.ReadToEnd(); - if (raw_result.StartsWith("HTTP/1.1")) - { - postResult = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4); - statusCode = Settings.str2int(raw_result.Split(' ')[1]); - } - else statusCode = 520; //Web server is returning an unknown error - }, TimeSpan.FromSeconds(30)); - result = postResult; - return statusCode; - } - - /// - /// Encode a string to a json string. - /// Will convert special chars to \u0000 unicode escape sequences. - /// - /// Source text - /// Encoded text - - private static string jsonEncode(string text) - { - StringBuilder result = new StringBuilder(); - foreach (char c in text) - { - if ((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z')) - { - result.Append(c); - } - else - { - result.AppendFormat(@"\u{0:x4}", (int)c); - } - } - - return result.ToString(); - } - } -} + } + + /// + /// Refreshes invalid token + /// + /// Login + /// Will contain the new access token returned by Minecraft.net, if the refresh is successful + /// Will contain the client token generated before sending to Minecraft.net + /// Will contain the player's PlayerID, needed for multiplayer + /// Returns the status of the new token request (Success, Failure, etc.) + /// + public static LoginResult GetNewToken(SessionToken currentsession, out SessionToken newsession) + { + newsession = new SessionToken(); + try + { + string result = ""; + string json_request = "{ \"accessToken\": \"" + jsonEncode(currentsession.ID) + "\", \"clientToken\": \"" + jsonEncode(currentsession.ClientID) + "\", \"selectedProfile\": { \"id\": \"" + jsonEncode(currentsession.PlayerID) + "\", \"name\": \"" + jsonEncode(currentsession.PlayerName) + "\" } }"; + int code = doHTTPSPost("authserver.mojang.com", "/refresh", json_request, ref result); + if (code == 200) + { + if (result == null) + { + return LoginResult.NullError; + } + else { + string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { newsession.ID = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "clientToken\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { newsession.ClientID = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { newsession.PlayerName = temp[1].Split('"')[0]; } + temp = result.Split(new string[] { "selectedProfile\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries); + if (temp.Length >= 2) { newsession.PlayerID = temp[1].Split('"')[0]; } + return LoginResult.Success; + } + } + else if (code == 403 && result.Contains("InvalidToken")) + { + return LoginResult.InvalidToken; + } + else + { + ConsoleIO.WriteLineFormatted("§8Got error code from server while refreshing authentication: " + code); + return LoginResult.OtherError; + } + } + catch + { + return LoginResult.OtherError; + } + } + + /// + /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server + /// + /// Username + /// Session ID + /// Server ID + /// TRUE if session was successfully checked + + public static bool SessionCheck(string uuid, string accesstoken, string serverhash) + { + try + { + string result = ""; + string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}"; + int code = doHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request, ref result); + return (result == ""); + } + catch { return false; } + } + + public static void RealmsListWorlds(string username, string uuid, string accesstoken) + { + string result = ""; + string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username, Program.MCHighestVersion); + doHTTPSGet("mcoapi.minecraft.net", "/worlds", cookies, ref result); + Console.WriteLine(result); + } + + /// + /// Make a HTTPS GET request to the specified endpoint of the Mojang API + /// + /// Host to connect to + /// Endpoint for making the request + /// Cookies for making the request + /// Request result + /// HTTP Status code + + private static int doHTTPSGet(string host, string endpoint, string cookies, ref string result) + { + List http_request = new List(); + http_request.Add("GET " + endpoint + " HTTP/1.1"); + http_request.Add("Cookie: " + cookies); + http_request.Add("Cache-Control: no-cache"); + http_request.Add("Pragma: no-cache"); + http_request.Add("Host: " + host); + http_request.Add("User-Agent: Java/1.6.0_27"); + http_request.Add("Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7"); + http_request.Add("Connection: close"); + http_request.Add(""); + return doHTTPSRequest(http_request, host, ref result); + } + + /// + /// Make a HTTPS POST request to the specified endpoint of the Mojang API + /// + /// Host to connect to + /// Endpoint for making the request + /// Request payload + /// Request result + /// HTTP Status code + + private static int doHTTPSPost(string host, string endpoint, string request, ref string result) + { + List http_request = new List(); + http_request.Add("POST " + endpoint + " HTTP/1.1"); + http_request.Add("Host: " + host); + http_request.Add("User-Agent: MCC/" + Program.Version); + http_request.Add("Content-Type: application/json"); + http_request.Add("Content-Length: " + Encoding.ASCII.GetBytes(request).Length); + http_request.Add("Connection: close"); + http_request.Add(""); + http_request.Add(request); + return doHTTPSRequest(http_request, host, ref result); + } + + /// + /// Manual HTTPS request since we must directly use a TcpClient because of the proxy. + /// This method connects to the server, enables SSL, do the request and read the response. + /// + /// Request headers and optional body (POST) + /// Host to connect to + /// Request result + /// HTTP Status code + + private static int doHTTPSRequest(List headers, string host, ref string result) + { + string postResult = null; + int statusCode = 520; + AutoTimeout.Perform(() => + { + TcpClient client = ProxyHandler.newTcpClient(host, 443, true); + SslStream stream = new SslStream(client.GetStream()); + stream.AuthenticateAsClient(host); + stream.Write(Encoding.ASCII.GetBytes(String.Join("\r\n", headers.ToArray()))); + System.IO.StreamReader sr = new System.IO.StreamReader(stream); + string raw_result = sr.ReadToEnd(); + if (raw_result.StartsWith("HTTP/1.1")) + { + postResult = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4); + statusCode = Settings.str2int(raw_result.Split(' ')[1]); + } + else statusCode = 520; //Web server is returning an unknown error + }, TimeSpan.FromSeconds(30)); + result = postResult; + return statusCode; + } + + /// + /// Encode a string to a json string. + /// Will convert special chars to \u0000 unicode escape sequences. + /// + /// Source text + /// Encoded text + + private static string jsonEncode(string text) + { + StringBuilder result = new StringBuilder(); + foreach (char c in text) + { + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z')) + { + result.Append(c); + } + else + { + result.AppendFormat(@"\u{0:x4}", (int)c); + } + } + + return result.ToString(); + } + } +} diff --git a/MinecraftClient/Protocol/SessionToken.cs b/MinecraftClient/Protocol/SessionToken.cs index 2d48a704..d00824f9 100644 --- a/MinecraftClient/Protocol/SessionToken.cs +++ b/MinecraftClient/Protocol/SessionToken.cs @@ -1,14 +1,21 @@ using System; - namespace MinecraftClient.Protocol { [Serializable] public class SessionToken { - public string ID { get; set; } = string.Empty; - public string PlayerName { get; set; } = string.Empty; - public string PlayerID { get; set; } = string.Empty; - public string ClientID { get; set; } = string.Empty; + public string ID { get; set; } + public string PlayerName { get; set; } + public string PlayerID { get; set; } + public string ClientID { get; set; } + + public SessionToken() + { + ID = String.Empty; + PlayerName = String.Empty; + PlayerID = String.Empty; + ClientID = String.Empty; + } } } From a82c6823af914a7a1fc1e4a1d2f3f81801fd54fa Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 5 Mar 2016 19:12:43 +0100 Subject: [PATCH 126/131] Add support for Minecraft 1.9 Currently no terrain handling but anything else should work. Related: #125 --- MinecraftClient/Program.cs | 6 +- .../Protocol/Handlers/Protocol16.cs | 3 + .../Protocol/Handlers/Protocol18.cs | 161 ++++++++++++++---- 3 files changed, 137 insertions(+), 33 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 402046e4..db950515 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -10,7 +10,7 @@ using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient { /// - /// Minecraft Console Client by ORelio (c) 2012-2014. + /// Minecraft Console Client by ORelio & Contributors (c) 2012-2016. /// Allows to connect to any Minecraft server, send and receive text, automated scripts. /// This source code is released under the CDDL 1.0 License. /// @@ -20,9 +20,9 @@ namespace MinecraftClient private static McTcpClient Client; public static string[] startupargs; - public const string Version = "1.8.2"; + public const string Version = "1.9.0 BETA"; public const string MCLowestVersion = "1.4.6"; - public const string MCHighestVersion = "1.8.8"; + public const string MCHighestVersion = "1.9.0"; private static Thread offlinePrompt = null; private static bool useMcVersionOnce = false; diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 905ee97f..1a080aa9 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -34,6 +34,9 @@ namespace MinecraftClient.Protocol.Handlers this.c = Client; this.protocolversion = ProtocolVersion; this.handler = Handler; + + if (Settings.TerrainAndMovements) + ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); } private Protocol16Handler(TcpClient Client) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index bd8af932..af04a0df 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -13,12 +13,13 @@ using MinecraftClient.Mapping; namespace MinecraftClient.Protocol.Handlers { /// - /// Implementation for Minecraft 1.7.X and 1.8.X Protocols + /// Implementation for Minecraft 1.7.X, 1.8.X, 1.9.X Protocols /// class Protocol18Handler : IMinecraftCom { private const int MC18Version = 47; + private const int MC19Version = 107; private int compression_treshold = 0; private bool autocomplete_received = false; @@ -44,6 +45,9 @@ namespace MinecraftClient.Protocol.Handlers this.protocolversion = ProtocolVersion; this.handler = Handler; this.forgeInfo = ForgeInfo; + + if (Settings.TerrainAndMovements && protocolversion > MC18Version) + ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); } private Protocol18Handler(TcpClient Client) @@ -125,6 +129,85 @@ namespace MinecraftClient.Protocol.Handlers packetID = readNextVarInt(packetData); //Packet ID } + /// + /// Abstract incoming packet numbering + /// + + private enum PacketIncomingType + { + KeepAlive, + JoinGame, + ChatMessage, + PlayerPositionAndLook, + ChunkData, + MultiBlockChange, + BlockChange, + MapChunkBulk, + UnloadChunk, + PlayerListUpdate, + TabCompleteResult, + PluginMessage, + KickPacket, + NetworkCompressionTreshold, + ResourcePackSend, + UnknownPacket + } + + /// + /// Get abstract numbering ov the specified packet ID + /// + /// Packet ID + /// Protocol version + /// Abstract numbering + + private PacketIncomingType getPacketIncomingType(int packetID, int protocol) + { + if (protocol < MC19Version) + { + switch (packetID) + { + case 0x00: return PacketIncomingType.KeepAlive; + case 0x01: return PacketIncomingType.JoinGame; + case 0x02: return PacketIncomingType.ChatMessage; + case 0x08: return PacketIncomingType.PlayerPositionAndLook; + case 0x21: return PacketIncomingType.ChunkData; + case 0x22: return PacketIncomingType.MultiBlockChange; + case 0x23: return PacketIncomingType.BlockChange; + case 0x26: return PacketIncomingType.MapChunkBulk; + //UnloadChunk does not exists prior to 1.9 + case 0x38: return PacketIncomingType.PlayerListUpdate; + case 0x3A: return PacketIncomingType.TabCompleteResult; + case 0x3F: return PacketIncomingType.PluginMessage; + case 0x40: return PacketIncomingType.KickPacket; + case 0x46: return PacketIncomingType.NetworkCompressionTreshold; + case 0x48: return PacketIncomingType.ResourcePackSend; + default: return PacketIncomingType.UnknownPacket; + } + } + else + { + switch (packetID) + { + case 0x1F: return PacketIncomingType.KeepAlive; + case 0x23: return PacketIncomingType.JoinGame; + case 0x0F: return PacketIncomingType.ChatMessage; + case 0x2E: return PacketIncomingType.PlayerPositionAndLook; + case 0x20: return PacketIncomingType.ChunkData; + case 0x10: return PacketIncomingType.MultiBlockChange; + case 0x0B: return PacketIncomingType.BlockChange; + //MapChunkBulk removed in 1.9 + case 0x1D: return PacketIncomingType.UnloadChunk; + case 0x2D: return PacketIncomingType.PlayerListUpdate; + case 0x0E: return PacketIncomingType.TabCompleteResult; + case 0x18: return PacketIncomingType.PluginMessage; + case 0x1A: return PacketIncomingType.KickPacket; + //NetworkCompressionTreshold removed in 1.9 + case 0x32: return PacketIncomingType.ResourcePackSend; + default: return PacketIncomingType.UnknownPacket; + } + } + } + /// /// Handle the given packet /// @@ -148,15 +231,15 @@ namespace MinecraftClient.Protocol.Handlers } // Regular in-game packets - switch (packetID) + switch (getPacketIncomingType(packetID, protocolversion)) { - case 0x00: //Keep-Alive - SendPacket(0x00, packetData); + case PacketIncomingType.KeepAlive: + SendPacket(protocolversion >= MC19Version ? 0x0B : 0x00, packetData); break; - case 0x01: //Join game + case PacketIncomingType.JoinGame: handler.OnGameJoined(); break; - case 0x02: //Chat message + case PacketIncomingType.ChatMessage: string message = readNextString(packetData); try { @@ -169,7 +252,7 @@ namespace MinecraftClient.Protocol.Handlers catch (ArgumentOutOfRangeException) { /* No message type */ } handler.OnTextReceived(ChatParser.ParseText(message)); break; - case 0x08: //Player Position and Look + case PacketIncomingType.PlayerPositionAndLook: if (Settings.TerrainAndMovements) { double x = readNextDouble(packetData); @@ -184,20 +267,23 @@ namespace MinecraftClient.Protocol.Handlers location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z; handler.UpdateLocation(location); + + if (protocolversion >= MC19Version) + readNextVarInt(packetData); } break; - case 0x21: //Chunk Data + case PacketIncomingType.ChunkData: if (Settings.TerrainAndMovements) { int chunkX = readNextInt(packetData); int chunkZ = readNextInt(packetData); bool chunksContinuous = readNextBool(packetData); - ushort chunkMask = readNextUShort(packetData); + ushort chunkMask = protocolversion >= MC19Version ? (ushort)readNextVarInt(packetData) : readNextUShort(packetData); int dataSize = readNextVarInt(packetData); ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData); } break; - case 0x22: //Multi Block Change + case PacketIncomingType.MultiBlockChange: if (Settings.TerrainAndMovements) { int chunkX = readNextInt(packetData); @@ -214,12 +300,12 @@ namespace MinecraftClient.Protocol.Handlers } } break; - case 0x23: //Block Change + case PacketIncomingType.BlockChange: if (Settings.TerrainAndMovements) handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); break; - case 0x26: //Map Chunk Bulk - if (Settings.TerrainAndMovements) + case PacketIncomingType.MapChunkBulk: + if (protocolversion < MC19Version && Settings.TerrainAndMovements) { bool hasSkyLight = readNextBool(packetData); int chunkCount = readNextVarInt(packetData); @@ -240,7 +326,15 @@ namespace MinecraftClient.Protocol.Handlers ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData); } break; - case 0x38: //Player List update + case PacketIncomingType.UnloadChunk: + if (protocolversion >= MC19Version && Settings.TerrainAndMovements) + { + int chunkX = readNextInt(packetData); + int chunkZ = readNextInt(packetData); + handler.GetWorld()[chunkX, chunkZ] = null; + } + break; + case PacketIncomingType.PlayerListUpdate: if (protocolversion >= MC18Version) { int action = readNextVarInt(packetData); @@ -274,7 +368,7 @@ namespace MinecraftClient.Protocol.Handlers else handler.OnPlayerLeave(FakeUUID); } break; - case 0x3A: //Tab-Complete Result + case PacketIncomingType.TabCompleteResult: int autocomplete_count = readNextVarInt(packetData); string tab_list = ""; for (int i = 0; i < autocomplete_count; i++) @@ -288,7 +382,7 @@ namespace MinecraftClient.Protocol.Handlers if (tab_list.Length > 0) ConsoleIO.WriteLineFormatted("§8" + tab_list, false); break; - case 0x3F: //Plugin message. + case PacketIncomingType.PluginMessage: String channel = readNextString(packetData); if (protocolversion < MC18Version) { @@ -308,6 +402,7 @@ namespace MinecraftClient.Protocol.Handlers // The remaining data in the array is the entire payload of the packet. handler.OnPluginChannelMessage(channel, packetData.ToArray()); + #region Forge Login if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE) { if (channel == "FML|HS") @@ -432,20 +527,22 @@ namespace MinecraftClient.Protocol.Handlers } } } + #endregion + return false; - case 0x40: //Kick Packet + case PacketIncomingType.KickPacket: handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(packetData))); return false; - case 0x46: //Network Compression Treshold Info - if (protocolversion >= MC18Version) + case PacketIncomingType.NetworkCompressionTreshold: + if (protocolversion >= MC18Version && protocolversion < MC19Version) compression_treshold = readNextVarInt(packetData); break; - case 0x48: //Resource Pack Send + case PacketIncomingType.ResourcePackSend: string url = readNextString(packetData); string hash = readNextString(packetData); //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory - SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); - SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); + SendPacket(protocolversion >= MC19Version ? 0x16 : 0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); + SendPacket(protocolversion >= MC19Version ? 0x16 : 0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); break; default: return false; //Ignored packet @@ -465,7 +562,7 @@ namespace MinecraftClient.Protocol.Handlers private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List cache) { - if (chunksContinuous && chunkMask == 0) + if (protocolversion >= MC19Version && chunksContinuous && chunkMask == 0) { //Unload the entire chunk column handler.GetWorld()[chunkX, chunkZ] = null; @@ -978,7 +1075,8 @@ namespace MinecraftClient.Protocol.Handlers ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); login_phase = false; - if (forgeInfo != null) { + if (forgeInfo != null) + { // Do the forge handshake. if (!CompleteForgeHandshake()) { @@ -1099,7 +1197,7 @@ namespace MinecraftClient.Protocol.Handlers byte[] message_val = Encoding.UTF8.GetBytes(message); byte[] message_len = getVarInt(message_val.Length); byte[] message_packet = concatBytes(message_len, message_val); - SendPacket(0x01, message_packet); + SendPacket(protocolversion >= MC19Version ? 0x02 : 0x01, message_packet); return true; } catch (SocketException) { return false; } @@ -1116,7 +1214,7 @@ namespace MinecraftClient.Protocol.Handlers { try { - SendPacket(0x16, new byte[] { 0 }); + SendPacket(protocolversion >= MC19Version ? 0x03 : 0x16, new byte[] { 0 }); return true; } catch (SocketException) { return false; } @@ -1149,7 +1247,7 @@ namespace MinecraftClient.Protocol.Handlers { try { - SendPacket(0x04, concatBytes( + SendPacket(protocolversion >= MC19Version ? 0x0C : 0x04, concatBytes( getDouble(location.X), getDouble(location.Y), getDouble(location.Z), new byte[] { onGround ? (byte)1 : (byte)0 })); return true; @@ -1180,7 +1278,7 @@ namespace MinecraftClient.Protocol.Handlers } else { - SendPacket(0x17, concatBytes(getString(channel), data)); + SendPacket(protocolversion >= MC19Version ? 0x09 : 0x17, concatBytes(getString(channel), data)); } return true; @@ -1218,14 +1316,17 @@ namespace MinecraftClient.Protocol.Handlers byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor); byte[] tocomplete_len = getVarInt(tocomplete_val.Length); + byte[] assume_command = new byte[] { 0x00 }; byte[] has_position = new byte[] { 0x00 }; byte[] tabcomplete_packet = protocolversion >= MC18Version - ? concatBytes(tocomplete_len, tocomplete_val, has_position) + ? protocolversion >= MC19Version + ? concatBytes(tocomplete_len, tocomplete_val, assume_command, has_position) + : concatBytes(tocomplete_len, tocomplete_val, has_position) : concatBytes(tocomplete_len, tocomplete_val); autocomplete_received = false; autocomplete_result = BehindCursor; - SendPacket(0x14, tabcomplete_packet); + SendPacket(protocolversion >= MC19Version ? 0x01 : 0x14, tabcomplete_packet); int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms) while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; } From 82e92f984bc0b374501420bf81b17d7e152206f8 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 5 Mar 2016 19:33:44 +0100 Subject: [PATCH 127/131] MC 1.9 : Add missing compatibility list entries --- MinecraftClient/Program.cs | 2 +- MinecraftClient/Protocol/ProtocolHandler.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index db950515..d8af9d34 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -10,7 +10,7 @@ using MinecraftClient.Protocol.Handlers.Forge; namespace MinecraftClient { /// - /// Minecraft Console Client by ORelio & Contributors (c) 2012-2016. + /// Minecraft Console Client by ORelio and Contributors (c) 2012-2016. /// Allows to connect to any Minecraft server, send and receive text, automated scripts. /// This source code is released under the CDDL 1.0 License. /// diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index d36834a5..84ddeee7 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -71,7 +71,7 @@ namespace MinecraftClient.Protocol 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 }; + int[] supportedVersions_Protocol18 = { 4, 5, 47, 107 }; if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1) return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo); throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported."); @@ -124,6 +124,8 @@ namespace MinecraftClient.Protocol case "1.8.7": case "1.8.8": return 47; + case "1.8.9": + return 107; default: return 0; } From d44a76e82c4fd4cab898ad89fdcd591cf5b9bc3f Mon Sep 17 00:00:00 2001 From: ORelio Date: Tue, 8 Mar 2016 18:03:02 +0100 Subject: [PATCH 128/131] Fix Tab list item parsing for 1.8 & 1.9 Went unnoticed until, well, now. --- .../Protocol/Handlers/Protocol18.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index af04a0df..0d6f04dd 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -346,8 +346,28 @@ namespace MinecraftClient.Protocol.Handlers { case 0x00: //Player Join string name = readNextString(packetData); + int propNum = readNextVarInt(packetData); + for (int p = 0; p < propNum; p++) + { + readNextString(packetData); + readNextString(packetData); + if (readNextBool(packetData)) + readNextString(packetData); + } + readNextVarInt(packetData); + readNextVarInt(packetData); + if (readNextBool(packetData)) + readNextString(packetData); handler.OnPlayerJoin(uuid, name); break; + case 0x01: //Update gamemode + case 0x02: //Update latency + readNextVarInt(packetData); + break; + case 0x03: //Update display name + if (readNextBool(packetData)) + readNextString(packetData); + break; case 0x04: //Player Leave handler.OnPlayerLeave(uuid); break; From d45f75f9f4b3c7bf2a4ca94315491282a29d1a80 Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 10 Mar 2016 13:29:05 +0100 Subject: [PATCH 129/131] Reduce output verbosity, default session cache - Memory session cache will be used by default - Verbose messages are now hidden by default - Improve ping procedure's Forge handling - Fix 1.8.9 and 1.9.0 in mcversion setting - SessionCache.cs: fix LF into CRLF - Use 1.9 translations instead of 1.8 --- MinecraftClient/Cache/CacheType.cs | 4 - MinecraftClient/MinecraftClient.csproj | 4 +- MinecraftClient/Program.cs | 38 +++++---- .../Protocol/Handlers/ChatParser.cs | 3 +- .../Protocol/Handlers/Protocol16.cs | 5 +- .../Protocol/Handlers/Protocol18.cs | 56 +++++++------ .../Protocol/IMinecraftComHandler.cs | 4 +- MinecraftClient/Protocol/ProtocolHandler.cs | 3 +- .../Protocol/SessionCache/CacheType.cs | 4 + .../SessionCache}/SessionCache.cs | 81 ++++++++++--------- MinecraftClient/Settings.cs | 22 ++--- 11 files changed, 119 insertions(+), 105 deletions(-) delete mode 100644 MinecraftClient/Cache/CacheType.cs create mode 100644 MinecraftClient/Protocol/SessionCache/CacheType.cs rename MinecraftClient/{Cache => Protocol/SessionCache}/SessionCache.cs (85%) diff --git a/MinecraftClient/Cache/CacheType.cs b/MinecraftClient/Cache/CacheType.cs deleted file mode 100644 index 9f49cd1c..00000000 --- a/MinecraftClient/Cache/CacheType.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace MinecraftClient.Cache -{ - public enum CacheType { NONE, MEMORY, DISK }; -} diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index be23d2ed..35a9ff85 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -73,8 +73,6 @@ - - @@ -151,6 +149,8 @@ + + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index d8af9d34..0dfb168d 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -6,6 +6,7 @@ using MinecraftClient.Protocol; using System.Reflection; using System.Threading; using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Protocol.SessionCache; namespace MinecraftClient { @@ -86,19 +87,21 @@ namespace MinecraftClient } //Load cached sessions from disk if necessary - if (Settings.CacheType == Cache.CacheType.DISK) + if (Settings.SessionCaching == CacheType.Disk) { - Console.WriteLine(Cache.SessionCache.InitializeDiskCache() ? "Cached sessions loaded." : "Cached sessions could not be loaded from disk"); + bool cacheLoaded = SessionCache.InitializeDiskCache(); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted(cacheLoaded ? "§8Session cache has been successfully loaded from disk." : "§8Cached sessions could not be loaded from disk"); } //Asking the user to type in missing data such as Username and Password if (Settings.Login == "") { - Console.Write(ConsoleIO.basicIO ? "Please type the username of your choice.\n" : "Username : "); + Console.Write(ConsoleIO.basicIO ? "Please type the username or email of your choice.\n" : "Login : "); Settings.Login = Console.ReadLine(); } - if (Settings.Password == "" && (Settings.CacheType == Cache.CacheType.NONE || !Cache.SessionCache.Contains(Settings.Login))) + if (Settings.Password == "" && (Settings.SessionCaching == CacheType.None || !SessionCache.Contains(Settings.Login.ToLower()))) { RequestPassword(); } @@ -143,18 +146,17 @@ namespace MinecraftClient else { // Validate cached session or login new session. - if (Settings.CacheType != Cache.CacheType.NONE && Cache.SessionCache.Contains(Settings.Login)) + if (Settings.SessionCaching != CacheType.None && SessionCache.Contains(Settings.Login.ToLower())) { - session = Cache.SessionCache.Get(Settings.Login); + session = SessionCache.Get(Settings.Login.ToLower()); result = ProtocolHandler.GetTokenValidation(session); - - if (result != ProtocolHandler.LoginResult.Success && Settings.Password == "") + if (result != ProtocolHandler.LoginResult.Success) { - RequestPassword(); + ConsoleIO.WriteLineFormatted("§8Cached session is invalid or expired."); + if (Settings.Password == "") + RequestPassword(); } - - Console.WriteLine("Cached session is " + (result == ProtocolHandler.LoginResult.Success ? "valid." : "invalid.")); - + else ConsoleIO.WriteLineFormatted("§8Cached session is still valid for " + session.PlayerName + '.'); } if (result != ProtocolHandler.LoginResult.Success) @@ -162,9 +164,9 @@ namespace MinecraftClient Console.WriteLine("Connecting to Minecraft.net..."); result = ProtocolHandler.GetLogin(Settings.Login, Settings.Password, out session); - if (result == ProtocolHandler.LoginResult.Success && Settings.CacheType != Cache.CacheType.NONE) + if (result == ProtocolHandler.LoginResult.Success && Settings.SessionCaching != CacheType.None) { - Cache.SessionCache.Store(Settings.Login, session); + SessionCache.Store(Settings.Login.ToLower(), session); } } @@ -180,7 +182,8 @@ namespace MinecraftClient if (Settings.playerHeadAsIcon) ConsoleIcon.setPlayerIconAsync(Settings.Username); - Console.WriteLine("Success. (session ID: " + session.ID + ')'); + if (Settings.DebugMessages) + Console.WriteLine("Success. (session ID: " + session.ID + ')'); //ProtocolHandler.RealmsListWorlds(Settings.Username, PlayerID, sessionID); //TODO REMOVE @@ -221,11 +224,6 @@ namespace MinecraftClient } } - if (forgeInfo != null && !forgeInfo.Mods.Any()) - { - forgeInfo = null; - } - if (protocolversion != 0) { try diff --git a/MinecraftClient/Protocol/Handlers/ChatParser.cs b/MinecraftClient/Protocol/Handlers/ChatParser.cs index 5d2e785b..5c5b6c84 100644 --- a/MinecraftClient/Protocol/Handlers/ChatParser.cs +++ b/MinecraftClient/Protocol/Handlers/ChatParser.cs @@ -123,7 +123,8 @@ namespace MinecraftClient.Protocol.Handlers } } - ConsoleIO.WriteLineFormatted("§8Translations file loaded."); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Translations file loaded."); } else //No external dictionnary found. { diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 1a080aa9..8480b259 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -449,7 +449,7 @@ namespace MinecraftClient.Protocol.Handlers if (serverID == "-") ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); - else + else if (Settings.DebugMessages) ConsoleIO.WriteLineFormatted("§8Handshake successful. (Server ID: " + serverID + ')'); return StartEncryption(uuid, username, sessionID, token, serverID, PublicServerkey); @@ -462,7 +462,8 @@ namespace MinecraftClient.Protocol.Handlers System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey); byte[] secretKey = CryptoHandler.GenerateAESPrivateKey(); - ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); if (serverIDhash != "-") { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 0d6f04dd..e35317c4 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -451,13 +451,15 @@ namespace MinecraftClient.Protocol.Handlers byte fmlProtocolVersion = readNextByte(packetData); // There's another value afterwards for the dimension, but we don't need it. - ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion); // Tell the server we're running the same version. SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion }); // Then tell the server that we're running the same mods. - ConsoleIO.WriteLineFormatted("§8Sending falsified mod list to server..."); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Sending falsified mod list to server..."); byte[][] mods = new byte[forgeInfo.Mods.Count][]; for (int i = 0; i < forgeInfo.Mods.Count; i++) { @@ -476,7 +478,8 @@ namespace MinecraftClient.Protocol.Handlers Thread.Sleep(2000); - ConsoleIO.WriteLineFormatted("§8Accepting server mod list..."); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Accepting server mod list..."); // Tell the server that yes, we are OK with the mods it has // even though we don't actually care what mods it has. @@ -497,8 +500,8 @@ namespace MinecraftClient.Protocol.Handlers // with blocks and items. int registrySize = readNextVarInt(packetData); - ConsoleIO.WriteLineFormatted("§8Received registry " + - "with " + registrySize + " entries"); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Received registry with " + registrySize + " entries"); fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE; } @@ -509,14 +512,10 @@ namespace MinecraftClient.Protocol.Handlers bool hasNextRegistry = readNextBool(packetData); string registryName = readNextString(packetData); int registrySize = readNextVarInt(packetData); - - ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + - " with " + registrySize + " entries"); - + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + " with " + registrySize + " entries"); if (!hasNextRegistry) - { fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE; - } } return false; @@ -525,9 +524,8 @@ namespace MinecraftClient.Protocol.Handlers // Just say yes. if (discriminator != FMLHandshakeDiscriminator.HandshakeAck) return false; - - ConsoleIO.WriteLineFormatted("§8Accepting server registries..."); - + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Accepting server registries..."); SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck, new byte[] { (byte)FMLHandshakeClientState.PENDINGCOMPLETE }); fmlHandshakeState = FMLHandshakeClientState.COMPLETE; @@ -540,8 +538,8 @@ namespace MinecraftClient.Protocol.Handlers SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck, new byte[] { (byte)FMLHandshakeClientState.COMPLETE }); - ConsoleIO.WriteLine("Forge server connection complete!"); - + if (Settings.DebugMessages) + ConsoleIO.WriteLine("Forge server connection complete!"); fmlHandshakeState = FMLHandshakeClientState.DONE; return true; } @@ -582,7 +580,7 @@ namespace MinecraftClient.Protocol.Handlers private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List cache) { - if (protocolversion >= MC19Version && chunksContinuous && chunkMask == 0) + if (protocolversion < MC19Version && chunksContinuous && chunkMask == 0) { //Unload the entire chunk column handler.GetWorld()[chunkX, chunkZ] = null; @@ -1124,7 +1122,7 @@ namespace MinecraftClient.Protocol.Handlers { readNextPacket(ref packetID, packetData); - if (packetID == 0x40) // Disconect + if (packetID == 0x40) // Disconnect { handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData))); return false; @@ -1148,7 +1146,8 @@ namespace MinecraftClient.Protocol.Handlers System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey); byte[] secretKey = CryptoHandler.GenerateAESPrivateKey(); - ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); + if (Settings.DebugMessages) + ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); if (serverIDhash != "-") { @@ -1408,8 +1407,6 @@ namespace MinecraftClient.Protocol.Handlers 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) { @@ -1418,13 +1415,24 @@ namespace MinecraftClient.Protocol.Handlers { forgeInfo = new ForgeInfo(modData); - ConsoleIO.WriteLineFormatted("§8Server is running forge. Mod list:"); - foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods) + if (forgeInfo.Mods.Any()) { - ConsoleIO.WriteLineFormatted("§8 " + mod.ToString()); + if (Settings.DebugMessages) + { + ConsoleIO.WriteLineFormatted("§8Server is running Forge. Mod list:"); + foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods) + { + ConsoleIO.WriteLineFormatted("§8 " + mod.ToString()); + } + } + else ConsoleIO.WriteLineFormatted("§8Server is running Forge."); } + else forgeInfo = null; } } + + ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ").")); + return true; } } diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 7ca8cea6..dae4b516 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -90,8 +90,8 @@ namespace MinecraftClient.Protocol void UnregisterPluginChannel(string channel, ChatBot bot); /// - /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information - /// about plugin channels. + /// Sends a plugin channel packet to the server. + /// See http://wiki.vg/Plugin_channel for more information about plugin channels. /// /// The channel to send the packet on. /// The payload for the packet. diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 84ddeee7..894f1c00 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -123,8 +123,9 @@ namespace MinecraftClient.Protocol case "1.8.6": case "1.8.7": case "1.8.8": - return 47; case "1.8.9": + return 47; + case "1.9.0": return 107; default: return 0; diff --git a/MinecraftClient/Protocol/SessionCache/CacheType.cs b/MinecraftClient/Protocol/SessionCache/CacheType.cs new file mode 100644 index 00000000..886d61f9 --- /dev/null +++ b/MinecraftClient/Protocol/SessionCache/CacheType.cs @@ -0,0 +1,4 @@ +namespace MinecraftClient.Protocol.SessionCache +{ + public enum CacheType { None, Memory, Disk }; +} diff --git a/MinecraftClient/Cache/SessionCache.cs b/MinecraftClient/Protocol/SessionCache/SessionCache.cs similarity index 85% rename from MinecraftClient/Cache/SessionCache.cs rename to MinecraftClient/Protocol/SessionCache/SessionCache.cs index a943958b..7a03fa9d 100644 --- a/MinecraftClient/Cache/SessionCache.cs +++ b/MinecraftClient/Protocol/SessionCache/SessionCache.cs @@ -6,15 +6,16 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Timers; -namespace MinecraftClient.Cache +namespace MinecraftClient.Protocol.SessionCache { - /// - /// Handle sessions caching and storage. + /// + /// Handle sessions caching and storage. /// public static class SessionCache { - const string filename = "cache.bin"; + private const string SessionCacheFile = "SessionCache.db"; + private static Dictionary sessions = new Dictionary(); private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); private static Timer updatetimer = new Timer(100); @@ -22,10 +23,10 @@ namespace MinecraftClient.Cache private static BinaryFormatter formatter = new BinaryFormatter(); - /// - /// Retrieve whether SessionCache contains a session for the given login. - /// - /// User login used with Minecraft.net + /// + /// Retrieve whether SessionCache contains a session for the given login. + /// + /// User login used with Minecraft.net /// TRUE if session is available public static bool Contains(string login) @@ -33,10 +34,10 @@ namespace MinecraftClient.Cache return sessions.ContainsKey(login); } - /// - /// Store a session and save it to disk if required. - /// - /// User login used with Minecraft.net + /// + /// Store a session and save it to disk if required. + /// + /// User login used with Minecraft.net /// User session token used with Minecraft.net public static void Store(string login, SessionToken session) @@ -50,18 +51,20 @@ namespace MinecraftClient.Cache sessions.Add(login, session); } - if (Settings.CacheType == CacheType.DISK && updatetimer.Enabled == true) { + if (Settings.SessionCaching == CacheType.Disk && updatetimer.Enabled == true) + { pendingadds.Add(new KeyValuePair(login, session)); - }else if (Settings.CacheType == CacheType.DISK) + } + else if (Settings.SessionCaching == CacheType.Disk) { SaveToDisk(); } } - /// - /// Retrieve a session token for the given login. - /// - /// User login used with Minecraft.net + /// + /// Retrieve a session token for the given login. + /// + /// User login used with Minecraft.net /// SessionToken for given login public static SessionToken Get(string login) @@ -69,16 +72,16 @@ namespace MinecraftClient.Cache return sessions[login]; } - /// - /// Initialize cache monitoring to keep cache updated with external changes. - /// + /// + /// Initialize cache monitoring to keep cache updated with external changes. + /// /// TRUE if session tokens are seeded from file public static bool InitializeDiskCache() { cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; cachemonitor.IncludeSubdirectories = false; - cachemonitor.Filter = filename; + cachemonitor.Filter = SessionCacheFile; cachemonitor.NotifyFilter = NotifyFilters.LastWrite; cachemonitor.Changed += new FileSystemEventHandler(OnChanged); cachemonitor.EnableRaisingEvents = true; @@ -88,10 +91,10 @@ namespace MinecraftClient.Cache return LoadFromDisk(); } - /// - /// Reloads cache on external cache file change. - /// - /// Sender + /// + /// Reloads cache on external cache file change. + /// + /// Sender /// Event data private static void OnChanged(object sender, FileSystemEventArgs e) @@ -100,10 +103,10 @@ namespace MinecraftClient.Cache updatetimer.Start(); } - /// - /// Called after timer elapsed. Reads disk cache and adds new/modified sessions back. - /// - /// Sender + /// + /// Called after timer elapsed. Reads disk cache and adds new/modified sessions back. + /// + /// Sender /// Event data private static void HandlePending(object sender, ElapsedEventArgs e) @@ -117,18 +120,18 @@ namespace MinecraftClient.Cache } } - /// - /// Reads cache file and loads SessionTokens into SessionCache. - /// + /// + /// Reads cache file and loads SessionTokens into SessionCache. + /// /// True if data is successfully loaded private static bool LoadFromDisk() { - if (File.Exists(filename)) + if (File.Exists(SessionCacheFile)) { try { - using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (FileStream fs = new FileStream(SessionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read)) { sessions = (Dictionary)formatter.Deserialize(fs); return true; @@ -146,15 +149,15 @@ namespace MinecraftClient.Cache return false; } - /// - /// Saves SessionToken's from SessionCache into cache file. + /// + /// Saves SessionToken's from SessionCache into cache file. /// private static void SaveToDisk() { - bool fileexists = File.Exists(filename); + bool fileexists = File.Exists(SessionCacheFile); - using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + using (FileStream fs = new FileStream(SessionCacheFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { cachemonitor.EnableRaisingEvents = false; diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 0d86cdf2..679b86bf 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.IO; using System.Text.RegularExpressions; +using MinecraftClient.Protocol.SessionCache; namespace MinecraftClient { @@ -29,9 +30,6 @@ namespace MinecraftClient public static string SingleCommand = ""; public static string ConsoleTitle = ""; - //Cache Settings - public static Cache.CacheType CacheType = Cache.CacheType.NONE; - //Proxy Settings public static bool ProxyEnabledLogin = false; public static bool ProxyEnabledIngame = false; @@ -42,8 +40,8 @@ namespace MinecraftClient public static string ProxyPassword = ""; //Other Settings - public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\03\03f31164d234f10a3230611656332f1756e570a9"; //MC 1.8 en_GB.lang - public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.8.json"; + public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\3d\3d7f778ea0a3baaf826ae75a094d77c46410902f"; //MC 1.9 en_GB.lang + public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.9.json"; public static string TranslationsFile_Website_Download = "http://resources.download.minecraft.net"; public static TimeSpan splitMessageDelay = TimeSpan.FromSeconds(2); public static List Bots_Owners = new List(); @@ -60,6 +58,8 @@ namespace MinecraftClient public static bool DisplayXPBarMessages = true; public static bool TerrainAndMovements = false; public static string PrivateMsgsCmdName = "tell"; + public static CacheType SessionCaching = CacheType.None; + public static bool DebugMessages = false; //AntiAFK Settings public static bool AntiAFK_Enabled = false; @@ -187,6 +187,7 @@ namespace MinecraftClient case "terrainandmovements": TerrainAndMovements = str2bool(argValue); break; case "privatemsgscmdname": PrivateMsgsCmdName = argValue.ToLower().Trim(); break; case "botmessagedelay": botMessageDelay = TimeSpan.FromSeconds(str2int(argValue)); break; + case "debugmessages": DebugMessages = str2bool(argValue); break; case "botowners": Bots_Owners.Clear(); @@ -203,10 +204,10 @@ namespace MinecraftClient } break; - case "accountcache": - if (argValue == "none") { CacheType = Cache.CacheType.NONE; } - else if (argValue == "memory") { CacheType = Cache.CacheType.MEMORY; } - else if (argValue == "disk") { CacheType = Cache.CacheType.DISK; } + case "sessioncache": + if (argValue == "none") { SessionCaching = CacheType.None; } + else if (argValue == "memory") { SessionCaching = CacheType.Memory; } + else if (argValue == "disk") { SessionCaching = CacheType.Disk; } break; case "accountlist": @@ -425,11 +426,12 @@ namespace MinecraftClient + "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" - + "accountcache=none #use 'none', 'memory' or 'disk'\r\n" + + "sessioncache=memory #use 'none', 'memory' or 'disk'\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n" + "exitonfailure=false\r\n" + + "debugmessages=false\r\n" + "scriptcache=true\r\n" + "timestamps=false\r\n" + "\r\n" From 199bbbabeb0e3355ef07d2ba9dc178a60e88f33a Mon Sep 17 00:00:00 2001 From: ORelio Date: Fri, 11 Mar 2016 10:52:19 +0100 Subject: [PATCH 130/131] Add /move get for getting coordinates Suggested in #129 --- MinecraftClient/Commands/Move.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 25ab8e16..68a4323c 100644 --- a/MinecraftClient/Commands/Move.cs +++ b/MinecraftClient/Commands/Move.cs @@ -9,7 +9,7 @@ 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 CMDDesc { get { return "move : walk or start walking."; } } public override string Run(McTcpClient handler, string command) { @@ -28,6 +28,7 @@ namespace MinecraftClient.Commands case "west": direction = Direction.West; break; case "north": direction = Direction.North; break; case "south": direction = Direction.South; break; + case "get": return handler.GetCurrentLocation().ToString(); default: return "Unknown direction '" + dirStr + "'."; } if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction)) From e8097206d0a654c0ddec2cde7c5dab69619dba7b Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 12 Mar 2016 11:01:25 +0100 Subject: [PATCH 131/131] Disable terrain and movements when not handled Related to #131 --- MinecraftClient/Protocol/Handlers/Protocol16.cs | 3 +++ MinecraftClient/Protocol/Handlers/Protocol18.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 8480b259..14dce500 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -36,7 +36,10 @@ namespace MinecraftClient.Protocol.Handlers this.handler = Handler; if (Settings.TerrainAndMovements) + { ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); + Settings.TerrainAndMovements = false; + } } private Protocol16Handler(TcpClient Client) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index e35317c4..4e804390 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -47,7 +47,10 @@ namespace MinecraftClient.Protocol.Handlers this.forgeInfo = ForgeInfo; if (Settings.TerrainAndMovements && protocolversion > MC18Version) + { ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); + Settings.TerrainAndMovements = false; + } } private Protocol18Handler(TcpClient Client)