diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 24614a86..4c057896 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -15,7 +15,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Implementation for Minecraft 1.7.X, 1.8.X, 1.9.X, 1.10.X Protocols /// - class Protocol18Handler : IMinecraftCom { private const int MC18Version = 47; @@ -59,7 +58,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Separate thread. Network reading loop. /// - private void Updater() { try @@ -80,8 +78,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Read data from the network. Should be called on a separate thread. /// - /// - + /// FALSE if an error occured, TRUE otherwise. private bool Update() { handler.OnUpdate(); @@ -106,7 +103,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// will contain packet ID /// will contain raw packet Data - private void readNextPacket(ref int packetID, List packetData) { packetData.Clear(); @@ -133,7 +129,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Abstract incoming packet numbering /// - private enum PacketIncomingType { KeepAlive, @@ -161,7 +156,6 @@ namespace MinecraftClient.Protocol.Handlers /// Packet ID /// Protocol version /// Abstract numbering - private PacketIncomingType getPacketIncomingType(int packetID, int protocol) { if (protocol < MC19Version) @@ -218,7 +212,6 @@ namespace MinecraftClient.Protocol.Handlers /// Packet ID /// Packet contents /// TRUE if the packet was processed, FALSE if ignored or unknown - private bool handlePacket(int packetID, List packetData) { if (login_phase) @@ -280,13 +273,16 @@ namespace MinecraftClient.Protocol.Handlers double z = readNextDouble(packetData); readData(8, packetData); //Ignore look byte locMask = readNextByte(packetData); - Location location = handler.GetCurrentLocation(); - location.X = (locMask & 1 << 0) != 0 ? location.X + x : x; - location.Y = (locMask & 1 << 1) != 0 ? location.Y + y : y; - location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z; - - handler.UpdateLocation(location); + if (protocolversion >= 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); + } + else handler.UpdateLocation(new Location(x, y, z)); if (protocolversion >= MC19Version) { @@ -302,9 +298,22 @@ namespace MinecraftClient.Protocol.Handlers int chunkX = readNextInt(packetData); int chunkZ = readNextInt(packetData); bool chunksContinuous = readNextBool(packetData); - ushort chunkMask = protocolversion >= MC19Version ? (ushort)readNextVarInt(packetData) : readNextUShort(packetData); - int dataSize = readNextVarInt(packetData); - ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData); + ushort chunkMask = protocolversion >= MC19Version + ? (ushort)readNextVarInt(packetData) + : readNextUShort(packetData); + if (protocolversion < MC18Version) + { + ushort addBitmap = readNextUShort(packetData); + int compressedDataSize = readNextInt(packetData); + byte[] compressed = readData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, true, chunksContinuous, new List(decompressed)); + } + else + { + int dataSize = readNextVarInt(packetData); + ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, packetData); + } } break; case PacketIncomingType.MultiBlockChange: @@ -312,42 +321,90 @@ namespace MinecraftClient.Protocol.Handlers { int chunkX = readNextInt(packetData); int chunkZ = readNextInt(packetData); - int recordCount = readNextVarInt(packetData); + int recordCount = protocolversion < MC18Version + ? (int)readNextShort(packetData) + : readNextVarInt(packetData); + for (int i = 0; i < recordCount; i++) { - byte locationXZ = readNextByte(packetData); + byte locationXZ; + ushort blockIdMeta; + int blockY; + + if (protocolversion < MC18Version) + { + blockIdMeta = readNextUShort(packetData); + blockY = (ushort)readNextByte(packetData); + locationXZ = readNextByte(packetData); + } + else + { + locationXZ = readNextByte(packetData); + blockY = (ushort)readNextByte(packetData); + blockIdMeta = (ushort)readNextVarInt(packetData); + } + int blockX = locationXZ >> 4; int blockZ = locationXZ & 0x0F; - int blockY = (ushort)readNextByte(packetData); - Block block = new Block((ushort)readNextVarInt(packetData)); + Block block = new Block(blockIdMeta); handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); } } break; case PacketIncomingType.BlockChange: if (Settings.TerrainAndMovements) - handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); + if (protocolversion < MC18Version) + { + int blockX = readNextInt(packetData); + int blockY = readNextByte(packetData); + int blockZ = readNextInt(packetData); + short blockId = (short)readNextVarInt(packetData); + byte blockMeta = readNextByte(packetData); + handler.GetWorld().SetBlock(new Location(blockX, blockY, blockZ), new Block(blockId, blockMeta)); + } + else handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); break; case PacketIncomingType.MapChunkBulk: if (protocolversion < MC19Version && Settings.TerrainAndMovements) { - bool hasSkyLight = readNextBool(packetData); - int chunkCount = readNextVarInt(packetData); + int chunkCount; + bool hasSkyLight; + List chunkData = packetData; + + //Read global fields + if (protocolversion < MC18Version) + { + chunkCount = readNextShort(packetData); + int compressedDataSize = readNextInt(packetData); + hasSkyLight = readNextBool(packetData); + byte[] compressed = readData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + chunkData = new List(decompressed); + } + else + { + hasSkyLight = readNextBool(packetData); + chunkCount = 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] = readNextInt(packetData); chunkZs[chunkColumnNo] = readNextInt(packetData); chunkMasks[chunkColumnNo] = readNextUShort(packetData); + addBitmaps[chunkColumnNo] = protocolversion < MC18Version + ? readNextUShort(packetData) + : (ushort)0; } //Process chunk records for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) - ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData); + ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true, chunkData); } break; case PacketIncomingType.UnloadChunk: @@ -597,11 +654,11 @@ namespace MinecraftClient.Protocol.Handlers /// Chunk X location /// Chunk Z location /// Chunk mask for reading data + /// Chunk mask for some additional 1.7 metadata /// Contains skylight info /// Are the chunk continuous /// Cache for reading chunk data - - private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List cache) + private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, ushort chunkMask2, bool hasSkyLight, bool chunksContinuous, List cache) { if (protocolversion >= MC19Version) { @@ -686,13 +743,16 @@ namespace MinecraftClient.Protocol.Handlers // Don't worry about skipping remaining data since there is no useful data afterwards in 1.9 // (plus, it would require parsing the tile entity lists' NBT) } - else + else if (protocolversion >= MC18Version) { - // Pre 1.9 chunk format - if (chunksContinuous && chunkMask == 0) { + // 1.8 chunk format + if (chunksContinuous && chunkMask == 0) + { //Unload the entire chunk column handler.GetWorld()[chunkX, chunkZ] = null; - } else { + } + else + { //Load chunk data from the server for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) { @@ -733,12 +793,70 @@ namespace MinecraftClient.Protocol.Handlers readData(Chunk.SizeX * Chunk.SizeZ, cache); } } + else + { + // 1.7 chunk format + if (chunksContinuous && chunkMask == 0) + { + //Unload the entire chunk column + handler.GetWorld()[chunkX, chunkZ] = null; + } + else + { + //Count chunk sections + int sectionCount = 0; + int addDataSectionCount = 0; + for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + { + if ((chunkMask & (1 << chunkY)) != 0) + sectionCount++; + if ((chunkMask2 & (1 << chunkY)) != 0) + addDataSectionCount++; + } + + //Read chunk data, unpacking 4-bit values into 8-bit values for block metadata + Queue blockTypes = new Queue(readData(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount, cache)); + Queue blockMeta = new Queue(); + foreach (byte packed in readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, cache)) + { + byte hig = (byte)(packed >> 4); + byte low = (byte)(packed & (byte)0x0F); + blockMeta.Enqueue(hig); + blockMeta.Enqueue(low); + } + + //Skip data we don't need + readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, cache); //Block light + if (hasSkyLight) + readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, cache); //Sky light + readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * addDataSectionCount) / 2, cache); //BlockAdd + if (chunksContinuous) + readData(Chunk.SizeX * Chunk.SizeZ, cache); //Biomes + + //Load chunk data + for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + { + if ((chunkMask & (1 << chunkY)) != 0) + { + Chunk chunk = new Chunk(); + + for (int blockY = 0; blockY < Chunk.SizeY; blockY++) + for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) + for (int blockX = 0; blockX < Chunk.SizeX; blockX++) + chunk[blockX, blockY, blockZ] = new Block(blockTypes.Dequeue(), blockMeta.Dequeue()); + + if (handler.GetWorld()[chunkX, chunkZ] == null) + handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn(); + handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk; + } + } + } + } } /// /// Start the updating thread. Should be called after login success. /// - private void StartUpdating() { netRead = new Thread(new ThreadStart(Updater)); @@ -749,7 +867,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Disconnect from the server, cancel network reading. /// - public void Dispose() { try @@ -768,7 +885,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Amount of bytes to read /// The data read from the network as an array - private byte[] readDataRAW(int offset) { if (offset > 0) @@ -790,7 +906,6 @@ namespace MinecraftClient.Protocol.Handlers /// Amount of bytes to read /// Cache of bytes to read from /// The data read from the cache as an array - private static byte[] readData(int offset, List cache) { byte[] result = cache.Take(offset).ToArray(); @@ -803,7 +918,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Cache of bytes to read from /// The string - private static string readNextString(List cache) { int length = readNextVarInt(cache); @@ -818,7 +932,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read a boolean from a cache of bytes and remove it from the cache /// /// The boolean value - private static bool readNextBool(List cache) { return readNextByte(cache) != 0x00; @@ -828,7 +941,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read a short integer from a cache of bytes and remove it from the cache /// /// The short integer value - private static short readNextShort(List cache) { byte[] rawValue = readData(2, cache); @@ -840,7 +952,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read an integer from a cache of bytes and remove it from the cache /// /// The integer value - private static int readNextInt(List cache) { byte[] rawValue = readData(4, cache); @@ -852,7 +963,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read an unsigned short integer from a cache of bytes and remove it from the cache /// /// The unsigned short integer value - private static ushort readNextUShort(List cache) { byte[] rawValue = readData(2, cache); @@ -864,7 +974,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read an unsigned long integer from a cache of bytes and remove it from the cache /// /// The unsigned long integer value - private static ulong readNextULong(List cache) { byte[] rawValue = readData(8, cache); @@ -876,7 +985,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read several little endian unsigned short integers at once from a cache of bytes and remove them from the cache /// /// The unsigned short integer value - private static ushort[] readNextUShortsLittleEndian(int amount, List cache) { byte[] rawValues = readData(2 * amount, cache); @@ -891,7 +999,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Cache of bytes to read from /// The uuid - private static Guid readNextUUID(List cache) { return new Guid(readData(16, cache)); @@ -902,7 +1009,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Cache of bytes to read from /// The byte array - private byte[] readNextByteArray(List cache) { int len = protocolversion >= MC18Version @@ -915,7 +1021,6 @@ namespace MinecraftClient.Protocol.Handlers /// Reads a length-prefixed array of unsigned long integers and removes it from the cache /// /// The unsigned long integer values - private static ulong[] readNextULongArray(List cache) { int len = readNextVarInt(cache); @@ -929,7 +1034,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read a double from a cache of bytes and remove it from the cache /// /// The double value - private static double readNextDouble(List cache) { byte[] rawValue = readData(8, cache); @@ -941,7 +1045,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read an integer from the network /// /// The integer - private int readNextVarIntRAW() { int i = 0; @@ -964,7 +1067,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Cache of bytes to read from /// The integer - private static int readNextVarInt(List cache) { int i = 0; @@ -987,7 +1089,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Cache of bytes to read from /// The int - private static int readNextVarShort(List cache) { ushort low = readNextUShort(cache); @@ -1004,7 +1105,6 @@ namespace MinecraftClient.Protocol.Handlers /// Read a single byte from a cache of bytes and remove it from the cache /// /// The byte that was read - private static byte readNextByte(List cache) { byte result = cache[0]; @@ -1017,7 +1117,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Integer to encode /// Byte array for this integer - private static byte[] getVarInt(int paramInt) { List bytes = new List(); @@ -1035,7 +1134,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Array to process /// Array ready to send - private byte[] getDouble(double number) { byte[] theDouble = BitConverter.GetBytes(number); @@ -1048,7 +1146,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Array to process /// Array ready to send - private byte[] getArray(byte[] array) { if (protocolversion < MC18Version) @@ -1065,7 +1162,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// String to process /// Array ready to send - private byte[] getString(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); @@ -1078,7 +1174,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Bytes to append /// Array containing all the data - private static byte[] concatBytes(params byte[][] bytes) { List result = new List(); @@ -1092,7 +1187,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// String to parse /// Int parsed - private static int atoi(string str) { return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray())); @@ -1101,7 +1195,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Network reading method. Read bytes from the socket or encrypted socket. /// - private void Receive(byte[] buffer, int start, int offset, SocketFlags f) { int read = 0; @@ -1120,7 +1213,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Discriminator to use. /// packet Data - private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, byte[] data) { SendPluginChannelPacket("FML|HS", concatBytes(new byte[] { (byte)discriminator }, data)); @@ -1131,7 +1223,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// packet ID /// packet Data - private void SendPacket(int packetID, IEnumerable packetData) { //The inner packet @@ -1160,7 +1251,6 @@ namespace MinecraftClient.Protocol.Handlers /// Send raw data to the server. Encryption will be handled automatically. /// /// data to send - private void SendRAW(byte[] buffer) { if (encrypted) @@ -1174,7 +1264,6 @@ namespace MinecraftClient.Protocol.Handlers /// Do the Minecraft login. /// /// True if login successful - public bool Login() { byte[] protocol_version = getVarInt(protocolversion); @@ -1261,7 +1350,6 @@ namespace MinecraftClient.Protocol.Handlers /// 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); @@ -1327,7 +1415,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Message /// True if properly sent - public bool SendChatMessage(string message) { if (String.IsNullOrEmpty(message)) @@ -1349,7 +1436,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Message /// True if properly sent - public bool SendRespawnPacket() { try @@ -1365,7 +1451,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Client string describing the client /// True if brand info was successfully sent - public bool SendBrandInfo(string brandInfo) { if (String.IsNullOrEmpty(brandInfo)) @@ -1380,7 +1465,6 @@ namespace MinecraftClient.Protocol.Handlers /// The new location of the player /// True if the player is on the ground /// True if the location update was successfully sent - public bool SendLocationUpdate(Location location, bool onGround) { if (Settings.TerrainAndMovements) @@ -1388,7 +1472,12 @@ namespace MinecraftClient.Protocol.Handlers try { SendPacket(protocolversion >= MC19Version ? 0x0C : 0x04, concatBytes( - getDouble(location.X), getDouble(location.Y), getDouble(location.Z), + getDouble(location.X), + getDouble(location.Y), + protocolversion < MC18Version + ? getDouble(location.Y + 1.62) + : new byte[0], + getDouble(location.Z), new byte[] { onGround ? (byte)1 : (byte)0 })); return true; } @@ -1402,7 +1491,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Channel to send packet on /// packet Data - public bool SendPluginChannelPacket(string channel, byte[] data) { try @@ -1430,7 +1518,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Disconnect from the server /// - public void Disconnect() { try @@ -1448,7 +1535,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Text behind cursor /// Completed text - IEnumerable IAutoComplete.AutoComplete(string BehindCursor) { if (String.IsNullOrEmpty(BehindCursor)) @@ -1480,7 +1566,6 @@ namespace MinecraftClient.Protocol.Handlers /// 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 = ""; diff --git a/MinecraftClient/Protocol/Handlers/ZlibUtils.cs b/MinecraftClient/Protocol/Handlers/ZlibUtils.cs index 9232e57b..8265779d 100644 --- a/MinecraftClient/Protocol/Handlers/ZlibUtils.cs +++ b/MinecraftClient/Protocol/Handlers/ZlibUtils.cs @@ -12,7 +12,6 @@ namespace MinecraftClient.Protocol.Handlers /// This library is open source and provided under the Microsoft Public License. /// More info about DotNetZip at dotnetzip.codeplex.com. /// - public static class ZlibUtils { /// @@ -20,7 +19,6 @@ namespace MinecraftClient.Protocol.Handlers /// /// Data to compress /// Compressed data as a byte array - public static byte[] Compress(byte[] to_compress) { ZlibStream stream = new ZlibStream(new System.IO.MemoryStream(to_compress, false), CompressionMode.Compress); @@ -42,7 +40,6 @@ namespace MinecraftClient.Protocol.Handlers /// Data to decompress /// Size of the data once decompressed /// Decompressed data as a byte array - public static byte[] Decompress(byte[] to_decompress, int size_uncompressed) { ZlibStream stream = new ZlibStream(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress); @@ -51,5 +48,23 @@ namespace MinecraftClient.Protocol.Handlers stream.Close(); return packetData_decompressed; } + + /// + /// Decompress a byte array into another byte array of a potentially unlimited size (!) + /// + /// Data to decompress + /// Decompressed data as byte array + public static byte[] Decompress(byte[] to_decompress) + { + ZlibStream stream = new ZlibStream(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress); + byte[] buffer = new byte[16 * 1024]; + using (System.IO.MemoryStream decompressedBuffer = new System.IO.MemoryStream()) + { + int read; + while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) + decompressedBuffer.Write(buffer, 0, read); + return decompressedBuffer.ToArray(); + } + } } }