From 424eab29dd2d4848142d368b6458ddc7e7cae1b1 Mon Sep 17 00:00:00 2001 From: Bas950 <34792838+Bas950@users.noreply.github.com> Date: Mon, 29 Jun 2020 16:05:12 +0200 Subject: [PATCH] Join Game and Respawn encoding/decoding & added ReadNextVarIntArray --- .../Protocol/Handlers/DataTypes.cs | 1733 ++++----- .../Protocol/Handlers/Protocol18.cs | 3105 +++++++++-------- 2 files changed, 2446 insertions(+), 2392 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index 5a077073..0b7ae768 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -10,864 +10,885 @@ using MinecraftClient.Mapping.EntityPalettes; namespace MinecraftClient.Protocol.Handlers { + /// + /// Handle data types encoding / decoding + /// + class DataTypes + { /// - /// Handle data types encoding / decoding + /// Protocol version for adjusting data types /// - class DataTypes + private int protocolversion; + + /// + /// Initialize a new DataTypes instance + /// + /// Protocol version + public DataTypes(int protocol) { - /// - /// Protocol version for adjusting data types - /// - private int protocolversion; - - /// - /// Initialize a new DataTypes instance - /// - /// Protocol version - public DataTypes(int protocol) - { - this.protocolversion = protocol; - } - - /// - /// Read some data from a cache of bytes and remove it from the cache - /// - /// Amount of bytes to read - /// Cache of bytes to read from - /// The data read from the cache as an array - public byte[] ReadData(int offset, Queue cache) - { - byte[] result = new byte[offset]; - for (int i = 0; i < offset; i++) - result[i] = cache.Dequeue(); - return result; - } - - /// - /// Read a string from a cache of bytes and remove it from the cache - /// - /// Cache of bytes to read from - /// The string - public string ReadNextString(Queue cache) - { - int length = ReadNextVarInt(cache); - if (length > 0) - { - return Encoding.UTF8.GetString(ReadData(length, cache)); - } - else return ""; - } - - /// - /// Read a boolean from a cache of bytes and remove it from the cache - /// - /// The boolean value - public bool ReadNextBool(Queue cache) - { - return ReadNextByte(cache) != 0x00; - } - - /// - /// Read a short integer from a cache of bytes and remove it from the cache - /// - /// The short integer value - public short ReadNextShort(Queue 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 - public int ReadNextInt(Queue cache) - { - byte[] rawValue = ReadData(4, cache); - Array.Reverse(rawValue); //Endianness - return BitConverter.ToInt32(rawValue, 0); - } - - /// - /// Read a long integer from a cache of bytes and remove it from the cache - /// - /// The unsigned long integer value - public long ReadNextLong(Queue cache) - { - byte[] rawValue = ReadData(8, cache); - Array.Reverse(rawValue); //Endianness - return BitConverter.ToInt64(rawValue, 0); - } - - /// - /// Read an unsigned short integer from a cache of bytes and remove it from the cache - /// - /// The unsigned short integer value - public ushort ReadNextUShort(Queue cache) - { - byte[] rawValue = ReadData(2, cache); - Array.Reverse(rawValue); //Endianness - return BitConverter.ToUInt16(rawValue, 0); - } - - /// - /// Read an unsigned long integer from a cache of bytes and remove it from the cache - /// - /// The unsigned long integer value - public ulong ReadNextULong(Queue cache) - { - byte[] rawValue = ReadData(8, cache); - Array.Reverse(rawValue); //Endianness - return BitConverter.ToUInt64(rawValue, 0); - } - - /// - /// Read a Location encoded as an ulong field and remove it from the cache - /// - /// The Location value - public Location ReadNextLocation(Queue cache) - { - ulong locEncoded = ReadNextULong(cache); - int x, y, z; - if (protocolversion >= Protocol18Handler.MC114Version) - { - x = (int)(locEncoded >> 38); - y = (int)(locEncoded & 0xFFF); - z = (int)(locEncoded << 26 >> 38); - } - else - { - x = (int)(locEncoded >> 38); - y = (int)((locEncoded >> 26) & 0xFFF); - z = (int)(locEncoded << 38 >> 38); - } - if (x >= 33554432) - x -= 67108864; - if (y >= 2048) - y -= 4096; - if (z >= 33554432) - z -= 67108864; - return new Location(x, y, z); - } - - /// - /// 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 - public ushort[] ReadNextUShortsLittleEndian(int amount, Queue cache) - { - byte[] rawValues = ReadData(2 * amount, cache); - ushort[] result = new ushort[amount]; - for (int i = 0; i < amount; i++) - result[i] = BitConverter.ToUInt16(rawValues, i * 2); - return result; - } - - /// - /// Read a uuid from a cache of bytes and remove it from the cache - /// - /// Cache of bytes to read from - /// The uuid - public Guid ReadNextUUID(Queue cache) - { - byte[] javaUUID = ReadData(16, cache); - Guid guid; - if (BitConverter.IsLittleEndian) - { - // Convert big-endian Java UUID to little-endian .NET GUID - byte[] netGUID = new byte[16]; - for (int i = 8; i < 16; i++) - netGUID[i] = javaUUID[i]; - netGUID[3] = javaUUID[0]; - netGUID[2] = javaUUID[1]; - netGUID[1] = javaUUID[2]; - netGUID[0] = javaUUID[3]; - netGUID[5] = javaUUID[4]; - netGUID[4] = javaUUID[5]; - netGUID[6] = javaUUID[7]; - netGUID[7] = javaUUID[6]; - guid = new Guid(netGUID); - } - else - { - guid = new Guid(javaUUID); - } - return guid; - } - - /// - /// Read a byte array from a cache of bytes and remove it from the cache - /// - /// Cache of bytes to read from - /// The byte array - public byte[] ReadNextByteArray(Queue cache) - { - int len = protocolversion >= Protocol18Handler.MC18Version - ? ReadNextVarInt(cache) - : ReadNextShort(cache); - return ReadData(len, cache); - } - - /// - /// Reads a length-prefixed array of unsigned long integers and removes it from the cache - /// - /// The unsigned long integer values - public ulong[] ReadNextULongArray(Queue cache) - { - int len = ReadNextVarInt(cache); - ulong[] result = new ulong[len]; - for (int i = 0; i < len; i++) - result[i] = ReadNextULong(cache); - return result; - } - - /// - /// Read a double from a cache of bytes and remove it from the cache - /// - /// The double value - public double ReadNextDouble(Queue cache) - { - byte[] rawValue = ReadData(8, cache); - Array.Reverse(rawValue); //Endianness - return BitConverter.ToDouble(rawValue, 0); - } - - /// - /// Read a float from a cache of bytes and remove it from the cache - /// - /// The float value - public float ReadNextFloat(Queue cache) - { - byte[] rawValue = ReadData(4, cache); - Array.Reverse(rawValue); //Endianness - return BitConverter.ToSingle(rawValue, 0); - } - - /// - /// Read an integer from the network - /// - /// The integer - public int ReadNextVarIntRAW(SocketWrapper socket) - { - int i = 0; - int j = 0; - int k = 0; - while (true) - { - k = socket.ReadDataRAW(1)[0]; - i |= (k & 0x7F) << j++ * 7; - if (j > 5) throw new OverflowException("VarInt too big"); - if ((k & 0x80) != 128) break; - } - return i; - } - - /// - /// Read an integer from a cache of bytes and remove it from the cache - /// - /// Cache of bytes to read from - /// The integer - public int ReadNextVarInt(Queue cache) - { - string rawData = BitConverter.ToString(cache.ToArray()); - int i = 0; - int j = 0; - int k = 0; - while (true) - { - k = ReadNextByte(cache); - i |= (k & 0x7F) << j++ * 7; - if (j > 5) throw new OverflowException("VarInt too big " + rawData); - if ((k & 0x80) != 128) break; - } - 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 - public int ReadNextVarShort(Queue cache) - { - ushort low = ReadNextUShort(cache); - byte high = 0; - if ((low & 0x8000) != 0) - { - low &= 0x7FFF; - high = ReadNextByte(cache); - } - return ((high & 0xFF) << 15) | low; - } - - /// - /// Read a single byte from a cache of bytes and remove it from the cache - /// - /// The byte that was read - public byte ReadNextByte(Queue cache) - { - byte result = cache.Dequeue(); - return result; - } - - /// - /// Read an uncompressed Named Binary Tag blob and remove it from the cache - /// - public Dictionary ReadNextNbt(Queue cache) - { - return ReadNextNbt(cache, true); - } - - /// - /// Read a single item slot from a cache of bytes and remove it from the cache - /// - /// The item that was read or NULL for an empty slot - public Item ReadNextItemSlot(Queue cache) - { - List slotData = new List(); - if (protocolversion > Protocol18Handler.MC113Version) - { - // MC 1.13 and greater - bool itemPresent = ReadNextBool(cache); - if (itemPresent) - { - int itemID = ReadNextVarInt(cache); - byte itemCount = ReadNextByte(cache); - Dictionary nbt = ReadNextNbt(cache); - return new Item(itemID, itemCount, nbt); - } - else return null; - } - else - { - // MC 1.12.2 and lower - short itemID = ReadNextShort(cache); - if (itemID == -1) - return null; - byte itemCount = ReadNextByte(cache); - short itemDamage = ReadNextShort(cache); - Dictionary nbt = ReadNextNbt(cache); - return new Item(itemID, itemCount, nbt); - } - } - - /// - /// Read entity information from a cache of bytes and remove it from the cache - /// - /// Mappings for converting entity type Ids to EntityType - /// TRUE for living entities (layout differs) - /// Entity information - public Entity ReadNextEntity(Queue cache, EntityPalette entityPalette, bool living) - { - int entityID = ReadNextVarInt(cache); - Guid entityUUID = Guid.Empty; - - if (protocolversion > Protocol18Handler.MC18Version) - { - entityUUID = ReadNextUUID(cache); - } - - EntityType entityType = entityPalette.FromId(ReadNextVarInt(cache), living); - Double entityX = ReadNextDouble(cache); - Double entityY = ReadNextDouble(cache); - Double entityZ = ReadNextDouble(cache); - byte entityYaw = ReadNextByte(cache); - byte entityPitch = ReadNextByte(cache); - - if (living) - { - byte entityHeadPitch = ReadNextByte(cache); - } - else - { - int metadata = ReadNextInt(cache); - } - - short velocityX = ReadNextShort(cache); - short velocityY = ReadNextShort(cache); - short velocityZ = ReadNextShort(cache); - - return new Entity(entityID, entityType, new Location(entityX, entityY, entityZ)); - } - - /// - /// Read an uncompressed Named Binary Tag blob and remove it from the cache (internal) - /// - private Dictionary ReadNextNbt(Queue cache, bool root) - { - Dictionary nbtData = new Dictionary(); - - if (root) - { - if (cache.Peek() == 0) // TAG_End - { - cache.Dequeue(); - return nbtData; - } - if (cache.Peek() != 10) // TAG_Compound - throw new System.IO.InvalidDataException("Failed to decode NBT: Does not start with TAG_Compound"); - ReadNextByte(cache); // Tag type (TAG_Compound) - - // NBT root name - string rootName = Encoding.ASCII.GetString(ReadData(ReadNextUShort(cache), cache)); - if (!String.IsNullOrEmpty(rootName)) - nbtData[""] = rootName; - } - - while (true) - { - int fieldType = ReadNextByte(cache); - - if (fieldType == 0) // TAG_End - return nbtData; - - int fieldNameLength = ReadNextUShort(cache); - string fieldName = Encoding.ASCII.GetString(ReadData(fieldNameLength, cache)); - object fieldValue = ReadNbtField(cache, fieldType); - - // This will override previous tags with the same name - nbtData[fieldName] = fieldValue; - } - } - - /// - /// Read a single Named Binary Tag field of the specified type and remove it from the cache - /// - private object ReadNbtField(Queue cache, int fieldType) - { - switch (fieldType) - { - case 1: // TAG_Byte - return ReadNextByte(cache); - case 2: // TAG_Short - return ReadNextShort(cache); - case 3: // TAG_Int - return ReadNextInt(cache); - case 4: // TAG_Long - return ReadNextLong(cache); - case 5: // TAG_Float - return ReadNextFloat(cache); - case 6: // TAG_Double - return ReadNextDouble(cache); - case 7: // TAG_Byte_Array - return ReadData(ReadNextInt(cache), cache); - case 8: // TAG_String - return Encoding.UTF8.GetString(ReadData(ReadNextUShort(cache), cache)); - case 9: // TAG_List - int listType = ReadNextByte(cache); - int listLength = ReadNextInt(cache); - object[] listItems = new object[listLength]; - for (int i = 0; i < listLength; i++) - listItems[i] = ReadNbtField(cache, listType); - return listItems; - case 10: // TAG_Compound - return ReadNextNbt(cache, false); - case 11: // TAG_Int_Array - listType = 3; - listLength = ReadNextInt(cache); - listItems = new object[listLength]; - for (int i = 0; i < listLength; i++) - listItems[i] = ReadNbtField(cache, listType); - return listItems; - case 12: // TAG_Long_Array - listType = 4; - listLength = ReadNextInt(cache); - listItems = new object[listLength]; - for (int i = 0; i < listLength; i++) - listItems[i] = ReadNbtField(cache, listType); - return listItems; - default: - throw new System.IO.InvalidDataException("Failed to decode NBT: Unknown field type " + fieldType); - } - } - - /// - /// Build an uncompressed Named Binary Tag blob for sending over the network - /// - /// Dictionary to encode as Nbt - /// Byte array for this NBT tag - public byte[] GetNbt(Dictionary nbt) - { - return GetNbt(nbt, true); - } - - /// - /// Build an uncompressed Named Binary Tag blob for sending over the network (internal) - /// - /// Dictionary to encode as Nbt - /// TRUE if starting a new NBT tag, FALSE if processing a nested NBT tag - /// Byte array for this NBT tag - private byte[] GetNbt(Dictionary nbt, bool root) - { - if (nbt == null || nbt.Count == 0) - return new byte[] { 0 }; // TAG_End - - List bytes = new List(); - - if (root) - { - bytes.Add(10); // TAG_Compound - - // NBT root name - string rootName = null; - if (nbt.ContainsKey("")) - rootName = nbt[""] as string; - if (rootName == null) - rootName = ""; - bytes.AddRange(GetUShort((ushort)rootName.Length)); - bytes.AddRange(Encoding.ASCII.GetBytes(rootName)); - } - - foreach (var item in nbt) - { - // Skip NBT root name - if (item.Key == "" && root) - continue; - - byte fieldType; - byte[] fieldNameLength = GetUShort((ushort)item.Key.Length); - byte[] fieldName = Encoding.ASCII.GetBytes(item.Key); - byte[] fieldData = GetNbtField(item.Value, out fieldType); - bytes.Add(fieldType); - bytes.AddRange(fieldNameLength); - bytes.AddRange(fieldName); - bytes.AddRange(fieldData); - } - - bytes.Add(0); // TAG_End - return bytes.ToArray(); - } - - /// - /// Convert a single object into its NBT representation (internal) - /// - /// Object to convert - /// Field type for the passed object - /// Binary data for the passed object - private byte[] GetNbtField(object obj, out byte fieldType) - { - if (obj is byte) - { - fieldType = 1; // TAG_Byte - return new[] { (byte)obj }; - } - else if (obj is short) - { - fieldType = 2; // TAG_Short - return GetShort((short)obj); - } - else if (obj is int) - { - fieldType = 3; // TAG_Int - return GetInt((int)obj); - } - else if (obj is long) - { - fieldType = 4; // TAG_Long - return GetLong((long)obj); - } - else if (obj is float) - { - fieldType = 5; // TAG_Float - return GetFloat((float)obj); - } - else if (obj is double) - { - fieldType = 6; // TAG_Double - return GetDouble((double)obj); - } - else if (obj is byte[]) - { - fieldType = 7; // TAG_Byte_Array - return (byte[])obj; - } - else if (obj is string) - { - fieldType = 8; // TAG_String - byte[] stringBytes = Encoding.UTF8.GetBytes((string)obj); - return ConcatBytes(GetUShort((ushort)stringBytes.Length), stringBytes); - } - else if (obj is object[]) - { - fieldType = 9; // TAG_List - - List list = new List((object[])obj); - int arrayLengthTotal = list.Count; - - // Treat empty list as TAG_Byte, length 0 - if (arrayLengthTotal == 0) - return ConcatBytes(new[] { (byte)1 }, GetInt(0)); - - // Encode first list item, retain its type - byte firstItemType; - string firstItemTypeString = list[0].GetType().Name; - byte[] firstItemBytes = GetNbtField(list[0], out firstItemType); - list.RemoveAt(0); - - // Encode further list items, check they have the same type - byte subsequentItemType; - List subsequentItemsBytes = new List(); - foreach (object item in list) - { - subsequentItemsBytes.AddRange(GetNbtField(item, out subsequentItemType)); - if (subsequentItemType != firstItemType) - throw new System.IO.InvalidDataException( - "GetNbt: Cannot encode object[] list with mixed types: " + firstItemTypeString + ", " + item.GetType().Name + " into NBT!"); - } - - // Build NBT list: type, length, item array - return ConcatBytes(new[] { firstItemType }, GetInt(arrayLengthTotal), firstItemBytes, subsequentItemsBytes.ToArray()); - } - else if (obj is Dictionary) - { - fieldType = 10; // TAG_Compound - return GetNbt((Dictionary)obj, false); - } - else if (obj is int[]) - { - fieldType = 11; // TAG_Int_Array - - int[] srcIntList = (int[])obj; - List encIntList = new List(); - encIntList.AddRange(GetInt(srcIntList.Length)); - foreach (int item in srcIntList) - encIntList.AddRange(GetInt(item)); - return encIntList.ToArray(); - } - else if (obj is long[]) - { - fieldType = 12; // TAG_Long_Array - - long[] srcLongList = (long[])obj; - List encLongList = new List(); - encLongList.AddRange(GetInt(srcLongList.Length)); - foreach (long item in srcLongList) - encLongList.AddRange(GetLong(item)); - return encLongList.ToArray(); - } - else - { - throw new System.IO.InvalidDataException("GetNbt: Cannot encode data type " + obj.GetType().Name + " into NBT!"); - } - } - - /// - /// Build an integer for sending over the network - /// - /// Integer to encode - /// Byte array for this integer - public 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(); - } - - /// - /// Get byte array representing a long integer - /// - /// Long to process - /// Array ready to send - public byte[] GetLong(long number) - { - byte[] theLong = BitConverter.GetBytes(number); - Array.Reverse(theLong); - return theLong; - } - - /// - /// Get byte array representing an integer - /// - /// Integer to process - /// Array ready to send - public byte[] GetInt(int number) - { - byte[] theInt = BitConverter.GetBytes(number); - Array.Reverse(theInt); - return theInt; - } - - /// - /// Get byte array representing a short - /// - /// Short to process - /// Array ready to send - public byte[] GetShort(short number) - { - byte[] theShort = BitConverter.GetBytes(number); - Array.Reverse(theShort); - return theShort; - } - - /// - /// Get byte array representing an unsigned short - /// - /// Short to process - /// Array ready to send - public byte[] GetUShort(ushort number) - { - byte[] theShort = BitConverter.GetBytes(number); - Array.Reverse(theShort); - return theShort; - } - - /// - /// Get byte array representing a double - /// - /// Double to process - /// Array ready to send - public byte[] GetDouble(double number) - { - byte[] theDouble = BitConverter.GetBytes(number); - Array.Reverse(theDouble); //Endianness - return theDouble; - } - - /// - /// Get byte array representing a float - /// - /// Floalt to process - /// Array ready to send - public byte[] GetFloat(float number) - { - byte[] theFloat = BitConverter.GetBytes(number); - Array.Reverse(theFloat); //Endianness - return theFloat; - } - - /// - /// Get byte array with length information prepended to it - /// - /// Array to process - /// Array ready to send - public byte[] GetArray(byte[] array) - { - if (protocolversion < Protocol18Handler.MC18Version) - { - byte[] length = BitConverter.GetBytes((short)array.Length); - Array.Reverse(length); //Endianness - return ConcatBytes(length, array); - } - else return ConcatBytes(GetVarInt(array.Length), array); - } - - /// - /// Get a byte array from the given string for sending over the network, with length information prepended. - /// - /// String to process - /// Array ready to send - public byte[] GetString(string text) - { - byte[] bytes = Encoding.UTF8.GetBytes(text); - return ConcatBytes(GetVarInt(bytes.Length), bytes); - } - - /// - /// Get a byte array representing the given location encoded as an 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 byte[] GetLocation(Location location) - { - byte[] locationBytes; - if (protocolversion >= Protocol18Handler.MC114Version) - { - locationBytes = BitConverter.GetBytes(((((ulong)location.X) & 0x3FFFFFF) << 38) | ((((ulong)location.Z) & 0x3FFFFFF) << 12) | (((ulong)location.Y) & 0xFFF)); - } - else locationBytes = BitConverter.GetBytes(((((ulong)location.X) & 0x3FFFFFF) << 38) | ((((ulong)location.Y) & 0xFFF) << 26) | (((ulong)location.Z) & 0x3FFFFFF)); - Array.Reverse(locationBytes); //Endianness - return locationBytes; - } - - /// - /// Get a byte array representing the given item as an item slot - /// - /// Item - /// Item slot representation - public byte[] GetItemSlot(Item item) - { - List slotData = new List(); - if (protocolversion > Protocol18Handler.MC113Version) - { - // MC 1.13 and greater - if (item == null || item.IsEmpty) - slotData.Add(0); // No item - else - { - slotData.Add(1); // Item is present - slotData.AddRange(GetVarInt((int)item.Type)); - slotData.Add((byte)item.Count); - slotData.AddRange(GetNbt(item.NBT)); - } - } - else - { - // MC 1.12.2 and lower - if (item == null || item.IsEmpty) - slotData.AddRange(GetShort(-1)); - else - { - slotData.AddRange(GetShort((short)item.Type)); - slotData.Add((byte)item.Count); - slotData.AddRange(GetNbt(item.NBT)); - } - } - return slotData.ToArray(); - } - - /// - /// Get protocol block face from Direction - /// - /// Direction - /// Block face byte enum - public byte GetBlockFace(Direction direction) - { - switch (direction) - { - case Direction.Down: return 0; - case Direction.Up: return 1; - case Direction.North: return 2; - case Direction.South: return 3; - case Direction.West: return 4; - case Direction.East: return 5; - default: throw new NotImplementedException("Unknown direction: " + direction.ToString()); - } - } - - /// - /// Easily append several byte arrays - /// - /// Bytes to append - /// Array containing all the data - public 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 - public int Atoi(string str) - { - return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray())); - } + this.protocolversion = protocol; } + + /// + /// Read some data from a cache of bytes and remove it from the cache + /// + /// Amount of bytes to read + /// Cache of bytes to read from + /// The data read from the cache as an array + public byte[] ReadData(int offset, Queue cache) + { + byte[] result = new byte[offset]; + for (int i = 0; i < offset; i++) + result[i] = cache.Dequeue(); + return result; + } + + /// + /// Read a string from a cache of bytes and remove it from the cache + /// + /// Cache of bytes to read from + /// The string + public string ReadNextString(Queue cache) + { + int length = ReadNextVarInt(cache); + if (length > 0) + { + return Encoding.UTF8.GetString(ReadData(length, cache)); + } + else return ""; + } + + /// + /// Read a boolean from a cache of bytes and remove it from the cache + /// + /// The boolean value + public bool ReadNextBool(Queue cache) + { + return ReadNextByte(cache) != 0x00; + } + + /// + /// Read a short integer from a cache of bytes and remove it from the cache + /// + /// The short integer value + public short ReadNextShort(Queue 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 + public int ReadNextInt(Queue cache) + { + byte[] rawValue = ReadData(4, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToInt32(rawValue, 0); + } + + /// + /// Read a long integer from a cache of bytes and remove it from the cache + /// + /// The unsigned long integer value + public long ReadNextLong(Queue cache) + { + byte[] rawValue = ReadData(8, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToInt64(rawValue, 0); + } + + /// + /// Read an unsigned short integer from a cache of bytes and remove it from the cache + /// + /// The unsigned short integer value + public ushort ReadNextUShort(Queue cache) + { + byte[] rawValue = ReadData(2, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToUInt16(rawValue, 0); + } + + /// + /// Read an unsigned long integer from a cache of bytes and remove it from the cache + /// + /// The unsigned long integer value + public ulong ReadNextULong(Queue cache) + { + byte[] rawValue = ReadData(8, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToUInt64(rawValue, 0); + } + + /// + /// Read a Location encoded as an ulong field and remove it from the cache + /// + /// The Location value + public Location ReadNextLocation(Queue cache) + { + ulong locEncoded = ReadNextULong(cache); + int x, y, z; + if (protocolversion >= Protocol18Handler.MC114Version) + { + x = (int)(locEncoded >> 38); + y = (int)(locEncoded & 0xFFF); + z = (int)(locEncoded << 26 >> 38); + } + else + { + x = (int)(locEncoded >> 38); + y = (int)((locEncoded >> 26) & 0xFFF); + z = (int)(locEncoded << 38 >> 38); + } + if (x >= 33554432) + x -= 67108864; + if (y >= 2048) + y -= 4096; + if (z >= 33554432) + z -= 67108864; + return new Location(x, y, z); + } + + /// + /// 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 + public ushort[] ReadNextUShortsLittleEndian(int amount, Queue cache) + { + byte[] rawValues = ReadData(2 * amount, cache); + ushort[] result = new ushort[amount]; + for (int i = 0; i < amount; i++) + result[i] = BitConverter.ToUInt16(rawValues, i * 2); + return result; + } + + /// + /// Read a uuid from a cache of bytes and remove it from the cache + /// + /// Cache of bytes to read from + /// The uuid + public Guid ReadNextUUID(Queue cache) + { + byte[] javaUUID = ReadData(16, cache); + Guid guid; + if (BitConverter.IsLittleEndian) + { + // Convert big-endian Java UUID to little-endian .NET GUID + byte[] netGUID = new byte[16]; + for (int i = 8; i < 16; i++) + netGUID[i] = javaUUID[i]; + netGUID[3] = javaUUID[0]; + netGUID[2] = javaUUID[1]; + netGUID[1] = javaUUID[2]; + netGUID[0] = javaUUID[3]; + netGUID[5] = javaUUID[4]; + netGUID[4] = javaUUID[5]; + netGUID[6] = javaUUID[7]; + netGUID[7] = javaUUID[6]; + guid = new Guid(netGUID); + } + else + { + guid = new Guid(javaUUID); + } + return guid; + } + + /// + /// Read a byte array from a cache of bytes and remove it from the cache + /// + /// Cache of bytes to read from + /// The byte array + public byte[] ReadNextByteArray(Queue cache) + { + int len = protocolversion >= Protocol18Handler.MC18Version + ? ReadNextVarInt(cache) + : ReadNextShort(cache); + return ReadData(len, cache); + } + + /// + /// Reads a length-prefixed array of unsigned long integers and removes it from the cache + /// + /// The unsigned long integer values + public ulong[] ReadNextULongArray(Queue cache) + { + int len = ReadNextVarInt(cache); + ulong[] result = new ulong[len]; + for (int i = 0; i < len; i++) + result[i] = ReadNextULong(cache); + return result; + } + + /// + /// Read a double from a cache of bytes and remove it from the cache + /// + /// The double value + public double ReadNextDouble(Queue cache) + { + byte[] rawValue = ReadData(8, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToDouble(rawValue, 0); + } + + /// + /// Read a float from a cache of bytes and remove it from the cache + /// + /// The float value + public float ReadNextFloat(Queue cache) + { + byte[] rawValue = ReadData(4, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToSingle(rawValue, 0); + } + + /// + /// Read an integer from the network + /// + /// The integer + public int ReadNextVarIntRAW(SocketWrapper socket) + { + int i = 0; + int j = 0; + int k = 0; + while (true) + { + k = socket.ReadDataRAW(1)[0]; + i |= (k & 0x7F) << j++ * 7; + if (j > 5) throw new OverflowException("VarInt too big"); + if ((k & 0x80) != 128) break; + } + return i; + } + + /// + /// Read an integer from a cache of bytes and remove it from the cache + /// + /// Cache of bytes to read from + /// The integer + public int ReadNextVarInt(Queue cache) + { + string rawData = BitConverter.ToString(cache.ToArray()); + int i = 0; + int j = 0; + int k = 0; + while (true) + { + k = ReadNextByte(cache); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) throw new OverflowException("VarInt too big " + rawData); + if ((k & 0x80) != 128) break; + } + return i; + } + + /// + /// Read an integer from a cache of bytes and remove it from the cache + /// + /// Cache of bytes to read from + /// The integer + public int[] ReadNextVarIntArray(Queue cache) + { + string rawData = BitConverter.ToString(cache); + int i = 0; + int j = 0; + int k = 0; + while (true) + { + k = ReadNextByte(cache); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) throw new OverflowException("VarInt too big " + rawData); + if ((k & 0x80) != 128) break; + } + 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 + public int ReadNextVarShort(Queue cache) + { + ushort low = ReadNextUShort(cache); + byte high = 0; + if ((low & 0x8000) != 0) + { + low &= 0x7FFF; + high = ReadNextByte(cache); + } + return ((high & 0xFF) << 15) | low; + } + + /// + /// Read a single byte from a cache of bytes and remove it from the cache + /// + /// The byte that was read + public byte ReadNextByte(Queue cache) + { + byte result = cache.Dequeue(); + return result; + } + + /// + /// Read an uncompressed Named Binary Tag blob and remove it from the cache + /// + public Dictionary ReadNextNbt(Queue cache) + { + return ReadNextNbt(cache, true); + } + + /// + /// Read a single item slot from a cache of bytes and remove it from the cache + /// + /// The item that was read or NULL for an empty slot + public Item ReadNextItemSlot(Queue cache) + { + List slotData = new List(); + if (protocolversion > Protocol18Handler.MC113Version) + { + // MC 1.13 and greater + bool itemPresent = ReadNextBool(cache); + if (itemPresent) + { + int itemID = ReadNextVarInt(cache); + byte itemCount = ReadNextByte(cache); + Dictionary nbt = ReadNextNbt(cache); + return new Item(itemID, itemCount, nbt); + } + else return null; + } + else + { + // MC 1.12.2 and lower + short itemID = ReadNextShort(cache); + if (itemID == -1) + return null; + byte itemCount = ReadNextByte(cache); + short itemDamage = ReadNextShort(cache); + Dictionary nbt = ReadNextNbt(cache); + return new Item(itemID, itemCount, nbt); + } + } + + /// + /// Read entity information from a cache of bytes and remove it from the cache + /// + /// Mappings for converting entity type Ids to EntityType + /// TRUE for living entities (layout differs) + /// Entity information + public Entity ReadNextEntity(Queue cache, EntityPalette entityPalette, bool living) + { + int entityID = ReadNextVarInt(cache); + Guid entityUUID = Guid.Empty; + + if (protocolversion > Protocol18Handler.MC18Version) + { + entityUUID = ReadNextUUID(cache); + } + + EntityType entityType = entityPalette.FromId(ReadNextVarInt(cache), living); + Double entityX = ReadNextDouble(cache); + Double entityY = ReadNextDouble(cache); + Double entityZ = ReadNextDouble(cache); + byte entityYaw = ReadNextByte(cache); + byte entityPitch = ReadNextByte(cache); + + if (living) + { + byte entityHeadPitch = ReadNextByte(cache); + } + else + { + int metadata = ReadNextInt(cache); + } + + short velocityX = ReadNextShort(cache); + short velocityY = ReadNextShort(cache); + short velocityZ = ReadNextShort(cache); + + return new Entity(entityID, entityType, new Location(entityX, entityY, entityZ)); + } + + /// + /// Read an uncompressed Named Binary Tag blob and remove it from the cache (internal) + /// + private Dictionary ReadNextNbt(Queue cache, bool root) + { + Dictionary nbtData = new Dictionary(); + + if (root) + { + if (cache.Peek() == 0) // TAG_End + { + cache.Dequeue(); + return nbtData; + } + if (cache.Peek() != 10) // TAG_Compound + throw new System.IO.InvalidDataException("Failed to decode NBT: Does not start with TAG_Compound"); + ReadNextByte(cache); // Tag type (TAG_Compound) + + // NBT root name + string rootName = Encoding.ASCII.GetString(ReadData(ReadNextUShort(cache), cache)); + if (!String.IsNullOrEmpty(rootName)) + nbtData[""] = rootName; + } + + while (true) + { + int fieldType = ReadNextByte(cache); + + if (fieldType == 0) // TAG_End + return nbtData; + + int fieldNameLength = ReadNextUShort(cache); + string fieldName = Encoding.ASCII.GetString(ReadData(fieldNameLength, cache)); + object fieldValue = ReadNbtField(cache, fieldType); + + // This will override previous tags with the same name + nbtData[fieldName] = fieldValue; + } + } + + /// + /// Read a single Named Binary Tag field of the specified type and remove it from the cache + /// + private object ReadNbtField(Queue cache, int fieldType) + { + switch (fieldType) + { + case 1: // TAG_Byte + return ReadNextByte(cache); + case 2: // TAG_Short + return ReadNextShort(cache); + case 3: // TAG_Int + return ReadNextInt(cache); + case 4: // TAG_Long + return ReadNextLong(cache); + case 5: // TAG_Float + return ReadNextFloat(cache); + case 6: // TAG_Double + return ReadNextDouble(cache); + case 7: // TAG_Byte_Array + return ReadData(ReadNextInt(cache), cache); + case 8: // TAG_String + return Encoding.UTF8.GetString(ReadData(ReadNextUShort(cache), cache)); + case 9: // TAG_List + int listType = ReadNextByte(cache); + int listLength = ReadNextInt(cache); + object[] listItems = new object[listLength]; + for (int i = 0; i < listLength; i++) + listItems[i] = ReadNbtField(cache, listType); + return listItems; + case 10: // TAG_Compound + return ReadNextNbt(cache, false); + case 11: // TAG_Int_Array + listType = 3; + listLength = ReadNextInt(cache); + listItems = new object[listLength]; + for (int i = 0; i < listLength; i++) + listItems[i] = ReadNbtField(cache, listType); + return listItems; + case 12: // TAG_Long_Array + listType = 4; + listLength = ReadNextInt(cache); + listItems = new object[listLength]; + for (int i = 0; i < listLength; i++) + listItems[i] = ReadNbtField(cache, listType); + return listItems; + default: + throw new System.IO.InvalidDataException("Failed to decode NBT: Unknown field type " + fieldType); + } + } + + /// + /// Build an uncompressed Named Binary Tag blob for sending over the network + /// + /// Dictionary to encode as Nbt + /// Byte array for this NBT tag + public byte[] GetNbt(Dictionary nbt) + { + return GetNbt(nbt, true); + } + + /// + /// Build an uncompressed Named Binary Tag blob for sending over the network (internal) + /// + /// Dictionary to encode as Nbt + /// TRUE if starting a new NBT tag, FALSE if processing a nested NBT tag + /// Byte array for this NBT tag + private byte[] GetNbt(Dictionary nbt, bool root) + { + if (nbt == null || nbt.Count == 0) + return new byte[] { 0 }; // TAG_End + + List bytes = new List(); + + if (root) + { + bytes.Add(10); // TAG_Compound + + // NBT root name + string rootName = null; + if (nbt.ContainsKey("")) + rootName = nbt[""] as string; + if (rootName == null) + rootName = ""; + bytes.AddRange(GetUShort((ushort)rootName.Length)); + bytes.AddRange(Encoding.ASCII.GetBytes(rootName)); + } + + foreach (var item in nbt) + { + // Skip NBT root name + if (item.Key == "" && root) + continue; + + byte fieldType; + byte[] fieldNameLength = GetUShort((ushort)item.Key.Length); + byte[] fieldName = Encoding.ASCII.GetBytes(item.Key); + byte[] fieldData = GetNbtField(item.Value, out fieldType); + bytes.Add(fieldType); + bytes.AddRange(fieldNameLength); + bytes.AddRange(fieldName); + bytes.AddRange(fieldData); + } + + bytes.Add(0); // TAG_End + return bytes.ToArray(); + } + + /// + /// Convert a single object into its NBT representation (internal) + /// + /// Object to convert + /// Field type for the passed object + /// Binary data for the passed object + private byte[] GetNbtField(object obj, out byte fieldType) + { + if (obj is byte) + { + fieldType = 1; // TAG_Byte + return new[] { (byte)obj }; + } + else if (obj is short) + { + fieldType = 2; // TAG_Short + return GetShort((short)obj); + } + else if (obj is int) + { + fieldType = 3; // TAG_Int + return GetInt((int)obj); + } + else if (obj is long) + { + fieldType = 4; // TAG_Long + return GetLong((long)obj); + } + else if (obj is float) + { + fieldType = 5; // TAG_Float + return GetFloat((float)obj); + } + else if (obj is double) + { + fieldType = 6; // TAG_Double + return GetDouble((double)obj); + } + else if (obj is byte[]) + { + fieldType = 7; // TAG_Byte_Array + return (byte[])obj; + } + else if (obj is string) + { + fieldType = 8; // TAG_String + byte[] stringBytes = Encoding.UTF8.GetBytes((string)obj); + return ConcatBytes(GetUShort((ushort)stringBytes.Length), stringBytes); + } + else if (obj is object[]) + { + fieldType = 9; // TAG_List + + List list = new List((object[])obj); + int arrayLengthTotal = list.Count; + + // Treat empty list as TAG_Byte, length 0 + if (arrayLengthTotal == 0) + return ConcatBytes(new[] { (byte)1 }, GetInt(0)); + + // Encode first list item, retain its type + byte firstItemType; + string firstItemTypeString = list[0].GetType().Name; + byte[] firstItemBytes = GetNbtField(list[0], out firstItemType); + list.RemoveAt(0); + + // Encode further list items, check they have the same type + byte subsequentItemType; + List subsequentItemsBytes = new List(); + foreach (object item in list) + { + subsequentItemsBytes.AddRange(GetNbtField(item, out subsequentItemType)); + if (subsequentItemType != firstItemType) + throw new System.IO.InvalidDataException( + "GetNbt: Cannot encode object[] list with mixed types: " + firstItemTypeString + ", " + item.GetType().Name + " into NBT!"); + } + + // Build NBT list: type, length, item array + return ConcatBytes(new[] { firstItemType }, GetInt(arrayLengthTotal), firstItemBytes, subsequentItemsBytes.ToArray()); + } + else if (obj is Dictionary) + { + fieldType = 10; // TAG_Compound + return GetNbt((Dictionary)obj, false); + } + else if (obj is int[]) + { + fieldType = 11; // TAG_Int_Array + + int[] srcIntList = (int[])obj; + List encIntList = new List(); + encIntList.AddRange(GetInt(srcIntList.Length)); + foreach (int item in srcIntList) + encIntList.AddRange(GetInt(item)); + return encIntList.ToArray(); + } + else if (obj is long[]) + { + fieldType = 12; // TAG_Long_Array + + long[] srcLongList = (long[])obj; + List encLongList = new List(); + encLongList.AddRange(GetInt(srcLongList.Length)); + foreach (long item in srcLongList) + encLongList.AddRange(GetLong(item)); + return encLongList.ToArray(); + } + else + { + throw new System.IO.InvalidDataException("GetNbt: Cannot encode data type " + obj.GetType().Name + " into NBT!"); + } + } + + /// + /// Build an integer for sending over the network + /// + /// Integer to encode + /// Byte array for this integer + public 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(); + } + + /// + /// Get byte array representing a long integer + /// + /// Long to process + /// Array ready to send + public byte[] GetLong(long number) + { + byte[] theLong = BitConverter.GetBytes(number); + Array.Reverse(theLong); + return theLong; + } + + /// + /// Get byte array representing an integer + /// + /// Integer to process + /// Array ready to send + public byte[] GetInt(int number) + { + byte[] theInt = BitConverter.GetBytes(number); + Array.Reverse(theInt); + return theInt; + } + + /// + /// Get byte array representing a short + /// + /// Short to process + /// Array ready to send + public byte[] GetShort(short number) + { + byte[] theShort = BitConverter.GetBytes(number); + Array.Reverse(theShort); + return theShort; + } + + /// + /// Get byte array representing an unsigned short + /// + /// Short to process + /// Array ready to send + public byte[] GetUShort(ushort number) + { + byte[] theShort = BitConverter.GetBytes(number); + Array.Reverse(theShort); + return theShort; + } + + /// + /// Get byte array representing a double + /// + /// Double to process + /// Array ready to send + public byte[] GetDouble(double number) + { + byte[] theDouble = BitConverter.GetBytes(number); + Array.Reverse(theDouble); //Endianness + return theDouble; + } + + /// + /// Get byte array representing a float + /// + /// Floalt to process + /// Array ready to send + public byte[] GetFloat(float number) + { + byte[] theFloat = BitConverter.GetBytes(number); + Array.Reverse(theFloat); //Endianness + return theFloat; + } + + /// + /// Get byte array with length information prepended to it + /// + /// Array to process + /// Array ready to send + public byte[] GetArray(byte[] array) + { + if (protocolversion < Protocol18Handler.MC18Version) + { + byte[] length = BitConverter.GetBytes((short)array.Length); + Array.Reverse(length); //Endianness + return ConcatBytes(length, array); + } + else return ConcatBytes(GetVarInt(array.Length), array); + } + + /// + /// Get a byte array from the given string for sending over the network, with length information prepended. + /// + /// String to process + /// Array ready to send + public byte[] GetString(string text) + { + byte[] bytes = Encoding.UTF8.GetBytes(text); + return ConcatBytes(GetVarInt(bytes.Length), bytes); + } + + /// + /// Get a byte array representing the given location encoded as an 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 byte[] GetLocation(Location location) + { + byte[] locationBytes; + if (protocolversion >= Protocol18Handler.MC114Version) + { + locationBytes = BitConverter.GetBytes(((((ulong)location.X) & 0x3FFFFFF) << 38) | ((((ulong)location.Z) & 0x3FFFFFF) << 12) | (((ulong)location.Y) & 0xFFF)); + } + else locationBytes = BitConverter.GetBytes(((((ulong)location.X) & 0x3FFFFFF) << 38) | ((((ulong)location.Y) & 0xFFF) << 26) | (((ulong)location.Z) & 0x3FFFFFF)); + Array.Reverse(locationBytes); //Endianness + return locationBytes; + } + + /// + /// Get a byte array representing the given item as an item slot + /// + /// Item + /// Item slot representation + public byte[] GetItemSlot(Item item) + { + List slotData = new List(); + if (protocolversion > Protocol18Handler.MC113Version) + { + // MC 1.13 and greater + if (item == null || item.IsEmpty) + slotData.Add(0); // No item + else + { + slotData.Add(1); // Item is present + slotData.AddRange(GetVarInt((int)item.Type)); + slotData.Add((byte)item.Count); + slotData.AddRange(GetNbt(item.NBT)); + } + } + else + { + // MC 1.12.2 and lower + if (item == null || item.IsEmpty) + slotData.AddRange(GetShort(-1)); + else + { + slotData.AddRange(GetShort((short)item.Type)); + slotData.Add((byte)item.Count); + slotData.AddRange(GetNbt(item.NBT)); + } + } + return slotData.ToArray(); + } + + /// + /// Get protocol block face from Direction + /// + /// Direction + /// Block face byte enum + public byte GetBlockFace(Direction direction) + { + switch (direction) + { + case Direction.Down: return 0; + case Direction.Up: return 1; + case Direction.North: return 2; + case Direction.South: return 3; + case Direction.West: return 4; + case Direction.East: return 5; + default: throw new NotImplementedException("Unknown direction: " + direction.ToString()); + } + } + + /// + /// Easily append several byte arrays + /// + /// Bytes to append + /// Array containing all the data + public 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 + public int Atoi(string str) + { + return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray())); + } + } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index f4a043ba..d9909966 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -15,1585 +15,1618 @@ using MinecraftClient.Inventory; namespace MinecraftClient.Protocol.Handlers { - /// - /// Implementation for Minecraft 1.7.X+ Protocols - /// - /// - /// Typical update steps for implementing protocol changes for a new Minecraft version: - /// - Perform a diff between latest supported version in MCC and new stable version to support on https://wiki.vg/Protocol - /// - If there are any changes in packets implemented by MCC, add MCXXXVersion field below and implement new packet layouts - /// - If packet IDs were changed, also update getPacketIncomingType() and getPacketOutgoingID() inside Protocol18PacketTypes.cs - /// - Also see Material.cs and ItemType.cs for updating block and item data inside MCC - /// - class Protocol18Handler : IMinecraftCom + /// + /// Implementation for Minecraft 1.7.X+ Protocols + /// + /// + /// Typical update steps for implementing protocol changes for a new Minecraft version: + /// - Perform a diff between latest supported version in MCC and new stable version to support on https://wiki.vg/Protocol + /// - If there are any changes in packets implemented by MCC, add MCXXXVersion field below and implement new packet layouts + /// - If packet IDs were changed, also update getPacketIncomingType() and getPacketOutgoingID() inside Protocol18PacketTypes.cs + /// - Also see Material.cs and ItemType.cs for updating block and item data inside MCC + /// + class Protocol18Handler : IMinecraftCom + { + internal const int MC18Version = 47; + internal const int MC19Version = 107; + internal const int MC191Version = 108; + internal const int MC110Version = 210; + internal const int MC1112Version = 316; + internal const int MC112Version = 335; + internal const int MC1122Version = 340; + internal const int MC113Version = 393; + internal const int MC114Version = 477; + internal const int MC115Version = 573; + internal const int MC1152Version = 578; + internal const int MC116Version = 735; + internal const int MC1161Version = 736; + + private int compression_treshold = 0; + private bool autocomplete_received = false; + private int autocomplete_transaction_id = 0; + private readonly List autocomplete_result = new List(); + private readonly Dictionary window_actions = new Dictionary(); + private bool login_phase = true; + private int protocolversion; + private int currentDimension; + + Protocol18Forge pForge; + Protocol18Terrain pTerrain; + IMinecraftComHandler handler; + EntityPalette entityPalette; + SocketWrapper socketWrapper; + DataTypes dataTypes; + Thread netRead; + + public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo forgeInfo) { - internal const int MC18Version = 47; - internal const int MC19Version = 107; - internal const int MC191Version = 108; - internal const int MC110Version = 210; - internal const int MC1112Version = 316; - internal const int MC112Version = 335; - internal const int MC1122Version = 340; - internal const int MC113Version = 393; - internal const int MC114Version = 477; - internal const int MC115Version = 573; - internal const int MC1152Version = 578; - internal const int MC116Version = 735; - internal const int MC1161Version = 736; + ConsoleIO.SetAutoCompleteEngine(this); + ChatParser.InitTranslations(); + this.socketWrapper = new SocketWrapper(Client); + this.dataTypes = new DataTypes(protocolVersion); + this.protocolversion = protocolVersion; + this.handler = handler; + this.pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler); + this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler); - private int compression_treshold = 0; - private bool autocomplete_received = false; - private int autocomplete_transaction_id = 0; - private readonly List autocomplete_result = new List(); - private readonly Dictionary window_actions = new Dictionary(); - private bool login_phase = true; - private int protocolversion; - private int currentDimension; + if (handler.GetTerrainEnabled() && protocolversion > MC1152Version) + { + ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); + handler.SetTerrainEnabled(false); + } - Protocol18Forge pForge; - Protocol18Terrain pTerrain; - IMinecraftComHandler handler; - EntityPalette entityPalette; - SocketWrapper socketWrapper; - DataTypes dataTypes; - Thread netRead; + if (handler.GetInventoryEnabled() && (protocolversion < MC110Version || protocolversion > MC1152Version)) + { + ConsoleIO.WriteLineFormatted("§8Inventories are currently not handled for that MC version."); + handler.SetInventoryEnabled(false); + } - public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo forgeInfo) + if (handler.GetEntityHandlingEnabled() && (protocolversion <= MC1122Version || protocolversion > MC1152Version)) + { + ConsoleIO.WriteLineFormatted("§8Entities are currently not handled for that MC version."); + handler.SetEntityHandlingEnabled(false); + } + + if (protocolversion >= MC113Version) + { + if (protocolVersion > MC1152Version && handler.GetTerrainEnabled()) + throw new NotImplementedException("Please update block types handling for this Minecraft version. See Material.cs"); + if (protocolVersion >= MC115Version) + Block.Palette = new Palette115(); + else if (protocolVersion >= MC114Version) + Block.Palette = new Palette114(); + else Block.Palette = new Palette113(); + } + else Block.Palette = new Palette112(); + + if (protocolversion >= MC114Version) + { + if (protocolversion > MC1152Version && handler.GetEntityHandlingEnabled()) + throw new NotImplementedException("Please update entity types handling for this Minecraft version. See EntityType.cs"); + if (protocolversion >= MC115Version) + entityPalette = new EntityPalette115(); + else entityPalette = new EntityPalette114(); + } + else entityPalette = new EntityPalette113(); + } + + /// + /// Separate thread. Network reading loop. + /// + private void Updater() + { + try + { + do { - ConsoleIO.SetAutoCompleteEngine(this); - ChatParser.InitTranslations(); - this.socketWrapper = new SocketWrapper(Client); - this.dataTypes = new DataTypes(protocolVersion); - this.protocolversion = protocolVersion; - this.handler = handler; - this.pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler); - this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler); - - if (handler.GetTerrainEnabled() && protocolversion > MC1152Version) - { - ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); - handler.SetTerrainEnabled(false); - } - - if (handler.GetInventoryEnabled() && (protocolversion < MC110Version || protocolversion > MC1152Version)) - { - ConsoleIO.WriteLineFormatted("§8Inventories are currently not handled for that MC version."); - handler.SetInventoryEnabled(false); - } - - if (handler.GetEntityHandlingEnabled() && (protocolversion <= MC1122Version || protocolversion > MC1152Version)) - { - ConsoleIO.WriteLineFormatted("§8Entities are currently not handled for that MC version."); - handler.SetEntityHandlingEnabled(false); - } - - if (protocolversion >= MC113Version) - { - if (protocolVersion > MC1152Version && handler.GetTerrainEnabled()) - throw new NotImplementedException("Please update block types handling for this Minecraft version. See Material.cs"); - if (protocolVersion >= MC115Version) - Block.Palette = new Palette115(); - else if (protocolVersion >= MC114Version) - Block.Palette = new Palette114(); - else Block.Palette = new Palette113(); - } - else Block.Palette = new Palette112(); - - if (protocolversion >= MC114Version) - { - if (protocolversion > MC1152Version && handler.GetEntityHandlingEnabled()) - throw new NotImplementedException("Please update entity types handling for this Minecraft version. See EntityType.cs"); - if (protocolversion >= MC115Version) - entityPalette = new EntityPalette115(); - else entityPalette = new EntityPalette114(); - } - else entityPalette = new EntityPalette113(); + Thread.Sleep(100); } + while (Update()); + } + catch (System.IO.IOException) { } + catch (SocketException) { } + catch (ObjectDisposedException) { } - /// - /// Separate thread. Network reading loop. - /// - private void Updater() + handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, ""); + } + + /// + /// Read data from the network. Should be called on a separate thread. + /// + /// FALSE if an error occured, TRUE otherwise. + private bool Update() + { + handler.OnUpdate(); + if (!socketWrapper.IsConnected()) + return false; + try + { + while (socketWrapper.HasDataAvailable()) { - try - { - do + int packetID = 0; + Queue packetData = new Queue(); + ReadNextPacket(ref packetID, packetData); + HandlePacket(packetID, new Queue(packetData)); + } + } + catch (System.IO.IOException) { return false; } + catch (SocketException) { return false; } + catch (NullReferenceException) { return false; } + return true; + } + + /// + /// Read the next packet from the network + /// + /// will contain packet ID + /// will contain raw packet Data + internal void ReadNextPacket(ref int packetID, Queue packetData) + { + packetData.Clear(); + int size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size + byte[] rawpacket = socketWrapper.ReadDataRAW(size);//Packet contents + for (int i = 0; i < rawpacket.Length; i++) + packetData.Enqueue(rawpacket[i]); + + //Handle packet decompression + if (protocolversion >= MC18Version + && compression_treshold > 0) + { + int sizeUncompressed = dataTypes.ReadNextVarInt(packetData); + if (sizeUncompressed != 0) // != 0 means compressed, let's decompress + { + byte[] toDecompress = packetData.ToArray(); + byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed); + packetData.Clear(); + for (int i = 0; i < uncompressed.Length; i++) + packetData.Enqueue(uncompressed[i]); + } + } + + packetID = dataTypes.ReadNextVarInt(packetData); //Packet ID + } + + /// + /// Handle the given packet + /// + /// Packet ID + /// Packet contents + /// TRUE if the packet was processed, FALSE if ignored or unknown + internal bool HandlePacket(int packetID, Queue packetData) + { + try + { + if (login_phase) + { + switch (packetID) //Packet IDs are different while logging in + { + case 0x03: + if (protocolversion >= MC18Version) + compression_treshold = dataTypes.ReadNextVarInt(packetData); + break; + default: + return false; //Ignored packet + } + } + // Regular in-game packets + else switch (Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion)) + { + case PacketIncomingType.KeepAlive: + SendPacket(PacketOutgoingType.KeepAlive, packetData); + handler.OnServerKeepAlive(); + break; + case PacketIncomingType.JoinGame: + handler.OnGameJoined(); + int playerEntityID = dataTypes.ReadNextInt(packetData); + handler.OnReceivePlayerEntityID(playerEntityID); + handler.OnGamemodeUpdate(Guid.Empty, dataTypes.ReadNextByte(packetData)); + if (protocolversion >= MC116Version) + dataTypes.ReadNextByte(packetData); // Previous Gamemode - 1.16 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextVarInt(packetData); // World Count - 1.16 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextVarIntArray(packetData); // World Names - 1.16 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextNbt(packetData); // Dimension Codec - 1.16 and above + if (protocolversion >= MC191Version) + this.currentDimension = dataTypes.ReadNextInt(packetData); + else if (protocolversion >= MC116Version) + this.currentDimension = dataTypes.ReadNextString(packetData); //In 1.16 it was changed to "Identifier" which seems to be a string + else + this.currentDimension = (sbyte)dataTypes.ReadNextByte(packetData); + if (protocolversion < MC114Version) + dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below + if (protocolversion >= MC116Version) + dataTypes.ReadNextString(packetData); // World Name - 1.16 and above + if (protocolversion >= MC115Version) + dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above + + dataTypes.ReadNextByte(packetData); // Max Players + + if (protocolversion < MC116Version) + dataTypes.ReadNextString(packetData); // Level Type - 1.15 and below + if (protocolversion >= MC114Version) + dataTypes.ReadNextVarInt(packetData); // View distance - 1.14 and above + if (protocolversion >= MC18Version) + dataTypes.ReadNextBool(packetData); // Reduced debug info - 1.8 and above + if (protocolversion >= MC115Version) + dataTypes.ReadNextBool(packetData); // Enable respawn screen - 1.15 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextBool(packetData); // Is Debug - 1.16 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextBool(packetData); // Is Flat - 1.16 and above + break; + case PacketIncomingType.ChatMessage: + string message = dataTypes.ReadNextString(packetData); + try + { + //Hide system messages or xp bar messages? + byte messageType = dataTypes.ReadNextByte(packetData); + if ((messageType == 1 && !Settings.DisplaySystemMessages) + || (messageType == 2 && !Settings.DisplayXPBarMessages)) + break; + } + catch (ArgumentOutOfRangeException) { /* No message type */ } + handler.OnTextReceived(message, true); + break; + case PacketIncomingType.Respawn: + if (protocolversion >= MC116Version) + this.currentDimension = dataTypes.ReadNextString(packetData); //In 1.16 it was changed to "Identifier" which seems to be a string + else + this.currentDimension = dataTypes.ReadNextInt(packetData); + if (protocolversion >= MC116Version) + dataTypes.ReadNextString(packetData); // World Name - 1.16 and above + if (protocolversion < MC114Version) + dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below + if (protocolversion >= MC115Version) + dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above + dataTypes.ReadNextByte(packetData); // Gamemode + if (protocolversion >= MC116Version) + dataTypes.ReadNextByte(packetData); // Previous Game mode - 1.16 and above + if (protocolversion < MC116Version) + dataTypes.ReadNextString(packetData); // Level Type - 1.15 and below + if (protocolversion >= MC116Version) + dataTypes.ReadNextBool(packetData); // Is Debug - 1.16 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextBool(packetData); // Is Flat - 1.16 and above + if (protocolversion >= MC116Version) + dataTypes.ReadNextBool(packetData); // Copy metadata - 1.16 and above + handler.OnRespawn(); + break; + case PacketIncomingType.PlayerPositionAndLook: + // These always need to be read, since we need the field after them for teleport confirm + double x = dataTypes.ReadNextDouble(packetData); + double y = dataTypes.ReadNextDouble(packetData); + double z = dataTypes.ReadNextDouble(packetData); + float yaw = dataTypes.ReadNextFloat(packetData); + float pitch = dataTypes.ReadNextFloat(packetData); + byte locMask = dataTypes.ReadNextByte(packetData); + + // entity handling require player pos for distance calculating + if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled()) + { + if (protocolversion >= MC18Version) { - Thread.Sleep(100); + 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, yaw, pitch); } - while (Update()); - } - catch (System.IO.IOException) { } - catch (SocketException) { } - catch (ObjectDisposedException) { } + else handler.UpdateLocation(new Location(x, y, z), yaw, pitch); + } - handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, ""); - } - - /// - /// Read data from the network. Should be called on a separate thread. - /// - /// FALSE if an error occured, TRUE otherwise. - private bool Update() - { - handler.OnUpdate(); - if (!socketWrapper.IsConnected()) - return false; - try - { - while (socketWrapper.HasDataAvailable()) + if (protocolversion >= MC19Version) + { + int teleportID = dataTypes.ReadNextVarInt(packetData); + // Teleport confirm packet + SendPacket(PacketOutgoingType.TeleportConfirm, dataTypes.GetVarInt(teleportID)); + } + break; + case PacketIncomingType.ChunkData: + if (handler.GetTerrainEnabled()) + { + int chunkX = dataTypes.ReadNextInt(packetData); + int chunkZ = dataTypes.ReadNextInt(packetData); + bool chunksContinuous = dataTypes.ReadNextBool(packetData); + ushort chunkMask = protocolversion >= MC19Version + ? (ushort)dataTypes.ReadNextVarInt(packetData) + : dataTypes.ReadNextUShort(packetData); + if (protocolversion < MC18Version) { - int packetID = 0; - Queue packetData = new Queue(); - ReadNextPacket(ref packetID, packetData); - HandlePacket(packetID, new Queue(packetData)); - } - } - catch (System.IO.IOException) { return false; } - catch (SocketException) { return false; } - catch (NullReferenceException) { return false; } - return true; - } - - /// - /// Read the next packet from the network - /// - /// will contain packet ID - /// will contain raw packet Data - internal void ReadNextPacket(ref int packetID, Queue packetData) - { - packetData.Clear(); - int size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size - byte[] rawpacket = socketWrapper.ReadDataRAW(size);//Packet contents - for (int i = 0; i < rawpacket.Length; i++) - packetData.Enqueue(rawpacket[i]); - - //Handle packet decompression - if (protocolversion >= MC18Version - && compression_treshold > 0) - { - int sizeUncompressed = dataTypes.ReadNextVarInt(packetData); - if (sizeUncompressed != 0) // != 0 means compressed, let's decompress - { - byte[] toDecompress = packetData.ToArray(); - byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed); - packetData.Clear(); - for (int i = 0; i < uncompressed.Length; i++) - packetData.Enqueue(uncompressed[i]); - } - } - - packetID = dataTypes.ReadNextVarInt(packetData); //Packet ID - } - - /// - /// Handle the given packet - /// - /// Packet ID - /// Packet contents - /// TRUE if the packet was processed, FALSE if ignored or unknown - internal bool HandlePacket(int packetID, Queue packetData) - { - try - { - if (login_phase) - { - switch (packetID) //Packet IDs are different while logging in - { - case 0x03: - if (protocolversion >= MC18Version) - compression_treshold = dataTypes.ReadNextVarInt(packetData); - break; - default: - return false; //Ignored packet - } - } - // Regular in-game packets - else switch (Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion)) - { - case PacketIncomingType.KeepAlive: - SendPacket(PacketOutgoingType.KeepAlive, packetData); - handler.OnServerKeepAlive(); - break; - case PacketIncomingType.JoinGame: - handler.OnGameJoined(); - int playerEntityID = dataTypes.ReadNextInt(packetData); - handler.OnReceivePlayerEntityID(playerEntityID); - handler.OnGamemodeUpdate(Guid.Empty, dataTypes.ReadNextByte(packetData)); - if (protocolversion >= MC191Version) - this.currentDimension = dataTypes.ReadNextInt(packetData); - else - this.currentDimension = (sbyte)dataTypes.ReadNextByte(packetData); - if (protocolversion < MC114Version) - dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below - if (protocolversion >= MC115Version) - dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above - dataTypes.ReadNextByte(packetData); - dataTypes.ReadNextString(packetData); - if (protocolversion >= MC114Version) - dataTypes.ReadNextVarInt(packetData); // View distance - 1.14 and above - if (protocolversion >= MC18Version) - dataTypes.ReadNextBool(packetData); // Reduced debug info - 1.8 and above - if (protocolversion >= MC115Version) - dataTypes.ReadNextBool(packetData); // Enable respawn screen - 1.15 and above - break; - case PacketIncomingType.ChatMessage: - string message = dataTypes.ReadNextString(packetData); - try - { - //Hide system messages or xp bar messages? - byte messageType = dataTypes.ReadNextByte(packetData); - if ((messageType == 1 && !Settings.DisplaySystemMessages) - || (messageType == 2 && !Settings.DisplayXPBarMessages)) - break; - } - catch (ArgumentOutOfRangeException) { /* No message type */ } - handler.OnTextReceived(message, true); - break; - case PacketIncomingType.Respawn: - this.currentDimension = dataTypes.ReadNextInt(packetData); - if (protocolversion < MC114Version) - dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below - if (protocolversion >= MC115Version) - dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above - dataTypes.ReadNextByte(packetData); - dataTypes.ReadNextString(packetData); - handler.OnRespawn(); - break; - case PacketIncomingType.PlayerPositionAndLook: - // These always need to be read, since we need the field after them for teleport confirm - double x = dataTypes.ReadNextDouble(packetData); - double y = dataTypes.ReadNextDouble(packetData); - double z = dataTypes.ReadNextDouble(packetData); - float yaw = dataTypes.ReadNextFloat(packetData); - float pitch = dataTypes.ReadNextFloat(packetData); - byte locMask = dataTypes.ReadNextByte(packetData); - - // entity handling require player pos for distance calculating - if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled()) - { - if (protocolversion >= MC18Version) - { - 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, yaw, pitch); - } - else handler.UpdateLocation(new Location(x, y, z), yaw, pitch); - } - - if (protocolversion >= MC19Version) - { - int teleportID = dataTypes.ReadNextVarInt(packetData); - // Teleport confirm packet - SendPacket(PacketOutgoingType.TeleportConfirm, dataTypes.GetVarInt(teleportID)); - } - break; - case PacketIncomingType.ChunkData: - if (handler.GetTerrainEnabled()) - { - int chunkX = dataTypes.ReadNextInt(packetData); - int chunkZ = dataTypes.ReadNextInt(packetData); - bool chunksContinuous = dataTypes.ReadNextBool(packetData); - ushort chunkMask = protocolversion >= MC19Version - ? (ushort)dataTypes.ReadNextVarInt(packetData) - : dataTypes.ReadNextUShort(packetData); - if (protocolversion < MC18Version) - { - ushort addBitmap = dataTypes.ReadNextUShort(packetData); - int compressedDataSize = dataTypes.ReadNextInt(packetData); - byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); - byte[] decompressed = ZlibUtils.Decompress(compressed); - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed)); - } - else - { - if (protocolversion >= MC114Version) - dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above - if (protocolversion >= MC115Version && chunksContinuous) - dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above - int dataSize = dataTypes.ReadNextVarInt(packetData); - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); - } - } - break; - case PacketIncomingType.MapData: - int mapid = dataTypes.ReadNextVarInt(packetData); - byte scale = dataTypes.ReadNextByte(packetData); - bool trackingposition = dataTypes.ReadNextBool(packetData); - bool locked = false; - if (protocolversion >= MC114Version) - { - locked = dataTypes.ReadNextBool(packetData); - } - int iconcount = dataTypes.ReadNextVarInt(packetData); - handler.OnMapData(mapid, scale, trackingposition, locked, iconcount); - break; - case PacketIncomingType.Title: - if (protocolversion >= MC18Version) - { - int action2 = dataTypes.ReadNextVarInt(packetData); - string titletext = String.Empty; - string subtitletext = String.Empty; - string actionbartext = String.Empty; - string json = String.Empty; - int fadein = -1; - int stay = -1; - int fadeout = -1; - if (protocolversion >= MC110Version) - { - if (action2 == 0) - { - json = titletext; - titletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); - } - else if (action2 == 1) - { - json = subtitletext; - subtitletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); - } - else if (action2 == 2) - { - json = actionbartext; - actionbartext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); - } - else if (action2 == 3) - { - fadein = dataTypes.ReadNextInt(packetData); - stay = dataTypes.ReadNextInt(packetData); - fadeout = dataTypes.ReadNextInt(packetData); - } - } - else - { - if (action2 == 0) - { - json = titletext; - titletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); - } - else if (action2 == 1) - { - json = subtitletext; - subtitletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); - } - else if (action2 == 2) - { - fadein = dataTypes.ReadNextInt(packetData); - stay = dataTypes.ReadNextInt(packetData); - fadeout = dataTypes.ReadNextInt(packetData); - } - } - handler.OnTitle(action2, titletext, subtitletext, actionbartext, fadein, stay, fadeout, json); - } - break; - case PacketIncomingType.MultiBlockChange: - if (handler.GetTerrainEnabled()) - { - int chunkX = dataTypes.ReadNextInt(packetData); - int chunkZ = dataTypes.ReadNextInt(packetData); - int recordCount = protocolversion < MC18Version - ? (int)dataTypes.ReadNextShort(packetData) - : dataTypes.ReadNextVarInt(packetData); - - for (int i = 0; i < recordCount; i++) - { - byte locationXZ; - ushort blockIdMeta; - int blockY; - - if (protocolversion < MC18Version) - { - blockIdMeta = dataTypes.ReadNextUShort(packetData); - blockY = (ushort)dataTypes.ReadNextByte(packetData); - locationXZ = dataTypes.ReadNextByte(packetData); - } - else - { - locationXZ = dataTypes.ReadNextByte(packetData); - blockY = (ushort)dataTypes.ReadNextByte(packetData); - blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData); - } - - int blockX = locationXZ >> 4; - int blockZ = locationXZ & 0x0F; - Block block = new Block(blockIdMeta); - handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); - } - } - break; - case PacketIncomingType.BlockChange: - if (handler.GetTerrainEnabled()) - { - if (protocolversion < MC18Version) - { - int blockX = dataTypes.ReadNextInt(packetData); - int blockY = dataTypes.ReadNextByte(packetData); - int blockZ = dataTypes.ReadNextInt(packetData); - short blockId = (short)dataTypes.ReadNextVarInt(packetData); - byte blockMeta = dataTypes.ReadNextByte(packetData); - handler.GetWorld().SetBlock(new Location(blockX, blockY, blockZ), new Block(blockId, blockMeta)); - } - else handler.GetWorld().SetBlock(dataTypes.ReadNextLocation(packetData), new Block((ushort)dataTypes.ReadNextVarInt(packetData))); - } - break; - case PacketIncomingType.MapChunkBulk: - if (protocolversion < MC19Version && handler.GetTerrainEnabled()) - { - int chunkCount; - bool hasSkyLight; - Queue chunkData = packetData; - - //Read global fields - if (protocolversion < MC18Version) - { - chunkCount = dataTypes.ReadNextShort(packetData); - int compressedDataSize = dataTypes.ReadNextInt(packetData); - hasSkyLight = dataTypes.ReadNextBool(packetData); - byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); - byte[] decompressed = ZlibUtils.Decompress(compressed); - chunkData = new Queue(decompressed); - } - else - { - hasSkyLight = dataTypes.ReadNextBool(packetData); - chunkCount = dataTypes.ReadNextVarInt(packetData); - } - - //Read chunk records - int[] chunkXs = new int[chunkCount]; - int[] chunkZs = new int[chunkCount]; - ushort[] chunkMasks = new ushort[chunkCount]; - ushort[] addBitmaps = new ushort[chunkCount]; - for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) - { - chunkXs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); - chunkZs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); - chunkMasks[chunkColumnNo] = dataTypes.ReadNextUShort(packetData); - addBitmaps[chunkColumnNo] = protocolversion < MC18Version - ? dataTypes.ReadNextUShort(packetData) - : (ushort)0; - } - - //Process chunk records - for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) - pTerrain.ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true, currentDimension, chunkData); - } - break; - case PacketIncomingType.UnloadChunk: - if (protocolversion >= MC19Version && handler.GetTerrainEnabled()) - { - int chunkX = dataTypes.ReadNextInt(packetData); - int chunkZ = dataTypes.ReadNextInt(packetData); - handler.GetWorld()[chunkX, chunkZ] = null; - } - break; - case PacketIncomingType.PlayerListUpdate: - if (protocolversion >= MC18Version) - { - int action = dataTypes.ReadNextVarInt(packetData); - int numActions = dataTypes.ReadNextVarInt(packetData); - for (int i = 0; i < numActions; i++) - { - Guid uuid = dataTypes.ReadNextUUID(packetData); - switch (action) - { - case 0x00: //Player Join - string name = dataTypes.ReadNextString(packetData); - int propNum = dataTypes.ReadNextVarInt(packetData); - for (int p = 0; p < propNum; p++) - { - string key = dataTypes.ReadNextString(packetData); - string val = dataTypes.ReadNextString(packetData); - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - } - handler.OnGamemodeUpdate(uuid, dataTypes.ReadNextVarInt(packetData)); - dataTypes.ReadNextVarInt(packetData); - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - handler.OnPlayerJoin(uuid, name); - break; - case 0x01: //Update gamemode - handler.OnGamemodeUpdate(uuid, dataTypes.ReadNextVarInt(packetData)); - break; - case 0x02: //Update latency - int latency = dataTypes.ReadNextVarInt(packetData); - handler.OnLatencyUpdate(uuid, latency); //Update latency; - break; - case 0x03: //Update display name - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - break; - case 0x04: //Player Leave - handler.OnPlayerLeave(uuid); - break; - default: - //Unknown player list item type - break; - } - } - } - else //MC 1.7.X does not provide UUID in tab-list updates - { - string name = dataTypes.ReadNextString(packetData); - bool online = dataTypes.ReadNextBool(packetData); - short ping = dataTypes.ReadNextShort(packetData); - Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); - if (online) - handler.OnPlayerJoin(FakeUUID, name); - else handler.OnPlayerLeave(FakeUUID); - } - break; - case PacketIncomingType.TabCompleteResult: - if (protocolversion >= MC113Version) - { - autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData); - dataTypes.ReadNextVarInt(packetData); // Start of text to replace - dataTypes.ReadNextVarInt(packetData); // Length of text to replace - } - - int autocomplete_count = dataTypes.ReadNextVarInt(packetData); - autocomplete_result.Clear(); - - for (int i = 0; i < autocomplete_count; i++) - { - autocomplete_result.Add(dataTypes.ReadNextString(packetData)); - if (protocolversion >= MC113Version) - { - // Skip optional tooltip for each tab-complete result - if (dataTypes.ReadNextBool(packetData)) - dataTypes.ReadNextString(packetData); - } - } - - autocomplete_received = true; - break; - case PacketIncomingType.PluginMessage: - String channel = dataTypes.ReadNextString(packetData); - // Length is unneeded as the whole remaining packetData is the entire payload of the packet. - if (protocolversion < MC18Version) - pForge.ReadNextVarShort(packetData); - handler.OnPluginChannelMessage(channel, packetData.ToArray()); - return pForge.HandlePluginMessage(channel, packetData, ref currentDimension); - case PacketIncomingType.KickPacket: - handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); - return false; - case PacketIncomingType.NetworkCompressionTreshold: - if (protocolversion >= MC18Version && protocolversion < MC19Version) - compression_treshold = dataTypes.ReadNextVarInt(packetData); - break; - case PacketIncomingType.OpenWindow: - if (handler.GetInventoryEnabled()) - { - if (protocolversion < MC114Version) - { - // MC 1.13 or lower - byte windowID = dataTypes.ReadNextByte(packetData); - string type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "").ToUpper(); - ContainerTypeOld inventoryType = (ContainerTypeOld)Enum.Parse(typeof(ContainerTypeOld), type); - string title = dataTypes.ReadNextString(packetData); - byte slots = dataTypes.ReadNextByte(packetData); - Container inventory = new Container(windowID, inventoryType, ChatParser.ParseText(title)); - handler.OnInventoryOpen(windowID, inventory); - } - else - { - // MC 1.14 or greater - int windowID = dataTypes.ReadNextVarInt(packetData); - int windowType = dataTypes.ReadNextVarInt(packetData); - string title = dataTypes.ReadNextString(packetData); - Container inventory = new Container(windowID, windowType, ChatParser.ParseText(title)); - handler.OnInventoryOpen(windowID, inventory); - } - } - break; - case PacketIncomingType.CloseWindow: - if (handler.GetInventoryEnabled()) - { - byte windowID = dataTypes.ReadNextByte(packetData); - lock (window_actions) { window_actions[windowID] = 0; } - handler.OnInventoryClose(windowID); - } - break; - case PacketIncomingType.WindowItems: - if (handler.GetInventoryEnabled()) - { - byte windowId = dataTypes.ReadNextByte(packetData); - short elements = dataTypes.ReadNextShort(packetData); - Dictionary inventorySlots = new Dictionary(); - for (short slotId = 0; slotId < elements; slotId++) - { - Item item = dataTypes.ReadNextItemSlot(packetData); - if (item != null) - inventorySlots[slotId] = item; - } - handler.OnWindowItems(windowId, inventorySlots); - } - break; - case PacketIncomingType.SetSlot: - if (handler.GetInventoryEnabled()) - { - byte windowID = dataTypes.ReadNextByte(packetData); - short slotID = dataTypes.ReadNextShort(packetData); - Item item = dataTypes.ReadNextItemSlot(packetData); - handler.OnSetSlot(windowID, slotID, item); - } - break; - case PacketIncomingType.ResourcePackSend: - string url = dataTypes.ReadNextString(packetData); - string hash = dataTypes.ReadNextString(packetData); - // Some server plugins may send invalid resource packs to probe the client and we need to ignore them (issue #1056) - if (hash.Length != 40) - break; - //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory - byte[] responseHeader = new byte[0]; - if (protocolversion < MC110Version) //MC 1.10 does not include resource pack hash in responses - responseHeader = dataTypes.ConcatBytes(dataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash)); - SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(3))); //Accepted pack - SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(0))); //Successfully loaded - break; - case PacketIncomingType.SpawnEntity: - if (handler.GetEntityHandlingEnabled()) - { - Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, false); - handler.OnSpawnEntity(entity); - } - break; - case PacketIncomingType.EntityEquipment: - if (handler.GetEntityHandlingEnabled()) - { - int entityid = dataTypes.ReadNextVarInt(packetData); - int slot2 = dataTypes.ReadNextVarInt(packetData); - Item item = dataTypes.ReadNextItemSlot(packetData); - handler.OnEntityEquipment(entityid, slot2, item); - } - break; - case PacketIncomingType.SpawnLivingEntity: - if (handler.GetEntityHandlingEnabled()) - { - Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, true); - // packet before 1.15 has metadata at the end - // this is not handled in dataTypes.ReadNextEntity() - // we are simply ignoring leftover data in packet - handler.OnSpawnEntity(entity); - } - break; - case PacketIncomingType.SpawnPlayer: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Guid UUID = dataTypes.ReadNextUUID(packetData); - double X = dataTypes.ReadNextDouble(packetData); - double Y = dataTypes.ReadNextDouble(packetData); - double Z = dataTypes.ReadNextDouble(packetData); - byte Yaw = dataTypes.ReadNextByte(packetData); - byte Pitch = dataTypes.ReadNextByte(packetData); - - Location EntityLocation = new Location(X, Y, Z); - - handler.OnSpawnPlayer(EntityID, UUID, EntityLocation, Yaw, Pitch); - } - break; - case PacketIncomingType.DestroyEntities: - if (handler.GetEntityHandlingEnabled()) - { - int EntityCount = dataTypes.ReadNextVarInt(packetData); - int[] EntitiesList = new int[EntityCount]; - for (int i = 0; i < EntityCount; i++) - { - EntitiesList[i] = dataTypes.ReadNextVarInt(packetData); - } - handler.OnDestroyEntities(EntitiesList); - } - break; - case PacketIncomingType.EntityPosition: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - bool OnGround = dataTypes.ReadNextBool(packetData); - DeltaX = DeltaX / (128 * 32); - DeltaY = DeltaY / (128 * 32); - DeltaZ = DeltaZ / (128 * 32); - handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); - } - break; - case PacketIncomingType.EntityPositionAndRotation: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); - byte _yaw = dataTypes.ReadNextByte(packetData); - byte _pitch = dataTypes.ReadNextByte(packetData); - bool OnGround = dataTypes.ReadNextBool(packetData); - DeltaX = DeltaX / (128 * 32); - DeltaY = DeltaY / (128 * 32); - DeltaZ = DeltaZ / (128 * 32); - handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); - } - break; - case PacketIncomingType.EntityProperties: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - int NumberOfProperties = dataTypes.ReadNextInt(packetData); - Dictionary keys = new Dictionary(); - for (int i = 0; i < NumberOfProperties; i++) - { - string _key = dataTypes.ReadNextString(packetData); - Double _value = dataTypes.ReadNextDouble(packetData); - - List op0 = new List(); - List op1 = new List(); - List op2 = new List(); - int NumberOfModifiers = dataTypes.ReadNextVarInt(packetData); - for (int j = 0; j < NumberOfModifiers; j++) - { - dataTypes.ReadNextUUID(packetData); - Double amount = dataTypes.ReadNextDouble(packetData); - byte operation = dataTypes.ReadNextByte(packetData); - switch (operation) - { - case 0: op0.Add(amount); break; - case 1: op1.Add(amount); break; - case 2: op2.Add(amount + 1); break; - } - } - if (op0.Count > 0) _value += op0.Sum(); - if (op1.Count > 0) _value *= 1 + op1.Sum(); - if (op2.Count > 0) _value *= op2.Aggregate((a, _x) => a * _x); - keys.Add(_key, _value); - } - handler.OnEntityProperties(EntityID, keys); - } - break; - case PacketIncomingType.TimeUpdate: - long WorldAge = dataTypes.ReadNextLong(packetData); - long TimeOfday = dataTypes.ReadNextLong(packetData); - handler.OnTimeUpdate(WorldAge, TimeOfday); - break; - case PacketIncomingType.EntityTeleport: - if (handler.GetEntityHandlingEnabled()) - { - int EntityID = dataTypes.ReadNextVarInt(packetData); - Double X = dataTypes.ReadNextDouble(packetData); - Double Y = dataTypes.ReadNextDouble(packetData); - Double Z = dataTypes.ReadNextDouble(packetData); - byte EntityYaw = dataTypes.ReadNextByte(packetData); - byte EntityPitch = dataTypes.ReadNextByte(packetData); - bool OnGround = dataTypes.ReadNextBool(packetData); - handler.OnEntityTeleport(EntityID, X, Y, Z, OnGround); - } - break; - case PacketIncomingType.UpdateHealth: - float health = dataTypes.ReadNextFloat(packetData); - int food; - if (protocolversion >= MC18Version) - food = dataTypes.ReadNextVarInt(packetData); - else - food = dataTypes.ReadNextShort(packetData); - dataTypes.ReadNextFloat(packetData); // Food Saturation - handler.OnUpdateHealth(health, food); - break; - case PacketIncomingType.SetExperience: - float experiencebar = dataTypes.ReadNextFloat(packetData); - int level = dataTypes.ReadNextVarInt(packetData); - int totalexperience = dataTypes.ReadNextVarInt(packetData); - handler.OnSetExperience(experiencebar, level, totalexperience); - break; - case PacketIncomingType.Explosion: - Location explosionLocation = new Location(dataTypes.ReadNextFloat(packetData), dataTypes.ReadNextFloat(packetData), dataTypes.ReadNextFloat(packetData)); - float explosionStrength = dataTypes.ReadNextFloat(packetData); - int explosionBlockCount = dataTypes.ReadNextInt(packetData); - // Ignoring additional fields (records, pushback) - handler.OnExplosion(explosionLocation, explosionStrength, explosionBlockCount); - break; - case PacketIncomingType.HeldItemChange: - byte slot = dataTypes.ReadNextByte(packetData); - handler.OnHeldItemChange(slot); - break; - default: - return false; //Ignored packet - } - return true; //Packet processed - } - catch (Exception innerException) - { - if (innerException is ThreadAbortException || innerException is SocketException || innerException.InnerException is SocketException) - throw; //Thread abort or Connection lost rather than invalid data - throw new System.IO.InvalidDataException( - String.Format("Failed to process incoming packet of type {0}. (PacketID: {1}, Protocol: {2}, LoginPhase: {3}, InnerException: {4}).", - Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion), - packetID, - protocolversion, - login_phase, - innerException.GetType()), - innerException); - } - } - - /// - /// 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(); - socketWrapper.Disconnect(); - } - } - catch { } - } - - /// - /// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically. - /// - /// packet type - /// packet Data - private void SendPacket(PacketOutgoingType packet, IEnumerable packetData) - { - SendPacket(Protocol18PacketTypes.GetPacketOutgoingID(packet, protocolversion), packetData); - } - - /// - /// Send a packet to the server. Compression and encryption will be handled automatically. - /// - /// packet ID - /// packet Data - private void SendPacket(int packetID, IEnumerable packetData) - { - //The inner packet - byte[] the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(packetID), packetData.ToArray()); - - if (compression_treshold > 0) //Compression enabled? - { - if (the_packet.Length >= compression_treshold) //Packet long enough for compressing? - { - byte[] compressed_packet = ZlibUtils.Compress(the_packet); - the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), compressed_packet); + ushort addBitmap = dataTypes.ReadNextUShort(packetData); + int compressedDataSize = dataTypes.ReadNextInt(packetData); + byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed)); } else { - byte[] uncompressed_length = dataTypes.GetVarInt(0); //Not compressed (short packet) - the_packet = dataTypes.ConcatBytes(uncompressed_length, the_packet); + if (protocolversion >= MC114Version) + dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above + if (protocolversion >= MC115Version && chunksContinuous) + dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above + int dataSize = dataTypes.ReadNextVarInt(packetData); + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); } - } - - socketWrapper.SendDataRAW(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet)); - } - - /// - /// Do the Minecraft login. - /// - /// True if login successful - public bool Login() - { - byte[] protocol_version = dataTypes.GetVarInt(protocolversion); - string server_address = pForge.GetServerAddress(handler.GetServerHost()); - byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port); - byte[] next_state = dataTypes.GetVarInt(2); - byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state); - - SendPacket(0x00, handshake_packet); - - byte[] login_packet = dataTypes.GetString(handler.GetUsername()); - - SendPacket(0x00, login_packet); - - int packetID = -1; - Queue packetData = new Queue(); - while (true) - { - ReadNextPacket(ref packetID, packetData); - if (packetID == 0x00) //Login rejected + } + break; + case PacketIncomingType.MapData: + int mapid = dataTypes.ReadNextVarInt(packetData); + byte scale = dataTypes.ReadNextByte(packetData); + bool trackingposition = dataTypes.ReadNextBool(packetData); + bool locked = false; + if (protocolversion >= MC114Version) + { + locked = dataTypes.ReadNextBool(packetData); + } + int iconcount = dataTypes.ReadNextVarInt(packetData); + handler.OnMapData(mapid, scale, trackingposition, locked, iconcount); + break; + case PacketIncomingType.Title: + if (protocolversion >= MC18Version) + { + int action2 = dataTypes.ReadNextVarInt(packetData); + string titletext = String.Empty; + string subtitletext = String.Empty; + string actionbartext = String.Empty; + string json = String.Empty; + int fadein = -1; + int stay = -1; + int fadeout = -1; + if (protocolversion >= MC110Version) { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); - return false; + if (action2 == 0) + { + json = titletext; + titletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); + } + else if (action2 == 1) + { + json = subtitletext; + subtitletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); + } + else if (action2 == 2) + { + json = actionbartext; + actionbartext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); + } + else if (action2 == 3) + { + fadein = dataTypes.ReadNextInt(packetData); + stay = dataTypes.ReadNextInt(packetData); + fadeout = dataTypes.ReadNextInt(packetData); + } } - else if (packetID == 0x01) //Encryption request + else { - string serverID = dataTypes.ReadNextString(packetData); - byte[] Serverkey = dataTypes.ReadNextByteArray(packetData); - byte[] token = dataTypes.ReadNextByteArray(packetData); - return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); + if (action2 == 0) + { + json = titletext; + titletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); + } + else if (action2 == 1) + { + json = subtitletext; + subtitletext = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); + } + else if (action2 == 2) + { + fadein = dataTypes.ReadNextInt(packetData); + stay = dataTypes.ReadNextInt(packetData); + fadeout = dataTypes.ReadNextInt(packetData); + } } - else if (packetID == 0x02) //Login successful + handler.OnTitle(action2, titletext, subtitletext, actionbartext, fadein, stay, fadeout, json); + } + break; + case PacketIncomingType.MultiBlockChange: + if (handler.GetTerrainEnabled()) + { + int chunkX = dataTypes.ReadNextInt(packetData); + int chunkZ = dataTypes.ReadNextInt(packetData); + int recordCount = protocolversion < MC18Version + ? (int)dataTypes.ReadNextShort(packetData) + : dataTypes.ReadNextVarInt(packetData); + + for (int i = 0; i < recordCount; i++) { - ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); - login_phase = false; + byte locationXZ; + ushort blockIdMeta; + int blockY; - if (!pForge.CompleteForgeHandshake()) - { - ConsoleIO.WriteLineFormatted("§8Forge Login Handshake did not complete successfully"); - return false; - } + if (protocolversion < MC18Version) + { + blockIdMeta = dataTypes.ReadNextUShort(packetData); + blockY = (ushort)dataTypes.ReadNextByte(packetData); + locationXZ = dataTypes.ReadNextByte(packetData); + } + else + { + locationXZ = dataTypes.ReadNextByte(packetData); + blockY = (ushort)dataTypes.ReadNextByte(packetData); + blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData); + } - StartUpdating(); - return true; //No need to check session or start encryption + int blockX = locationXZ >> 4; + int blockZ = locationXZ & 0x0F; + Block block = new Block(blockIdMeta); + handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); } - else HandlePacket(packetID, packetData); - } - } - - /// - /// 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(); - - if (Settings.DebugMessages) - ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated."); - - if (serverIDhash != "-") - { - Console.WriteLine("Checking Session..."); - if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey))) + } + break; + case PacketIncomingType.BlockChange: + if (handler.GetTerrainEnabled()) + { + if (protocolversion < MC18Version) { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session."); - return false; + int blockX = dataTypes.ReadNextInt(packetData); + int blockY = dataTypes.ReadNextByte(packetData); + int blockZ = dataTypes.ReadNextInt(packetData); + short blockId = (short)dataTypes.ReadNextVarInt(packetData); + byte blockMeta = dataTypes.ReadNextByte(packetData); + handler.GetWorld().SetBlock(new Location(blockX, blockY, blockZ), new Block(blockId, blockMeta)); } - } + else handler.GetWorld().SetBlock(dataTypes.ReadNextLocation(packetData), new Block((ushort)dataTypes.ReadNextVarInt(packetData))); + } + break; + case PacketIncomingType.MapChunkBulk: + if (protocolversion < MC19Version && handler.GetTerrainEnabled()) + { + int chunkCount; + bool hasSkyLight; + Queue chunkData = packetData; - //Encrypt the data - byte[] key_enc = dataTypes.GetArray(RSAService.Encrypt(secretKey, false)); - byte[] token_enc = dataTypes.GetArray(RSAService.Encrypt(token, false)); - - //Encryption Response packet - SendPacket(0x01, dataTypes.ConcatBytes(key_enc, token_enc)); - - //Start client-side encryption - socketWrapper.SwitchToEncrypted(secretKey); - - //Process the next packet - int packetID = -1; - Queue packetData = new Queue(); - while (true) - { - ReadNextPacket(ref packetID, packetData); - if (packetID == 0x00) //Login rejected + //Read global fields + if (protocolversion < MC18Version) { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); - return false; + chunkCount = dataTypes.ReadNextShort(packetData); + int compressedDataSize = dataTypes.ReadNextInt(packetData); + hasSkyLight = dataTypes.ReadNextBool(packetData); + byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + chunkData = new Queue(decompressed); } - else if (packetID == 0x02) //Login successful + else { - login_phase = false; - - if (!pForge.CompleteForgeHandshake()) - { - ConsoleIO.WriteLineFormatted("§8Forge StartEncryption Handshake did not complete successfully"); - return false; - } - - StartUpdating(); - return true; + hasSkyLight = dataTypes.ReadNextBool(packetData); + chunkCount = dataTypes.ReadNextVarInt(packetData); } - else HandlePacket(packetID, packetData); - } - } - /// - /// Disconnect from the server - /// - public void Disconnect() - { - socketWrapper.Disconnect(); - } + //Read chunk records + int[] chunkXs = new int[chunkCount]; + int[] chunkZs = new int[chunkCount]; + ushort[] chunkMasks = new ushort[chunkCount]; + ushort[] addBitmaps = new ushort[chunkCount]; + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + { + chunkXs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); + chunkZs[chunkColumnNo] = dataTypes.ReadNextInt(packetData); + chunkMasks[chunkColumnNo] = dataTypes.ReadNextUShort(packetData); + addBitmaps[chunkColumnNo] = protocolversion < MC18Version + ? dataTypes.ReadNextUShort(packetData) + : (ushort)0; + } - /// - /// Autocomplete text while typing username or command - /// - /// Text behind cursor - /// Completed text - IEnumerable IAutoComplete.AutoComplete(string BehindCursor) - { - if (String.IsNullOrEmpty(BehindCursor)) - return new string[] { }; + //Process chunk records + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + pTerrain.ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true, currentDimension, chunkData); + } + break; + case PacketIncomingType.UnloadChunk: + if (protocolversion >= MC19Version && handler.GetTerrainEnabled()) + { + int chunkX = dataTypes.ReadNextInt(packetData); + int chunkZ = dataTypes.ReadNextInt(packetData); + handler.GetWorld()[chunkX, chunkZ] = null; + } + break; + case PacketIncomingType.PlayerListUpdate: + if (protocolversion >= MC18Version) + { + int action = dataTypes.ReadNextVarInt(packetData); + int numActions = dataTypes.ReadNextVarInt(packetData); + for (int i = 0; i < numActions; i++) + { + Guid uuid = dataTypes.ReadNextUUID(packetData); + switch (action) + { + case 0x00: //Player Join + string name = dataTypes.ReadNextString(packetData); + int propNum = dataTypes.ReadNextVarInt(packetData); + for (int p = 0; p < propNum; p++) + { + string key = dataTypes.ReadNextString(packetData); + string val = dataTypes.ReadNextString(packetData); + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + } + handler.OnGamemodeUpdate(uuid, dataTypes.ReadNextVarInt(packetData)); + dataTypes.ReadNextVarInt(packetData); + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + handler.OnPlayerJoin(uuid, name); + break; + case 0x01: //Update gamemode + handler.OnGamemodeUpdate(uuid, dataTypes.ReadNextVarInt(packetData)); + break; + case 0x02: //Update latency + int latency = dataTypes.ReadNextVarInt(packetData); + handler.OnLatencyUpdate(uuid, latency); //Update latency; + break; + case 0x03: //Update display name + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + break; + case 0x04: //Player Leave + handler.OnPlayerLeave(uuid); + break; + default: + //Unknown player list item type + break; + } + } + } + else //MC 1.7.X does not provide UUID in tab-list updates + { + string name = dataTypes.ReadNextString(packetData); + bool online = dataTypes.ReadNextBool(packetData); + short ping = dataTypes.ReadNextShort(packetData); + Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); + if (online) + handler.OnPlayerJoin(FakeUUID, name); + else handler.OnPlayerLeave(FakeUUID); + } + break; + case PacketIncomingType.TabCompleteResult: + if (protocolversion >= MC113Version) + { + autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData); + dataTypes.ReadNextVarInt(packetData); // Start of text to replace + dataTypes.ReadNextVarInt(packetData); // Length of text to replace + } - byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id); - byte[] assume_command = new byte[] { 0x00 }; - byte[] has_position = new byte[] { 0x00 }; + int autocomplete_count = dataTypes.ReadNextVarInt(packetData); + autocomplete_result.Clear(); - byte[] tabcomplete_packet = new byte[] { }; - - if (protocolversion >= MC18Version) - { + for (int i = 0; i < autocomplete_count; i++) + { + autocomplete_result.Add(dataTypes.ReadNextString(packetData)); if (protocolversion >= MC113Version) { - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id); - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + // Skip optional tooltip for each tab-complete result + if (dataTypes.ReadNextBool(packetData)) + dataTypes.ReadNextString(packetData); + } + } + + autocomplete_received = true; + break; + case PacketIncomingType.PluginMessage: + String channel = dataTypes.ReadNextString(packetData); + // Length is unneeded as the whole remaining packetData is the entire payload of the packet. + if (protocolversion < MC18Version) + pForge.ReadNextVarShort(packetData); + handler.OnPluginChannelMessage(channel, packetData.ToArray()); + return pForge.HandlePluginMessage(channel, packetData, ref currentDimension); + case PacketIncomingType.KickPacket: + handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); + return false; + case PacketIncomingType.NetworkCompressionTreshold: + if (protocolversion >= MC18Version && protocolversion < MC19Version) + compression_treshold = dataTypes.ReadNextVarInt(packetData); + break; + case PacketIncomingType.OpenWindow: + if (handler.GetInventoryEnabled()) + { + if (protocolversion < MC114Version) + { + // MC 1.13 or lower + byte windowID = dataTypes.ReadNextByte(packetData); + string type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "").ToUpper(); + ContainerTypeOld inventoryType = (ContainerTypeOld)Enum.Parse(typeof(ContainerTypeOld), type); + string title = dataTypes.ReadNextString(packetData); + byte slots = dataTypes.ReadNextByte(packetData); + Container inventory = new Container(windowID, inventoryType, ChatParser.ParseText(title)); + handler.OnInventoryOpen(windowID, inventory); } else { - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + // MC 1.14 or greater + int windowID = dataTypes.ReadNextVarInt(packetData); + int windowType = dataTypes.ReadNextVarInt(packetData); + string title = dataTypes.ReadNextString(packetData); + Container inventory = new Container(windowID, windowType, ChatParser.ParseText(title)); + handler.OnInventoryOpen(windowID, inventory); + } + } + break; + case PacketIncomingType.CloseWindow: + if (handler.GetInventoryEnabled()) + { + byte windowID = dataTypes.ReadNextByte(packetData); + lock (window_actions) { window_actions[windowID] = 0; } + handler.OnInventoryClose(windowID); + } + break; + case PacketIncomingType.WindowItems: + if (handler.GetInventoryEnabled()) + { + byte windowId = dataTypes.ReadNextByte(packetData); + short elements = dataTypes.ReadNextShort(packetData); + Dictionary inventorySlots = new Dictionary(); + for (short slotId = 0; slotId < elements; slotId++) + { + Item item = dataTypes.ReadNextItemSlot(packetData); + if (item != null) + inventorySlots[slotId] = item; + } + handler.OnWindowItems(windowId, inventorySlots); + } + break; + case PacketIncomingType.SetSlot: + if (handler.GetInventoryEnabled()) + { + byte windowID = dataTypes.ReadNextByte(packetData); + short slotID = dataTypes.ReadNextShort(packetData); + Item item = dataTypes.ReadNextItemSlot(packetData); + handler.OnSetSlot(windowID, slotID, item); + } + break; + case PacketIncomingType.ResourcePackSend: + string url = dataTypes.ReadNextString(packetData); + string hash = dataTypes.ReadNextString(packetData); + // Some server plugins may send invalid resource packs to probe the client and we need to ignore them (issue #1056) + if (hash.Length != 40) + break; + //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory + byte[] responseHeader = new byte[0]; + if (protocolversion < MC110Version) //MC 1.10 does not include resource pack hash in responses + responseHeader = dataTypes.ConcatBytes(dataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash)); + SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(3))); //Accepted pack + SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(0))); //Successfully loaded + break; + case PacketIncomingType.SpawnEntity: + if (handler.GetEntityHandlingEnabled()) + { + Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, false); + handler.OnSpawnEntity(entity); + } + break; + case PacketIncomingType.EntityEquipment: + if (handler.GetEntityHandlingEnabled()) + { + int entityid = dataTypes.ReadNextVarInt(packetData); + int slot2 = dataTypes.ReadNextVarInt(packetData); + Item item = dataTypes.ReadNextItemSlot(packetData); + handler.OnEntityEquipment(entityid, slot2, item); + } + break; + case PacketIncomingType.SpawnLivingEntity: + if (handler.GetEntityHandlingEnabled()) + { + Entity entity = dataTypes.ReadNextEntity(packetData, entityPalette, true); + // packet before 1.15 has metadata at the end + // this is not handled in dataTypes.ReadNextEntity() + // we are simply ignoring leftover data in packet + handler.OnSpawnEntity(entity); + } + break; + case PacketIncomingType.SpawnPlayer: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Guid UUID = dataTypes.ReadNextUUID(packetData); + double X = dataTypes.ReadNextDouble(packetData); + double Y = dataTypes.ReadNextDouble(packetData); + double Z = dataTypes.ReadNextDouble(packetData); + byte Yaw = dataTypes.ReadNextByte(packetData); + byte Pitch = dataTypes.ReadNextByte(packetData); - if (protocolversion >= MC19Version) + Location EntityLocation = new Location(X, Y, Z); + + handler.OnSpawnPlayer(EntityID, UUID, EntityLocation, Yaw, Pitch); + } + break; + case PacketIncomingType.DestroyEntities: + if (handler.GetEntityHandlingEnabled()) + { + int EntityCount = dataTypes.ReadNextVarInt(packetData); + int[] EntitiesList = new int[EntityCount]; + for (int i = 0; i < EntityCount; i++) + { + EntitiesList[i] = dataTypes.ReadNextVarInt(packetData); + } + handler.OnDestroyEntities(EntitiesList); + } + break; + case PacketIncomingType.EntityPosition: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + bool OnGround = dataTypes.ReadNextBool(packetData); + DeltaX = DeltaX / (128 * 32); + DeltaY = DeltaY / (128 * 32); + DeltaZ = DeltaZ / (128 * 32); + handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); + } + break; + case PacketIncomingType.EntityPositionAndRotation: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Double DeltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + Double DeltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData)); + byte _yaw = dataTypes.ReadNextByte(packetData); + byte _pitch = dataTypes.ReadNextByte(packetData); + bool OnGround = dataTypes.ReadNextBool(packetData); + DeltaX = DeltaX / (128 * 32); + DeltaY = DeltaY / (128 * 32); + DeltaZ = DeltaZ / (128 * 32); + handler.OnEntityPosition(EntityID, DeltaX, DeltaY, DeltaZ, OnGround); + } + break; + case PacketIncomingType.EntityProperties: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + int NumberOfProperties = dataTypes.ReadNextInt(packetData); + Dictionary keys = new Dictionary(); + for (int i = 0; i < NumberOfProperties; i++) + { + string _key = dataTypes.ReadNextString(packetData); + Double _value = dataTypes.ReadNextDouble(packetData); + + List op0 = new List(); + List op1 = new List(); + List op2 = new List(); + int NumberOfModifiers = dataTypes.ReadNextVarInt(packetData); + for (int j = 0; j < NumberOfModifiers; j++) + { + dataTypes.ReadNextUUID(packetData); + Double amount = dataTypes.ReadNextDouble(packetData); + byte operation = dataTypes.ReadNextByte(packetData); + switch (operation) { - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command); + case 0: op0.Add(amount); break; + case 1: op1.Add(amount); break; + case 2: op2.Add(amount + 1); break; } - - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position); + } + if (op0.Count > 0) _value += op0.Sum(); + if (op1.Count > 0) _value *= 1 + op1.Sum(); + if (op2.Count > 0) _value *= op2.Aggregate((a, _x) => a * _x); + keys.Add(_key, _value); } - } - else - { - tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor)); - } - - autocomplete_received = false; - autocomplete_result.Clear(); - autocomplete_result.Add(BehindCursor); - SendPacket(PacketOutgoingType.TabComplete, 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--; } - if (autocomplete_result.Count > 0) - ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false); - return autocomplete_result; - } - - /// - /// Ping a Minecraft server to get information about the server - /// - /// True if ping was successful - public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo) - { - string version = ""; - TcpClient tcp = ProxyHandler.newTcpClient(host, port); - tcp.ReceiveBufferSize = 1024 * 1024; - SocketWrapper socketWrapper = new SocketWrapper(tcp); - DataTypes dataTypes = new DataTypes(MC18Version); - - byte[] packet_id = dataTypes.GetVarInt(0); - byte[] protocol_version = dataTypes.GetVarInt(-1); - byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port); - byte[] next_state = dataTypes.GetVarInt(1); - byte[] packet = dataTypes.ConcatBytes(packet_id, protocol_version, dataTypes.GetString(host), server_port, next_state); - byte[] tosend = dataTypes.ConcatBytes(dataTypes.GetVarInt(packet.Length), packet); - - socketWrapper.SendDataRAW(tosend); - - byte[] status_request = dataTypes.GetVarInt(0); - byte[] request_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(status_request.Length), status_request); - - socketWrapper.SendDataRAW(request_packet); - - int packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); - if (packetLength > 0) //Read Response length - { - Queue packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); - if (dataTypes.ReadNextVarInt(packetData) == 0x00) //Read Packet ID - { - string result = dataTypes.ReadNextString(packetData); //Get the Json data - - if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) - { - Json.JSONData jsonData = Json.ParseJson(result); - if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) - { - Json.JSONData versionData = jsonData.Properties["version"]; - - //Retrieve display name of the Minecraft version - if (versionData.Properties.ContainsKey("name")) - version = versionData.Properties["name"].StringValue; - - //Retrieve protocol version number for handling this server - if (versionData.Properties.ContainsKey("protocol")) - protocolversion = dataTypes.Atoi(versionData.Properties["protocol"].StringValue); - - // Check for forge on the server. - Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo); - - ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ").")); - - return true; - } - } - } - } - return false; - } - - /// - /// Get max length for chat messages - /// - /// Max length, in characters - public int GetMaxChatMessageLength() - { - return protocolversion > MC110Version - ? 256 - : 100; - } - - /// - /// 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[] message_packet = dataTypes.GetString(message); - SendPacket(PacketOutgoingType.ChatMessage, message_packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendEntityAction(int PlayerEntityID, int ActionID) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetVarInt(PlayerEntityID)); - fields.AddRange(dataTypes.GetVarInt(ActionID)); - fields.AddRange(dataTypes.GetVarInt(0)); - SendPacket(PacketOutgoingType.EntityAction, fields); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - /// - /// Send a respawn packet to the server - /// - /// True if properly sent - public bool SendRespawnPacket() - { - try - { - SendPacket(PacketOutgoingType.ClientStatus, new byte[] { 0 }); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { 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; - // Plugin channels were significantly changed between Minecraft 1.12 and 1.13 - // https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14132#Plugin_Channels - if (protocolversion >= MC113Version) - { - return SendPluginChannelPacket("minecraft:brand", dataTypes.GetString(brandInfo)); - } - else - { - return SendPluginChannelPacket("MC|Brand", dataTypes.GetString(brandInfo)); - } - } - - /// - /// Inform the server of the client's Minecraft settings - /// - /// Client language eg en_US - /// View distance, in chunks - /// Game difficulty (client-side...) - /// Chat mode (allows muting yourself) - /// Show chat colors - /// Show skin layers - /// 1.9+ main hand - /// True if client settings were successfully sent - public bool SendClientSettings(string language, byte viewDistance, byte difficulty, byte chatMode, bool chatColors, byte skinParts, byte mainHand) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetString(language)); - fields.Add(viewDistance); - fields.AddRange(protocolversion >= MC19Version - ? dataTypes.GetVarInt(chatMode) - : new byte[] { chatMode }); - fields.Add(chatColors ? (byte)1 : (byte)0); - if (protocolversion < MC18Version) - { - fields.Add(difficulty); - fields.Add((byte)(skinParts & 0x1)); //show cape - } - else fields.Add(skinParts); - if (protocolversion >= MC19Version) - fields.AddRange(dataTypes.GetVarInt(mainHand)); - SendPacket(PacketOutgoingType.ClientSettings, fields); - } - catch (SocketException) { } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - return false; - } - - /// - /// Send a location update to the server - /// - /// The new location of the player - /// True if the player is on the ground - /// Optional new yaw for updating player look - /// Optional new pitch for updating player look - /// True if the location update was successfully sent - public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null) - { - if (handler.GetTerrainEnabled()) - { - byte[] yawpitch = new byte[0]; - PacketOutgoingType packetType = PacketOutgoingType.PlayerPosition; - - if (yaw.HasValue && pitch.HasValue) - { - yawpitch = dataTypes.ConcatBytes(dataTypes.GetFloat(yaw.Value), dataTypes.GetFloat(pitch.Value)); - packetType = PacketOutgoingType.PlayerPositionAndLook; - } - - try - { - SendPacket(packetType, dataTypes.ConcatBytes( - dataTypes.GetDouble(location.X), - dataTypes.GetDouble(location.Y), - protocolversion < MC18Version - ? dataTypes.GetDouble(location.Y + 1.62) - : new byte[0], - dataTypes.GetDouble(location.Z), - yawpitch, - new byte[] { onGround ? (byte)1 : (byte)0 })); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - else return false; - } - - /// - /// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically - /// - /// Channel to send packet on - /// packet Data - public bool SendPluginChannelPacket(string channel, byte[] data) - { - try - { - // In 1.7, length needs to be included. - // In 1.8, it must not be. - if (protocolversion < MC18Version) - { - byte[] length = BitConverter.GetBytes((short)data.Length); - Array.Reverse(length); - - SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), length, data)); - } - else - { - SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), data)); - } - - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - /// - /// Send an Interact Entity Packet to server - /// - /// - /// - /// - public bool SendInteractEntity(int EntityID, int type) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); - SendPacket(PacketOutgoingType.InteractEntity, fields); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - // TODO: Interact at block location (e.g. chest minecart) - public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand) - { - try - { - List fields = new List(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); - fields.AddRange(dataTypes.GetFloat(X)); - fields.AddRange(dataTypes.GetFloat(Y)); - fields.AddRange(dataTypes.GetFloat(Z)); - fields.AddRange(dataTypes.GetVarInt(hand)); - SendPacket(PacketOutgoingType.InteractEntity, fields); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z) - { - return false; - } - - public bool SendUseItem(int hand) - { - if (protocolversion < MC19Version) - return false; // Packet does not exist prior to MC 1.9 - // According to https://wiki.vg/index.php?title=Protocol&oldid=5486#Player_Block_Placement - // MC 1.7 does this using Player Block Placement with special values - // TODO once Player Block Placement is implemented for older versions - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetVarInt(hand)); - SendPacket(PacketOutgoingType.UseItem, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendPlayerDigging(int status, Location location, Direction face) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetVarInt(status)); - packet.AddRange(dataTypes.GetLocation(location)); - packet.AddRange(dataTypes.GetVarInt(dataTypes.GetBlockFace(face))); - SendPacket(PacketOutgoingType.PlayerDigging, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendPlayerBlockPlacement(int hand, Location location, Direction face) - { - if (protocolversion < MC114Version) - return false; // NOT IMPLEMENTED for older MC versions - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetVarInt(hand)); - packet.AddRange(dataTypes.GetLocation(location)); - packet.AddRange(dataTypes.GetVarInt(dataTypes.GetBlockFace(face))); - packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorX - packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorY - packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorZ - packet.Add(0); // insideBlock = false; - SendPacket(PacketOutgoingType.PlayerBlockPlacement, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendHeldItemChange(short slot) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetShort(slot)); - SendPacket(PacketOutgoingType.HeldItemChange, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item) - { - try - { - short actionNumber; - lock (window_actions) - { - if (!window_actions.ContainsKey(windowId)) - window_actions[windowId] = 0; - actionNumber = (short)(window_actions[windowId] + 1); - window_actions[windowId] = actionNumber; - } - - byte button = 0; - byte mode = 0; - switch (action) - { - case WindowActionType.LeftClick: button = 0; break; - case WindowActionType.RightClick: button = 1; break; - case WindowActionType.MiddleClick: button = 2; mode = 3; break; - case WindowActionType.DropItem: - button = 0; - mode = 4; - item = new Item(-1, 0, null); - Container inventory = handler.GetInventory(windowId); - if (inventory.Items.ContainsKey(slotId)) - inventory.Items[slotId].Count--; // server won't update us after dropped - if (inventory.Items[slotId].Count == 0) - inventory.Items.Remove(slotId); - break; - case WindowActionType.DropItemStack: - button = 1; - mode = 4; - item = new Item(-1, 0, null); - inventory = handler.GetInventory(windowId); - inventory.Items.Remove(slotId); // server won't update us after dropped - break; - } - - List packet = new List(); - packet.Add((byte)windowId); - packet.AddRange(dataTypes.GetShort((short)slotId)); - packet.Add(button); - packet.AddRange(dataTypes.GetShort(actionNumber)); - - if (protocolversion >= MC19Version) - packet.AddRange(dataTypes.GetVarInt(mode)); - else packet.Add(mode); - - packet.AddRange(dataTypes.GetItemSlot(item)); - - SendPacket(PacketOutgoingType.ClickWindow, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendCreativeInventoryAction(int slot, ItemType itemType, int count, Dictionary nbt) - { - try - { - List packet = new List(); - packet.AddRange(dataTypes.GetShort((short)slot)); - packet.AddRange(dataTypes.GetItemSlot(new Item((int)itemType, count, nbt))); - SendPacket(PacketOutgoingType.CreativeInventoryAction, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendAnimation(int animation, int playerid) - { - try - { - if (animation == 0 || animation == 1) - { - List packet = new List(); - - if (protocolversion < MC18Version) - { - packet.AddRange(dataTypes.GetInt(playerid)); - packet.Add((byte)1); // Swing arm - } - else if (protocolversion < MC19Version) - { - // No fields in 1.8.X - } - else // MC 1.9+ - { - packet.AddRange(dataTypes.GetVarInt(animation)); - } - - SendPacket(PacketOutgoingType.Animation, packet); - return true; - } - else - { - return false; - } - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendCloseWindow(int windowId) - { - try - { - lock (window_actions) - { - if (window_actions.ContainsKey(windowId)) - window_actions[windowId] = 0; - } - SendPacket(PacketOutgoingType.CloseWindow, new[] { (byte)windowId }); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } - - public bool SendUpdateSign(Location sign, string line1, string line2, string line3, string line4) - { - try - { - if (line1.Length > 23) - line1 = line1.Substring(0, 23); - if (line2.Length > 23) - line2 = line1.Substring(0, 23); - if (line3.Length > 23) - line3 = line1.Substring(0, 23); - if (line4.Length > 23) - line4 = line1.Substring(0, 23); - - List packet = new List(); - packet.AddRange(dataTypes.GetLocation(sign)); - packet.AddRange(dataTypes.GetString(line1)); - packet.AddRange(dataTypes.GetString(line2)); - packet.AddRange(dataTypes.GetString(line3)); - packet.AddRange(dataTypes.GetString(line4)); - SendPacket(PacketOutgoingType.UpdateSign, packet); - return true; - } - catch (SocketException) { return false; } - catch (System.IO.IOException) { return false; } - catch (ObjectDisposedException) { return false; } - } + handler.OnEntityProperties(EntityID, keys); + } + break; + case PacketIncomingType.TimeUpdate: + long WorldAge = dataTypes.ReadNextLong(packetData); + long TimeOfday = dataTypes.ReadNextLong(packetData); + handler.OnTimeUpdate(WorldAge, TimeOfday); + break; + case PacketIncomingType.EntityTeleport: + if (handler.GetEntityHandlingEnabled()) + { + int EntityID = dataTypes.ReadNextVarInt(packetData); + Double X = dataTypes.ReadNextDouble(packetData); + Double Y = dataTypes.ReadNextDouble(packetData); + Double Z = dataTypes.ReadNextDouble(packetData); + byte EntityYaw = dataTypes.ReadNextByte(packetData); + byte EntityPitch = dataTypes.ReadNextByte(packetData); + bool OnGround = dataTypes.ReadNextBool(packetData); + handler.OnEntityTeleport(EntityID, X, Y, Z, OnGround); + } + break; + case PacketIncomingType.UpdateHealth: + float health = dataTypes.ReadNextFloat(packetData); + int food; + if (protocolversion >= MC18Version) + food = dataTypes.ReadNextVarInt(packetData); + else + food = dataTypes.ReadNextShort(packetData); + dataTypes.ReadNextFloat(packetData); // Food Saturation + handler.OnUpdateHealth(health, food); + break; + case PacketIncomingType.SetExperience: + float experiencebar = dataTypes.ReadNextFloat(packetData); + int level = dataTypes.ReadNextVarInt(packetData); + int totalexperience = dataTypes.ReadNextVarInt(packetData); + handler.OnSetExperience(experiencebar, level, totalexperience); + break; + case PacketIncomingType.Explosion: + Location explosionLocation = new Location(dataTypes.ReadNextFloat(packetData), dataTypes.ReadNextFloat(packetData), dataTypes.ReadNextFloat(packetData)); + float explosionStrength = dataTypes.ReadNextFloat(packetData); + int explosionBlockCount = dataTypes.ReadNextInt(packetData); + // Ignoring additional fields (records, pushback) + handler.OnExplosion(explosionLocation, explosionStrength, explosionBlockCount); + break; + case PacketIncomingType.HeldItemChange: + byte slot = dataTypes.ReadNextByte(packetData); + handler.OnHeldItemChange(slot); + break; + default: + return false; //Ignored packet + } + return true; //Packet processed + } + catch (Exception innerException) + { + if (innerException is ThreadAbortException || innerException is SocketException || innerException.InnerException is SocketException) + throw; //Thread abort or Connection lost rather than invalid data + throw new System.IO.InvalidDataException( + String.Format("Failed to process incoming packet of type {0}. (PacketID: {1}, Protocol: {2}, LoginPhase: {3}, InnerException: {4}).", + Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion), + packetID, + protocolversion, + login_phase, + innerException.GetType()), + innerException); + } } + + /// + /// 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(); + socketWrapper.Disconnect(); + } + } + catch { } + } + + /// + /// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically. + /// + /// packet type + /// packet Data + private void SendPacket(PacketOutgoingType packet, IEnumerable packetData) + { + SendPacket(Protocol18PacketTypes.GetPacketOutgoingID(packet, protocolversion), packetData); + } + + /// + /// Send a packet to the server. Compression and encryption will be handled automatically. + /// + /// packet ID + /// packet Data + private void SendPacket(int packetID, IEnumerable packetData) + { + //The inner packet + byte[] the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(packetID), packetData.ToArray()); + + if (compression_treshold > 0) //Compression enabled? + { + if (the_packet.Length >= compression_treshold) //Packet long enough for compressing? + { + byte[] compressed_packet = ZlibUtils.Compress(the_packet); + the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), compressed_packet); + } + else + { + byte[] uncompressed_length = dataTypes.GetVarInt(0); //Not compressed (short packet) + the_packet = dataTypes.ConcatBytes(uncompressed_length, the_packet); + } + } + + socketWrapper.SendDataRAW(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet)); + } + + /// + /// Do the Minecraft login. + /// + /// True if login successful + public bool Login() + { + byte[] protocol_version = dataTypes.GetVarInt(protocolversion); + string server_address = pForge.GetServerAddress(handler.GetServerHost()); + byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port); + byte[] next_state = dataTypes.GetVarInt(2); + byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state); + + SendPacket(0x00, handshake_packet); + + byte[] login_packet = dataTypes.GetString(handler.GetUsername()); + + SendPacket(0x00, login_packet); + + int packetID = -1; + Queue packetData = new Queue(); + while (true) + { + ReadNextPacket(ref packetID, packetData); + if (packetID == 0x00) //Login rejected + { + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); + return false; + } + else if (packetID == 0x01) //Encryption request + { + string serverID = dataTypes.ReadNextString(packetData); + byte[] Serverkey = dataTypes.ReadNextByteArray(packetData); + byte[] token = dataTypes.ReadNextByteArray(packetData); + return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); + } + else if (packetID == 0x02) //Login successful + { + ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); + login_phase = false; + + if (!pForge.CompleteForgeHandshake()) + { + ConsoleIO.WriteLineFormatted("§8Forge Login Handshake did not complete successfully"); + return false; + } + + StartUpdating(); + return true; //No need to check session or start encryption + } + else HandlePacket(packetID, packetData); + } + } + + /// + /// 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(); + + if (Settings.DebugMessages) + 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 = dataTypes.GetArray(RSAService.Encrypt(secretKey, false)); + byte[] token_enc = dataTypes.GetArray(RSAService.Encrypt(token, false)); + + //Encryption Response packet + SendPacket(0x01, dataTypes.ConcatBytes(key_enc, token_enc)); + + //Start client-side encryption + socketWrapper.SwitchToEncrypted(secretKey); + + //Process the next packet + int packetID = -1; + Queue packetData = new Queue(); + while (true) + { + ReadNextPacket(ref packetID, packetData); + if (packetID == 0x00) //Login rejected + { + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData))); + return false; + } + else if (packetID == 0x02) //Login successful + { + login_phase = false; + + if (!pForge.CompleteForgeHandshake()) + { + ConsoleIO.WriteLineFormatted("§8Forge StartEncryption Handshake did not complete successfully"); + return false; + } + + StartUpdating(); + return true; + } + else HandlePacket(packetID, packetData); + } + } + + /// + /// Disconnect from the server + /// + public void Disconnect() + { + socketWrapper.Disconnect(); + } + + /// + /// Autocomplete text while typing username or command + /// + /// Text behind cursor + /// Completed text + IEnumerable IAutoComplete.AutoComplete(string BehindCursor) + { + if (String.IsNullOrEmpty(BehindCursor)) + return new string[] { }; + + byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id); + byte[] assume_command = new byte[] { 0x00 }; + byte[] has_position = new byte[] { 0x00 }; + + byte[] tabcomplete_packet = new byte[] { }; + + if (protocolversion >= MC18Version) + { + if (protocolversion >= MC113Version) + { + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id); + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + } + else + { + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + + if (protocolversion >= MC19Version) + { + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command); + } + + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position); + } + } + else + { + tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor)); + } + + autocomplete_received = false; + autocomplete_result.Clear(); + autocomplete_result.Add(BehindCursor); + SendPacket(PacketOutgoingType.TabComplete, 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--; } + if (autocomplete_result.Count > 0) + ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false); + return autocomplete_result; + } + + /// + /// Ping a Minecraft server to get information about the server + /// + /// True if ping was successful + public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo) + { + string version = ""; + TcpClient tcp = ProxyHandler.newTcpClient(host, port); + tcp.ReceiveBufferSize = 1024 * 1024; + SocketWrapper socketWrapper = new SocketWrapper(tcp); + DataTypes dataTypes = new DataTypes(MC18Version); + + byte[] packet_id = dataTypes.GetVarInt(0); + byte[] protocol_version = dataTypes.GetVarInt(-1); + byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port); + byte[] next_state = dataTypes.GetVarInt(1); + byte[] packet = dataTypes.ConcatBytes(packet_id, protocol_version, dataTypes.GetString(host), server_port, next_state); + byte[] tosend = dataTypes.ConcatBytes(dataTypes.GetVarInt(packet.Length), packet); + + socketWrapper.SendDataRAW(tosend); + + byte[] status_request = dataTypes.GetVarInt(0); + byte[] request_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(status_request.Length), status_request); + + socketWrapper.SendDataRAW(request_packet); + + int packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper); + if (packetLength > 0) //Read Response length + { + Queue packetData = new Queue(socketWrapper.ReadDataRAW(packetLength)); + if (dataTypes.ReadNextVarInt(packetData) == 0x00) //Read Packet ID + { + string result = dataTypes.ReadNextString(packetData); //Get the Json data + + if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) + { + Json.JSONData jsonData = Json.ParseJson(result); + if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version")) + { + Json.JSONData versionData = jsonData.Properties["version"]; + + //Retrieve display name of the Minecraft version + if (versionData.Properties.ContainsKey("name")) + version = versionData.Properties["name"].StringValue; + + //Retrieve protocol version number for handling this server + if (versionData.Properties.ContainsKey("protocol")) + protocolversion = dataTypes.Atoi(versionData.Properties["protocol"].StringValue); + + // Check for forge on the server. + Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo); + + ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ").")); + + return true; + } + } + } + } + return false; + } + + /// + /// Get max length for chat messages + /// + /// Max length, in characters + public int GetMaxChatMessageLength() + { + return protocolversion > MC110Version + ? 256 + : 100; + } + + /// + /// 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[] message_packet = dataTypes.GetString(message); + SendPacket(PacketOutgoingType.ChatMessage, message_packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendEntityAction(int PlayerEntityID, int ActionID) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetVarInt(PlayerEntityID)); + fields.AddRange(dataTypes.GetVarInt(ActionID)); + fields.AddRange(dataTypes.GetVarInt(0)); + SendPacket(PacketOutgoingType.EntityAction, fields); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + /// + /// Send a respawn packet to the server + /// + /// True if properly sent + public bool SendRespawnPacket() + { + try + { + SendPacket(PacketOutgoingType.ClientStatus, new byte[] { 0 }); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { 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; + // Plugin channels were significantly changed between Minecraft 1.12 and 1.13 + // https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14132#Plugin_Channels + if (protocolversion >= MC113Version) + { + return SendPluginChannelPacket("minecraft:brand", dataTypes.GetString(brandInfo)); + } + else + { + return SendPluginChannelPacket("MC|Brand", dataTypes.GetString(brandInfo)); + } + } + + /// + /// Inform the server of the client's Minecraft settings + /// + /// Client language eg en_US + /// View distance, in chunks + /// Game difficulty (client-side...) + /// Chat mode (allows muting yourself) + /// Show chat colors + /// Show skin layers + /// 1.9+ main hand + /// True if client settings were successfully sent + public bool SendClientSettings(string language, byte viewDistance, byte difficulty, byte chatMode, bool chatColors, byte skinParts, byte mainHand) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetString(language)); + fields.Add(viewDistance); + fields.AddRange(protocolversion >= MC19Version + ? dataTypes.GetVarInt(chatMode) + : new byte[] { chatMode }); + fields.Add(chatColors ? (byte)1 : (byte)0); + if (protocolversion < MC18Version) + { + fields.Add(difficulty); + fields.Add((byte)(skinParts & 0x1)); //show cape + } + else fields.Add(skinParts); + if (protocolversion >= MC19Version) + fields.AddRange(dataTypes.GetVarInt(mainHand)); + SendPacket(PacketOutgoingType.ClientSettings, fields); + } + catch (SocketException) { } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + return false; + } + + /// + /// Send a location update to the server + /// + /// The new location of the player + /// True if the player is on the ground + /// Optional new yaw for updating player look + /// Optional new pitch for updating player look + /// True if the location update was successfully sent + public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null) + { + if (handler.GetTerrainEnabled()) + { + byte[] yawpitch = new byte[0]; + PacketOutgoingType packetType = PacketOutgoingType.PlayerPosition; + + if (yaw.HasValue && pitch.HasValue) + { + yawpitch = dataTypes.ConcatBytes(dataTypes.GetFloat(yaw.Value), dataTypes.GetFloat(pitch.Value)); + packetType = PacketOutgoingType.PlayerPositionAndLook; + } + + try + { + SendPacket(packetType, dataTypes.ConcatBytes( + dataTypes.GetDouble(location.X), + dataTypes.GetDouble(location.Y), + protocolversion < MC18Version + ? dataTypes.GetDouble(location.Y + 1.62) + : new byte[0], + dataTypes.GetDouble(location.Z), + yawpitch, + new byte[] { onGround ? (byte)1 : (byte)0 })); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + else return false; + } + + /// + /// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically + /// + /// Channel to send packet on + /// packet Data + public bool SendPluginChannelPacket(string channel, byte[] data) + { + try + { + // In 1.7, length needs to be included. + // In 1.8, it must not be. + if (protocolversion < MC18Version) + { + byte[] length = BitConverter.GetBytes((short)data.Length); + Array.Reverse(length); + + SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), length, data)); + } + else + { + SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), data)); + } + + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + /// + /// Send an Interact Entity Packet to server + /// + /// + /// + /// + public bool SendInteractEntity(int EntityID, int type) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetVarInt(EntityID)); + fields.AddRange(dataTypes.GetVarInt(type)); + SendPacket(PacketOutgoingType.InteractEntity, fields); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + // TODO: Interact at block location (e.g. chest minecart) + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand) + { + try + { + List fields = new List(); + fields.AddRange(dataTypes.GetVarInt(EntityID)); + fields.AddRange(dataTypes.GetVarInt(type)); + fields.AddRange(dataTypes.GetFloat(X)); + fields.AddRange(dataTypes.GetFloat(Y)); + fields.AddRange(dataTypes.GetFloat(Z)); + fields.AddRange(dataTypes.GetVarInt(hand)); + SendPacket(PacketOutgoingType.InteractEntity, fields); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z) + { + return false; + } + + public bool SendUseItem(int hand) + { + if (protocolversion < MC19Version) + return false; // Packet does not exist prior to MC 1.9 + // According to https://wiki.vg/index.php?title=Protocol&oldid=5486#Player_Block_Placement + // MC 1.7 does this using Player Block Placement with special values + // TODO once Player Block Placement is implemented for older versions + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetVarInt(hand)); + SendPacket(PacketOutgoingType.UseItem, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendPlayerDigging(int status, Location location, Direction face) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetVarInt(status)); + packet.AddRange(dataTypes.GetLocation(location)); + packet.AddRange(dataTypes.GetVarInt(dataTypes.GetBlockFace(face))); + SendPacket(PacketOutgoingType.PlayerDigging, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendPlayerBlockPlacement(int hand, Location location, Direction face) + { + if (protocolversion < MC114Version) + return false; // NOT IMPLEMENTED for older MC versions + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetVarInt(hand)); + packet.AddRange(dataTypes.GetLocation(location)); + packet.AddRange(dataTypes.GetVarInt(dataTypes.GetBlockFace(face))); + packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorX + packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorY + packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorZ + packet.Add(0); // insideBlock = false; + SendPacket(PacketOutgoingType.PlayerBlockPlacement, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendHeldItemChange(short slot) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetShort(slot)); + SendPacket(PacketOutgoingType.HeldItemChange, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item) + { + try + { + short actionNumber; + lock (window_actions) + { + if (!window_actions.ContainsKey(windowId)) + window_actions[windowId] = 0; + actionNumber = (short)(window_actions[windowId] + 1); + window_actions[windowId] = actionNumber; + } + + byte button = 0; + byte mode = 0; + switch (action) + { + case WindowActionType.LeftClick: button = 0; break; + case WindowActionType.RightClick: button = 1; break; + case WindowActionType.MiddleClick: button = 2; mode = 3; break; + case WindowActionType.DropItem: + button = 0; + mode = 4; + item = new Item(-1, 0, null); + Container inventory = handler.GetInventory(windowId); + if (inventory.Items.ContainsKey(slotId)) + inventory.Items[slotId].Count--; // server won't update us after dropped + if (inventory.Items[slotId].Count == 0) + inventory.Items.Remove(slotId); + break; + case WindowActionType.DropItemStack: + button = 1; + mode = 4; + item = new Item(-1, 0, null); + inventory = handler.GetInventory(windowId); + inventory.Items.Remove(slotId); // server won't update us after dropped + break; + } + + List packet = new List(); + packet.Add((byte)windowId); + packet.AddRange(dataTypes.GetShort((short)slotId)); + packet.Add(button); + packet.AddRange(dataTypes.GetShort(actionNumber)); + + if (protocolversion >= MC19Version) + packet.AddRange(dataTypes.GetVarInt(mode)); + else packet.Add(mode); + + packet.AddRange(dataTypes.GetItemSlot(item)); + + SendPacket(PacketOutgoingType.ClickWindow, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendCreativeInventoryAction(int slot, ItemType itemType, int count, Dictionary nbt) + { + try + { + List packet = new List(); + packet.AddRange(dataTypes.GetShort((short)slot)); + packet.AddRange(dataTypes.GetItemSlot(new Item((int)itemType, count, nbt))); + SendPacket(PacketOutgoingType.CreativeInventoryAction, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendAnimation(int animation, int playerid) + { + try + { + if (animation == 0 || animation == 1) + { + List packet = new List(); + + if (protocolversion < MC18Version) + { + packet.AddRange(dataTypes.GetInt(playerid)); + packet.Add((byte)1); // Swing arm + } + else if (protocolversion < MC19Version) + { + // No fields in 1.8.X + } + else // MC 1.9+ + { + packet.AddRange(dataTypes.GetVarInt(animation)); + } + + SendPacket(PacketOutgoingType.Animation, packet); + return true; + } + else + { + return false; + } + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendCloseWindow(int windowId) + { + try + { + lock (window_actions) + { + if (window_actions.ContainsKey(windowId)) + window_actions[windowId] = 0; + } + SendPacket(PacketOutgoingType.CloseWindow, new[] { (byte)windowId }); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public bool SendUpdateSign(Location sign, string line1, string line2, string line3, string line4) + { + try + { + if (line1.Length > 23) + line1 = line1.Substring(0, 23); + if (line2.Length > 23) + line2 = line1.Substring(0, 23); + if (line3.Length > 23) + line3 = line1.Substring(0, 23); + if (line4.Length > 23) + line4 = line1.Substring(0, 23); + + List packet = new List(); + packet.AddRange(dataTypes.GetLocation(sign)); + packet.AddRange(dataTypes.GetString(line1)); + packet.AddRange(dataTypes.GetString(line2)); + packet.AddRange(dataTypes.GetString(line3)); + packet.AddRange(dataTypes.GetString(line4)); + SendPacket(PacketOutgoingType.UpdateSign, packet); + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + } }