diff --git a/source/client/common/Utils.cpp b/source/client/common/Utils.cpp index c6e8968..9935a59 100644 --- a/source/client/common/Utils.cpp +++ b/source/client/common/Utils.cpp @@ -373,6 +373,11 @@ uint8_t* ZlibInflateToMemory(uint8_t* pInput, size_t compressedSize, size_t deco } uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t* compressedSizeOut) +{ + return ZlibDeflateToMemoryLvl(pInput, sizeBytes, compressedSizeOut, Z_DEFAULT_COMPRESSION); +} + +uint8_t* ZlibDeflateToMemoryLvl(uint8_t* pInput, size_t sizeBytes, size_t* compressedSizeOut, int level) { z_stream strm; memset(&strm, 0, sizeof strm); @@ -384,7 +389,7 @@ uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t* compress strm.opaque = Z_NULL; // initialize deflation state machine - ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION); + ret = deflateInit(&strm, level); if (ret != Z_OK) return nullptr; @@ -394,7 +399,7 @@ uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t* compress uint8_t* pOut = new uint8_t[sizeBytes + ZLIB_PADDING_BYTES]; strm.avail_in = sizeBytes; strm.next_in = pInput; - strm.avail_in = sizeBytes + ZLIB_PADDING_BYTES; + strm.avail_out = sizeBytes + ZLIB_PADDING_BYTES; strm.next_out = pOut; ret = deflate(&strm, Z_FINISH); diff --git a/source/client/common/Utils.hpp b/source/client/common/Utils.hpp index 00dfa3d..af3a20d 100644 --- a/source/client/common/Utils.hpp +++ b/source/client/common/Utils.hpp @@ -567,6 +567,7 @@ bool DeleteDirectory(const std::string& name, bool unused); // compress and decompress stuff with zlib: ( you must SAFE_DELETE_ARRAY what it returns ) uint8_t* ZlibInflateToMemory(uint8_t* pInput, size_t compressedSize, size_t decompressedSize); uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t *compressedSizeOut); +uint8_t* ZlibDeflateToMemoryLvl(uint8_t* pInput, size_t sizeBytes, size_t* compressedSizeOut, int level); // things that we added: #ifndef ORIGINAL_CODE diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index c7c81e0..a4fb926 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -26,6 +26,8 @@ ClientSideNetworkHandler::ClientSideNetworkHandler(Minecraft* pMinecraft, RakNet m_pMinecraft = pMinecraft; m_pRakNetInstance = pRakNetInstance; m_pServerPeer = m_pRakNetInstance->getPeer(); + m_chunksRequested = 0; + m_serverProtocolVersion = 0; } void ClientSideNetworkHandler::levelGenerated(Level* level) @@ -108,6 +110,8 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, StartGa m_pLevel->setTime(pStartGamePkt->m_time); + m_serverProtocolVersion = pStartGamePkt->m_version; + m_pMinecraft->setLevel(m_pLevel, "ClientSideNetworkHandler -> setLevel", pLocalPlayer); } @@ -326,10 +330,13 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, ChunkDa pChunk->m_bUnsaved = true; - if (areAllChunksLoaded()) - flushAllBufferedUpdates(); - else - requestNextChunk(); + if (m_serverProtocolVersion < 2) + { + if (areAllChunksLoaded()) + flushAllBufferedUpdates(); + else + requestNextChunk(); + } } void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlayerEquipmentPacket* pPlayerEquipmentPkt) @@ -355,6 +362,99 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlayerE pPlayer->m_pInventory->selectItemById(pPlayerEquipmentPkt->m_itemID); } +void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet) +{ + int uncompMagic = 12847812, compMagic = 58712758, chunkSepMagic = 284787658; + RakNet::BitStream* bs = &packet->m_data, bs2; + + int thing = 0; + bs->Read(thing); + if (thing != compMagic && thing != uncompMagic) + { + LogMsg("Error, invalid level data packet with magic %d", thing); + return; + } + + if (thing == compMagic) + { + int uncompSize = 0, compSize = 0; + bs->Read(uncompSize); + bs->Read(compSize); + + LogMsg("Decompressing level data. Compressed, uncompressed sizes: %d bytes, %d bytes", compSize, uncompSize); + + uint8_t* pCompData = new uint8_t[compSize]; + bs->Read((char*)pCompData, compSize); + + uint8_t* pUncompData = ZlibInflateToMemory(pCompData, compSize, uncompSize); + delete[] pCompData; + + if (!pUncompData) + { + LogMsg("Error, can't decompress level data"); + return; + } + + bs2.Reset(); + bs2.Write((const char*)pUncompData, uncompSize); + bs2.ResetReadPointer(); + bs = &bs2; + + bs->Read(thing); + } + + int chunksX = 0, chunksZ = 0; + bs->Read(chunksX); + bs->Read(chunksZ); + + if (chunksX != C_MAX_CHUNKS_X || chunksZ != C_MAX_CHUNKS_Z) + { + LogMsg("Error, we don't yet support a level of size %d x %d chunks. Some chunks may disappear or be regenerated.", chunksX, chunksZ); + } + + for (int x = 0; x < chunksX; x++) + { + for (int z = 0; z < chunksZ; z++) + { + uint8_t ptype = 0; + bs->Read(thing); + + int dataSize = 0; + bs->Read(dataSize); + + if (thing != chunkSepMagic) + { + _FAIL_BECAUSE_INVALID: + LogMsg("Error, aborting because level data is invalid. Reading chunk %d,%d. Thing=%d PType=%d",x,z,thing,ptype); + return; + } + + LevelChunk* pChunk = m_pLevel->getChunk(x, z); + if (!pChunk) + LogMsg("No chunk at %d,%d", x, z); + + RakNet::BitStream bs2; + bs2.Write(*bs, 8 * dataSize); + + bs2.Read(ptype); + if (ptype != PACKET_CHUNK_DATA) + goto _FAIL_BECAUSE_INVALID; + + ChunkDataPacket cdp(x, z, pChunk); + cdp.read(&bs2); + LogMsg("Chunk %d,%d", cdp.m_x, cdp.m_z); + + if (pChunk) + handle(guid, &cdp); + + while (m_pLevel->updateLights()); + } + } + + m_chunksRequested = 256;// all chunks are loaded!! + flushAllBufferedUpdates(); +} + bool ClientSideNetworkHandler::areAllChunksLoaded() { return m_chunksRequested > 255; @@ -368,8 +468,15 @@ void ClientSideNetworkHandler::requestNextChunk() // @BUG: The return value of areAllChunksLoaded() is actually true even before the // 256th chunk is loaded. - m_pRakNetInstance->send(new RequestChunkPacket(m_chunksRequested % 16, m_chunksRequested / 16)); - m_chunksRequested++; + if (m_serverProtocolVersion < 2) + { + m_pRakNetInstance->send(new RequestChunkPacket(m_chunksRequested % 16, m_chunksRequested / 16)); + m_chunksRequested++; + } + else + { + m_pRakNetInstance->send(new RequestChunkPacket(-9999, -9999)); + } } void ClientSideNetworkHandler::flushAllBufferedUpdates() diff --git a/source/client/network/ClientSideNetworkHandler.hpp b/source/client/network/ClientSideNetworkHandler.hpp index db05d7a..7dc3df3 100644 --- a/source/client/network/ClientSideNetworkHandler.hpp +++ b/source/client/network/ClientSideNetworkHandler.hpp @@ -42,6 +42,7 @@ public: void handle(const RakNet::RakNetGUID&, UpdateBlockPacket*) override; void handle(const RakNet::RakNetGUID&, ChunkDataPacket*) override; void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*) override; + void handle(const RakNet::RakNetGUID&, LevelDataPacket*) override; bool areAllChunksLoaded(); void requestNextChunk(); @@ -56,6 +57,7 @@ private: RakNet::RakNetGUID m_serverGUID; int m_field_24; std::vector m_bufferedBlockUpdates; - int m_chunksRequested = 0; + int m_chunksRequested; + int m_serverProtocolVersion; }; diff --git a/source/client/network/MinecraftPackets.cpp b/source/client/network/MinecraftPackets.cpp index dc0e96e..d521498 100644 --- a/source/client/network/MinecraftPackets.cpp +++ b/source/client/network/MinecraftPackets.cpp @@ -36,6 +36,9 @@ Packet* MinecraftPackets::createPacket(int type) return new ChunkDataPacket; case PACKET_PLAYER_EQUIPMENT: return new PlayerEquipmentPacket; + + case PACKET_LEVEL_DATA: + return new LevelDataPacket; } return nullptr; diff --git a/source/client/network/NetEventCallback.cpp b/source/client/network/NetEventCallback.cpp index 871234b..8637d3c 100644 --- a/source/client/network/NetEventCallback.cpp +++ b/source/client/network/NetEventCallback.cpp @@ -76,3 +76,7 @@ void NetEventCallback::handle(const RakNet::RakNetGUID& guid, ChunkDataPacket* p void NetEventCallback::handle(const RakNet::RakNetGUID& guid, PlayerEquipmentPacket* packet) { } + +void NetEventCallback::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet) +{ +} diff --git a/source/client/network/NetEventCallback.hpp b/source/client/network/NetEventCallback.hpp index 814a22d..538b65c 100644 --- a/source/client/network/NetEventCallback.hpp +++ b/source/client/network/NetEventCallback.hpp @@ -23,8 +23,10 @@ class UpdateBlockPacket; class RequestChunkPacket; class ChunkDataPacket; class PlayerEquipmentPacket; +class LevelDataPacket; #include "world/level/Level.hpp" class Level; +class LevelChunk; class NetEventCallback { @@ -47,5 +49,6 @@ public: virtual void handle(const RakNet::RakNetGUID&, RequestChunkPacket*); virtual void handle(const RakNet::RakNetGUID&, ChunkDataPacket*); virtual void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*); + virtual void handle(const RakNet::RakNetGUID&, LevelDataPacket*); }; diff --git a/source/client/network/Packet.hpp b/source/client/network/Packet.hpp index 639aa98..4be350e 100644 --- a/source/client/network/Packet.hpp +++ b/source/client/network/Packet.hpp @@ -16,6 +16,7 @@ #include "NetEventCallback.hpp" class NetEventCallback; +class Level; class LevelChunk; enum ePacketType : uint8_t @@ -32,6 +33,8 @@ enum ePacketType : uint8_t PACKET_REQUEST_CHUNK, PACKET_CHUNK_DATA, PACKET_PLAYER_EQUIPMENT, + + PACKET_LEVEL_DATA = 200, }; class Packet @@ -227,6 +230,19 @@ public: LevelChunk* m_pChunk; }; +class LevelDataPacket : public Packet +{ +public: + LevelDataPacket() {} + LevelDataPacket(Level* level) : m_pLevel(level) {} + void handle(const RakNet::RakNetGUID&, NetEventCallback* pCallback) override; + void write(RakNet::BitStream*); + void read(RakNet::BitStream*); +public: + RakNet::BitStream m_data; + Level* m_pLevel; +}; + class PlayerEquipmentPacket : public Packet { public: diff --git a/source/client/network/ServerSideNetworkHandler.cpp b/source/client/network/ServerSideNetworkHandler.cpp index e6df1b8..74c717a 100644 --- a/source/client/network/ServerSideNetworkHandler.cpp +++ b/source/client/network/ServerSideNetworkHandler.cpp @@ -113,7 +113,7 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LoginPacke sgp.field_10 = pPlayer->m_pos.x; sgp.field_14 = pPlayer->m_pos.y - pPlayer->field_84; sgp.field_18 = pPlayer->m_pos.z; - sgp.m_version = 1; + sgp.m_version = 2; sgp.m_time = m_pLevel->getTime(); RakNet::BitStream sgpbs; @@ -292,6 +292,12 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RequestChu { puts_ignorable("RequestChunkPacket"); + if (packet->m_x == -9999) + { + m_pRakNetInstance->send(guid, new LevelDataPacket(m_pLevel)); + return; + } + LevelChunk* pChunk = m_pLevel->getChunk(packet->m_x, packet->m_z); if (!pChunk) { diff --git a/source/client/network/packets/ChunkDataPacket.cpp b/source/client/network/packets/ChunkDataPacket.cpp index 7a3f64a..850ceb0 100644 --- a/source/client/network/packets/ChunkDataPacket.cpp +++ b/source/client/network/packets/ChunkDataPacket.cpp @@ -44,6 +44,7 @@ void ChunkDataPacket::write(RakNet::BitStream* bs) } } + m_data.ResetReadPointer(); bs->Write(m_data); } @@ -52,4 +53,5 @@ void ChunkDataPacket::read(RakNet::BitStream* bs) bs->Read(m_x); bs->Read(m_z); bs->Read(m_data); + m_data.ResetReadPointer(); } diff --git a/source/client/network/packets/LevelDataPacket.cpp b/source/client/network/packets/LevelDataPacket.cpp new file mode 100644 index 0000000..6a4fb7a --- /dev/null +++ b/source/client/network/packets/LevelDataPacket.cpp @@ -0,0 +1,86 @@ +/******************************************************************** + Minecraft: Pocket Edition - Decompilation Project + Copyright (C) 2023 iProgramInCpp + + The following code is licensed under the BSD 1 clause license. + SPDX-License-Identifier: BSD-1-Clause + ********************************************************************/ + +#include "../Packet.hpp" +#include "world/level/levelgen/chunk/LevelChunk.hpp" + +void LevelDataPacket::handle(const RakNet::RakNetGUID& guid, NetEventCallback* pCallback) +{ + pCallback->handle(guid, this); +} + +void LevelDataPacket::write(RakNet::BitStream* pbs) +{ + // @TODO: Maybe offload this to a different 'worker thread'? Or maybe just the compression job? + + // send a crapton of them in a raw packet(why? Laziness) + int chunksX = C_MAX_CHUNKS_X; + int chunksZ = C_MAX_CHUNKS_Z; + int minus9999 = -9999; + RakNet::BitStream bs; + pbs->Write(PACKET_LEVEL_DATA); + + int uncompMagic = 12847812, compMagic = 58712758, chunkSepMagic = 284787658; + bs.Write(uncompMagic); + bs.Write(chunksX); + bs.Write(chunksZ); + for (int x = 0; x < chunksX; x++) + { + for (int z = 0; z < chunksZ; z++) + { + bs.Write(chunkSepMagic); + + RakNet::BitStream bs2; + LevelChunk* pChunk = m_pLevel->getChunk(x, z); + ChunkDataPacket cdp(x, z, pChunk); + cdp.write(&bs2); + + int dataSize = int(bs2.GetNumberOfBytesUsed()); + bs.Write(dataSize); + bs.Write((const char*)bs2.GetData(), dataSize); + } + } + + // compress it + size_t compSize = 0; + size_t uncompSize = bs.GetNumberOfBytesUsed(); + uint8_t* pCompressedData = nullptr; + + if (uncompSize > 1024) + { + // takes about 5ms on release to compress everything on L4, compression ratio is around 14-15%. + // Increase or decrease this depending on your server's load. + pCompressedData = ZlibDeflateToMemoryLvl(bs.GetData(), uncompSize, &compSize, 4); + } + + if (pCompressedData) + { + float ratio = 100.0f * float(compSize) / float(uncompSize); + LogMsg("Compression ratio: %.2f (%d comp, %d uncomp)", ratio, int(compSize), int(uncompSize)); + + int cs2 = int(compSize), us2 = int(uncompSize); + bs.Reset(); + bs.Write(compMagic); + bs.Write(us2); + bs.Write(cs2); + bs.Write((const char*)pCompressedData, compSize); + SAFE_DELETE_ARRAY(pCompressedData); + } + else + { + LogMsg("Level not compressed."); + } + + pbs->Write(bs); + return; +} + +void LevelDataPacket::read(RakNet::BitStream* bs) +{ + bs->Read(m_data); +} diff --git a/windows_vs/minecraftcpp.vcxproj b/windows_vs/minecraftcpp.vcxproj index 7d58eeb..a953370 100644 --- a/windows_vs/minecraftcpp.vcxproj +++ b/windows_vs/minecraftcpp.vcxproj @@ -433,6 +433,7 @@ +