// WSSCompact.cpp // Interfaces to the cWSSCompact class representing the "compact" storage schema (PAK-files) #include "Globals.h" #include "WSSCompact.h" #include "../World.h" #include "zlib.h" #include #include "../StringCompression.h" #include "../ChestEntity.h" #include "../SignEntity.h" #include "../DispenserEntity.h" #include "../FurnaceEntity.h" #include "../NoteEntity.h" #include "../JukeboxEntity.h" #include "../BlockID.h" #pragma pack(push, 1) /// The chunk header, as stored in the file: struct cWSSCompact::sChunkHeader { int m_ChunkX; int m_ChunkZ; int m_CompressedSize; int m_UncompressedSize; } ; #pragma pack(pop) /// The maximum number of PAK files that are cached const int MAX_PAK_FILES = 16; /// The maximum number of unsaved chunks before the cPAKFile saves them to disk const int MAX_DIRTY_CHUNKS = 16; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cJsonChunkSerializer: cJsonChunkSerializer::cJsonChunkSerializer(void) : m_HasJsonData(false) { } void cJsonChunkSerializer::Entity(cEntity * a_Entity) { // TODO: a_Entity->SaveToJson(m_Root); } void cJsonChunkSerializer::BlockEntity(cBlockEntity * a_BlockEntity) { const char * SaveInto = NULL; switch (a_BlockEntity->GetBlockType()) { case E_BLOCK_CHEST: SaveInto = "Chests"; break; case E_BLOCK_DISPENSER: SaveInto = "Dispensers"; break; case E_BLOCK_FURNACE: SaveInto = "Furnaces"; break; case E_BLOCK_SIGN_POST: SaveInto = "Signs"; break; case E_BLOCK_WALLSIGN: SaveInto = "Signs"; break; case E_BLOCK_NOTE_BLOCK: SaveInto = "Notes"; break; case E_BLOCK_JUKEBOX: SaveInto = "Jukeboxes"; break; default: { ASSERT(!"Unhandled blocktype in BlockEntities list while saving to JSON"); break; } } // switch (BlockEntity->GetBlockType()) if (SaveInto == NULL) { return; } Json::Value val; a_BlockEntity->SaveToJson(val); m_Root[SaveInto].append(val); m_HasJsonData = true; } bool cJsonChunkSerializer::LightIsValid(bool a_IsLightValid) { if (!a_IsLightValid) { return false; } m_Root["IsLightValid"] = true; m_HasJsonData = true; return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cWSSCompact: cWSSCompact::~cWSSCompact() { for (cPAKFiles::iterator itr = m_PAKFiles.begin(); itr != m_PAKFiles.end(); ++itr) { delete *itr; } } bool cWSSCompact::LoadChunk(const cChunkCoords & a_Chunk) { AString ChunkData; int UncompressedSize = 0; if (!GetChunkData(a_Chunk, UncompressedSize, ChunkData)) { // The reason for failure is already printed in GetChunkData() return false; } return LoadChunkFromData(a_Chunk, UncompressedSize, ChunkData, m_World); } bool cWSSCompact::SaveChunk(const cChunkCoords & a_Chunk) { cCSLock Lock(m_CS); cPAKFile * f = LoadPAKFile(a_Chunk); if (f == NULL) { // For some reason we couldn't locate the file LOG("Cannot locate a proper PAK file for chunk [%d, %d]", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ); return false; } return f->SaveChunk(a_Chunk, m_World); } cWSSCompact::cPAKFile * cWSSCompact::LoadPAKFile(const cChunkCoords & a_Chunk) { // ASSUMES that m_CS has been locked // We need to retain this weird conversion code, because some edge chunks are in the wrong PAK file const int LayerX = FAST_FLOOR_DIV(a_Chunk.m_ChunkX, 32); const int LayerZ = FAST_FLOOR_DIV(a_Chunk.m_ChunkZ, 32); // Is it already cached? for (cPAKFiles::iterator itr = m_PAKFiles.begin(); itr != m_PAKFiles.end(); ++itr) { if (((*itr) != NULL) && ((*itr)->GetLayerX() == LayerX) && ((*itr)->GetLayerZ() == LayerZ)) { // Move the file to front and return it: cPAKFile * f = *itr; if (itr != m_PAKFiles.begin()) { m_PAKFiles.erase(itr); m_PAKFiles.push_front(f); } return f; } } // Load it anew: AString FileName; Printf(FileName, "%s/X%i_Z%i.pak", m_World->GetName().c_str(), LayerX, LayerZ ); cPAKFile * f = new cPAKFile(FileName, LayerX, LayerZ); if (f == NULL) { return NULL; } m_PAKFiles.push_front(f); // If there are too many PAK files cached, delete the last one used: if (m_PAKFiles.size() > MAX_PAK_FILES) { delete m_PAKFiles.back(); m_PAKFiles.pop_back(); } return f; } bool cWSSCompact::GetChunkData(const cChunkCoords & a_Chunk, int & a_UncompressedSize, AString & a_Data) { cCSLock Lock(m_CS); cPAKFile * f = LoadPAKFile(a_Chunk); if (f == NULL) { return false; } return f->GetChunkData(a_Chunk, a_UncompressedSize, a_Data); } /* // TODO: Rewrite saving to use the same principles as loading bool cWSSCompact::SetChunkData(const cChunkCoords & a_Chunk, int a_UncompressedSize, const AString & a_Data) { cCSLock Lock(m_CS); cPAKFile * f = LoadPAKFile(a_Chunk); if (f == NULL) { return false; } return f->SetChunkData(a_Chunk, a_UncompressedSize, a_Data); } */ bool cWSSCompact::EraseChunkData(const cChunkCoords & a_Chunk) { cCSLock Lock(m_CS); cPAKFile * f = LoadPAKFile(a_Chunk); if (f == NULL) { return false; } return f->EraseChunkData(a_Chunk); } void cWSSCompact::LoadEntitiesFromJson(Json::Value & a_Value, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities, cWorld * a_World) { // Load chests Json::Value AllChests = a_Value.get("Chests", Json::nullValue); if (!AllChests.empty()) { for (Json::Value::iterator itr = AllChests.begin(); itr != AllChests.end(); ++itr ) { Json::Value & Chest = *itr; cChestEntity * ChestEntity = new cChestEntity(0,0,0, a_World); if (!ChestEntity->LoadFromJson( Chest ) ) { LOGERROR("ERROR READING CHEST FROM JSON!" ); delete ChestEntity; } else { a_BlockEntities.push_back( ChestEntity ); } } // for itr - AllChests[] } // Load dispensers Json::Value AllDispensers = a_Value.get("Dispensers", Json::nullValue); if( !AllDispensers.empty() ) { for( Json::Value::iterator itr = AllDispensers.begin(); itr != AllDispensers.end(); ++itr ) { Json::Value & Dispenser = *itr; cDispenserEntity * DispenserEntity = new cDispenserEntity(0,0,0, a_World); if( !DispenserEntity->LoadFromJson( Dispenser ) ) { LOGERROR("ERROR READING DISPENSER FROM JSON!" ); delete DispenserEntity; } else { a_BlockEntities.push_back( DispenserEntity ); } } // for itr - AllDispensers[] } // Load furnaces Json::Value AllFurnaces = a_Value.get("Furnaces", Json::nullValue); if( !AllFurnaces.empty() ) { for( Json::Value::iterator itr = AllFurnaces.begin(); itr != AllFurnaces.end(); ++itr ) { Json::Value & Furnace = *itr; cFurnaceEntity * FurnaceEntity = new cFurnaceEntity(0,0,0, a_World); if( !FurnaceEntity->LoadFromJson( Furnace ) ) { LOGERROR("ERROR READING FURNACE FROM JSON!" ); delete FurnaceEntity; } else { a_BlockEntities.push_back( FurnaceEntity ); } } // for itr - AllFurnaces[] } // Load signs Json::Value AllSigns = a_Value.get("Signs", Json::nullValue); if( !AllSigns.empty() ) { for( Json::Value::iterator itr = AllSigns.begin(); itr != AllSigns.end(); ++itr ) { Json::Value & Sign = *itr; cSignEntity * SignEntity = new cSignEntity( E_BLOCK_SIGN_POST, 0,0,0, a_World); if ( !SignEntity->LoadFromJson( Sign ) ) { LOGERROR("ERROR READING SIGN FROM JSON!" ); delete SignEntity; } else { a_BlockEntities.push_back( SignEntity ); } } // for itr - AllSigns[] } // Load note blocks Json::Value AllNotes = a_Value.get("Notes", Json::nullValue); if( !AllNotes.empty() ) { for( Json::Value::iterator itr = AllNotes.begin(); itr != AllNotes.end(); ++itr ) { Json::Value & Note = *itr; cNoteEntity * NoteEntity = new cNoteEntity(0, 0, 0, a_World); if ( !NoteEntity->LoadFromJson( Note ) ) { LOGERROR("ERROR READING NOTE BLOCK FROM JSON!" ); delete NoteEntity; } else { a_BlockEntities.push_back( NoteEntity ); } } // for itr - AllNotes[] } // Load jukeboxes Json::Value AllJukeboxes = a_Value.get("Jukeboxes", Json::nullValue); if( !AllJukeboxes.empty() ) { for( Json::Value::iterator itr = AllJukeboxes.begin(); itr != AllJukeboxes.end(); ++itr ) { Json::Value & Jukebox = *itr; cJukeboxEntity * JukeboxEntity = new cJukeboxEntity(0, 0, 0, a_World); if ( !JukeboxEntity->LoadFromJson( Jukebox ) ) { LOGERROR("ERROR READING JUKEBOX FROM JSON!" ); delete JukeboxEntity; } else { a_BlockEntities.push_back( JukeboxEntity ); } } // for itr - AllJukeboxes[] } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cWSSCompact::cPAKFile #define READ(Var) \ if (f.Read(&Var, sizeof(Var)) != sizeof(Var)) \ { \ LOGERROR("ERROR READING %s FROM FILE %s (line %d); file offset %d", #Var, m_FileName.c_str(), __LINE__, f.Tell()); \ return; \ } cWSSCompact::cPAKFile::cPAKFile(const AString & a_FileName, int a_LayerX, int a_LayerZ) : m_FileName(a_FileName), m_LayerX(a_LayerX), m_LayerZ(a_LayerZ), m_NumDirty(0), m_ChunkVersion( CHUNK_VERSION ), // Init with latest version m_PakVersion( PAK_VERSION ) { cFile f; if (!f.Open(m_FileName, cFile::fmRead)) { return; } // Read headers: READ(m_PakVersion); if (m_PakVersion != 1) { LOGERROR("File \"%s\" is in an unknown pak format (%d)", m_FileName.c_str(), m_PakVersion); return; } READ(m_ChunkVersion); switch( m_ChunkVersion ) { case 1: m_ChunkSize.Set(16, 128, 16); break; case 2: case 3: m_ChunkSize.Set(16, 256, 16); break; default: LOGERROR("File \"%s\" is in an unknown chunk format (%d)", m_FileName.c_str(), m_ChunkVersion); return; }; short NumChunks = 0; READ(NumChunks); // Read chunk headers: for (int i = 0; i < NumChunks; i++) { sChunkHeader * Header = new sChunkHeader; READ(*Header); m_ChunkHeaders.push_back(Header); } // for i - chunk headers // Read chunk data: if (f.ReadRestOfFile(m_DataContents) == -1) { LOGERROR("Cannot read file \"%s\" contents", m_FileName.c_str()); return; } if( m_ChunkVersion == 1 ) // Convert chunks to version 2 { UpdateChunk1To2(); } #if AXIS_ORDER == AXIS_ORDER_XZY if( m_ChunkVersion == 2 ) // Convert chunks to version 3 { UpdateChunk2To3(); } #endif } cWSSCompact::cPAKFile::~cPAKFile() { if (m_NumDirty > 0) { SynchronizeFile(); } for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { delete *itr; } } bool cWSSCompact::cPAKFile::GetChunkData(const cChunkCoords & a_Chunk, int & a_UncompressedSize, AString & a_Data) { int ChunkX = a_Chunk.m_ChunkX; int ChunkZ = a_Chunk.m_ChunkZ; sChunkHeader * Header = NULL; int Offset = 0; for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { if (((*itr)->m_ChunkX == ChunkX) && ((*itr)->m_ChunkZ == ChunkZ)) { Header = *itr; break; } Offset += (*itr)->m_CompressedSize; } if ((Header == NULL) || (Offset + Header->m_CompressedSize > (int)m_DataContents.size())) { // Chunk not found / data invalid return false; } a_UncompressedSize = Header->m_UncompressedSize; a_Data.assign(m_DataContents, Offset, Header->m_CompressedSize); return true; } bool cWSSCompact::cPAKFile::SaveChunk(const cChunkCoords & a_Chunk, cWorld * a_World) { if (!SaveChunkToData(a_Chunk, a_World)) { return false; } if (m_NumDirty > MAX_DIRTY_CHUNKS) { SynchronizeFile(); } return true; } void cWSSCompact::cPAKFile::UpdateChunk1To2() { int Offset = 0; AString NewDataContents; int ChunksConverted = 0; for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { sChunkHeader * Header = *itr; if( ChunksConverted % 32 == 0 ) { LOGINFO("Updating \"%s\" version 1 to version 2: %d %%", m_FileName.c_str(), (ChunksConverted * 100) / m_ChunkHeaders.size() ); } ChunksConverted++; AString Data; int UncompressedSize = Header->m_UncompressedSize; Data.assign(m_DataContents, Offset, Header->m_CompressedSize); Offset += Header->m_CompressedSize; // Crude data integrity check: int ExpectedSize = (16*128*16)*2 + (16*128*16)/2; // For version 1 if (UncompressedSize < ExpectedSize) { LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d bytes out of %d needed), erasing", Header->m_ChunkX, Header->m_ChunkZ, UncompressedSize, ExpectedSize ); Offset += Header->m_CompressedSize; continue; } // Decompress the data: AString UncompressedData; { int errorcode = UncompressString(Data.data(), Data.size(), UncompressedData, UncompressedSize); if (errorcode != Z_OK) { LOGERROR("Error %d decompressing data for chunk [%d, %d]", errorcode, Header->m_ChunkX, Header->m_ChunkZ ); Offset += Header->m_CompressedSize; continue; } } if (UncompressedSize != (int)UncompressedData.size()) { LOGWARNING("Uncompressed data size differs (exp %d bytes, got %d) for chunk [%d, %d]", UncompressedSize, UncompressedData.size(), Header->m_ChunkX, Header->m_ChunkZ ); Offset += Header->m_CompressedSize; continue; } // Old version is 128 blocks high with YZX axis order char ConvertedData[cChunkDef::BlockDataSize]; int Index = 0; unsigned int InChunkOffset = 0; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) { for( int y = 0; y < 128; ++y ) { ConvertedData[Index++] = UncompressedData[y + z * 128 + x * 128 * 16 + InChunkOffset]; } // Add 128 empty blocks after an old y column memset(ConvertedData + Index, E_BLOCK_AIR, 128); Index += 128; } InChunkOffset += (16 * 128 * 16); for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) // Metadata { for( int y = 0; y < 64; ++y ) { ConvertedData[Index++] = UncompressedData[y + z * 64 + x * 64 * 16 + InChunkOffset]; } memset(ConvertedData + Index, 0, 64); Index += 64; } InChunkOffset += (16 * 128 * 16) / 2; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) // Block light { for( int y = 0; y < 64; ++y ) { ConvertedData[Index++] = UncompressedData[y + z * 64 + x * 64 * 16 + InChunkOffset]; } memset(ConvertedData + Index, 0, 64); Index += 64; } InChunkOffset += (16*128*16)/2; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) // Sky light { for( int y = 0; y < 64; ++y ) { ConvertedData[Index++] = UncompressedData[y + z * 64 + x * 64 * 16 + InChunkOffset]; } memset(ConvertedData + Index, 0, 64); Index += 64; } InChunkOffset += (16 * 128 * 16) / 2; AString Converted(ConvertedData, ARRAYCOUNT(ConvertedData)); // Add JSON data afterwards if (UncompressedData.size() > InChunkOffset) { Converted.append( UncompressedData.begin() + InChunkOffset, UncompressedData.end() ); } // Re-compress data AString CompressedData; { int errorcode = CompressString(Converted.data(), Converted.size(), CompressedData); if (errorcode != Z_OK) { LOGERROR("Error %d compressing data for chunk [%d, %d]", errorcode, Header->m_ChunkX, Header->m_ChunkZ ); continue; } } // Save into file's cache Header->m_UncompressedSize = Converted.size(); Header->m_CompressedSize = CompressedData.size(); NewDataContents.append( CompressedData ); } // Done converting m_DataContents = NewDataContents; m_ChunkVersion = 2; SynchronizeFile(); LOGINFO("Updated \"%s\" version 1 to version 2", m_FileName.c_str() ); } void cWSSCompact::cPAKFile::UpdateChunk2To3() { int Offset = 0; AString NewDataContents; int ChunksConverted = 0; for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { sChunkHeader * Header = *itr; if( ChunksConverted % 32 == 0 ) { LOGINFO("Updating \"%s\" version 2 to version 3: %d %%", m_FileName.c_str(), (ChunksConverted * 100) / m_ChunkHeaders.size() ); } ChunksConverted++; AString Data; int UncompressedSize = Header->m_UncompressedSize; Data.assign(m_DataContents, Offset, Header->m_CompressedSize); Offset += Header->m_CompressedSize; // Crude data integrity check: const int ExpectedSize = (16*256*16)*2 + (16*256*16)/2; // For version 2 if (UncompressedSize < ExpectedSize) { LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d bytes out of %d needed), erasing", Header->m_ChunkX, Header->m_ChunkZ, UncompressedSize, ExpectedSize ); Offset += Header->m_CompressedSize; continue; } // Decompress the data: AString UncompressedData; { int errorcode = UncompressString(Data.data(), Data.size(), UncompressedData, UncompressedSize); if (errorcode != Z_OK) { LOGERROR("Error %d decompressing data for chunk [%d, %d]", errorcode, Header->m_ChunkX, Header->m_ChunkZ ); Offset += Header->m_CompressedSize; continue; } } if (UncompressedSize != (int)UncompressedData.size()) { LOGWARNING("Uncompressed data size differs (exp %d bytes, got %d) for chunk [%d, %d]", UncompressedSize, UncompressedData.size(), Header->m_ChunkX, Header->m_ChunkZ ); Offset += Header->m_CompressedSize; continue; } char ConvertedData[ExpectedSize]; memset(ConvertedData, 0, ExpectedSize); // Cannot use cChunk::MakeIndex because it might change again????????? // For compatibility, use what we know is current #define MAKE_2_INDEX( x, y, z ) ( y + (z * 256) + (x * 256 * 16) ) #define MAKE_3_INDEX( x, y, z ) ( x + (z * 16) + (y * 16 * 16) ) unsigned int InChunkOffset = 0; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y ) // YZX Loop order is important, in 1.1 Y was first then Z then X { ConvertedData[ MAKE_3_INDEX(x, y, z) ] = UncompressedData[InChunkOffset]; ++InChunkOffset; } // for y, z, x unsigned int index2 = 0; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y ) { ConvertedData[ InChunkOffset + MAKE_3_INDEX(x, y, z)/2 ] |= ( (UncompressedData[ InChunkOffset + index2/2 ] >> ((index2&1)*4) ) & 0x0f ) << ((x&1)*4); ++index2; } InChunkOffset += index2 / 2; index2 = 0; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y ) { ConvertedData[ InChunkOffset + MAKE_3_INDEX(x, y, z)/2 ] |= ( (UncompressedData[ InChunkOffset + index2/2 ] >> ((index2&1)*4) ) & 0x0f ) << ((x&1)*4); ++index2; } InChunkOffset += index2 / 2; index2 = 0; for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y ) { ConvertedData[ InChunkOffset + MAKE_3_INDEX(x, y, z)/2 ] |= ( (UncompressedData[ InChunkOffset + index2/2 ] >> ((index2&1)*4) ) & 0x0f ) << ((x&1)*4); ++index2; } InChunkOffset += index2 / 2; index2 = 0; AString Converted(ConvertedData, ExpectedSize); // Add JSON data afterwards if (UncompressedData.size() > InChunkOffset) { Converted.append( UncompressedData.begin() + InChunkOffset, UncompressedData.end() ); } // Re-compress data AString CompressedData; { int errorcode = CompressString(Converted.data(), Converted.size(), CompressedData); if (errorcode != Z_OK) { LOGERROR("Error %d compressing data for chunk [%d, %d]", errorcode, Header->m_ChunkX, Header->m_ChunkZ ); continue; } } // Save into file's cache Header->m_UncompressedSize = Converted.size(); Header->m_CompressedSize = CompressedData.size(); NewDataContents.append( CompressedData ); } // Done converting m_DataContents = NewDataContents; m_ChunkVersion = 3; SynchronizeFile(); LOGINFO("Updated \"%s\" version 2 to version 3", m_FileName.c_str() ); } bool cWSSCompact::LoadChunkFromData(const cChunkCoords & a_Chunk, int & a_UncompressedSize, const AString & a_Data, cWorld * a_World) { // Crude data integrity check: if (a_UncompressedSize < cChunkDef::BlockDataSize) { LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d bytes out of %d needed), erasing", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, a_UncompressedSize, cChunkDef::BlockDataSize ); EraseChunkData(a_Chunk); return false; } // Decompress the data: AString UncompressedData; int errorcode = UncompressString(a_Data.data(), a_Data.size(), UncompressedData, a_UncompressedSize); if (errorcode != Z_OK) { LOGERROR("Error %d decompressing data for chunk [%d, %d]", errorcode, a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ ); return false; } if (a_UncompressedSize != (int)UncompressedData.size()) { LOGWARNING("Uncompressed data size differs (exp %d bytes, got %d) for chunk [%d, %d]", a_UncompressedSize, UncompressedData.size(), a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ ); return false; } cEntityList Entities; cBlockEntityList BlockEntities; bool IsLightValid = false; if (a_UncompressedSize > cChunkDef::BlockDataSize) { Json::Value root; // will contain the root value after parsing. Json::Reader reader; if ( !reader.parse( UncompressedData.data() + cChunkDef::BlockDataSize, root, false ) ) { LOGERROR("Failed to parse trailing JSON in chunk [%d, %d]!", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ ); } else { LoadEntitiesFromJson(root, Entities, BlockEntities, a_World); IsLightValid = root.get("IsLightValid", false).asBool(); } } BLOCKTYPE * BlockData = (BLOCKTYPE *)UncompressedData.data(); NIBBLETYPE * MetaData = (NIBBLETYPE *)(BlockData + cChunkDef::MetaOffset); NIBBLETYPE * BlockLight = (NIBBLETYPE *)(BlockData + cChunkDef::LightOffset); NIBBLETYPE * SkyLight = (NIBBLETYPE *)(BlockData + cChunkDef::SkyLightOffset); a_World->SetChunkData( a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ, BlockData, MetaData, IsLightValid ? BlockLight : NULL, IsLightValid ? SkyLight : NULL, NULL, NULL, Entities, BlockEntities, false ); return true; } bool cWSSCompact::cPAKFile::EraseChunkData(const cChunkCoords & a_Chunk) { int ChunkX = a_Chunk.m_ChunkX; int ChunkZ = a_Chunk.m_ChunkZ; int Offset = 0; for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { if (((*itr)->m_ChunkX == ChunkX) && ((*itr)->m_ChunkZ == ChunkZ)) { m_DataContents.erase(Offset, (*itr)->m_CompressedSize); delete *itr; itr = m_ChunkHeaders.erase(itr); return true; } Offset += (*itr)->m_CompressedSize; } return false; } bool cWSSCompact::cPAKFile::SaveChunkToData(const cChunkCoords & a_Chunk, cWorld * a_World) { // Serialize the chunk: cJsonChunkSerializer Serializer; if (!a_World->GetChunkData(a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ, Serializer)) { // Chunk not valid LOG("cWSSCompact: Trying to save chunk [%d, %d, %d] that has no data, ignoring request.", a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ); return false; } AString Data; Data.assign((const char *)Serializer.GetBlockData(), cChunkDef::BlockDataSize); if (Serializer.HasJsonData()) { AString JsonData; Json::StyledWriter writer; JsonData = writer.write(Serializer.GetRoot()); Data.append(JsonData); } // Compress the data: AString CompressedData; int errorcode = CompressString(Data.data(), Data.size(), CompressedData); if ( errorcode != Z_OK ) { LOGERROR("Error %i compressing data for chunk [%d, %d, %d]", errorcode, a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ); return false; } // Erase any existing data for the chunk: EraseChunkData(a_Chunk); // Save the header: sChunkHeader * Header = new sChunkHeader; if (Header == NULL) { LOGWARNING("Cannot create a new chunk header to save chunk [%d, %d, %d]", a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ); return false; } Header->m_CompressedSize = (int)CompressedData.size(); Header->m_ChunkX = a_Chunk.m_ChunkX; Header->m_ChunkZ = a_Chunk.m_ChunkZ; Header->m_UncompressedSize = (int)Data.size(); m_ChunkHeaders.push_back(Header); m_DataContents.append(CompressedData.data(), CompressedData.size()); m_NumDirty++; return true; } #define WRITE(Var) \ if (f.Write(&Var, sizeof(Var)) != sizeof(Var)) \ { \ LOGERROR("cWSSCompact: ERROR writing %s to file \"%s\" (line %d); file offset %d", #Var, m_FileName.c_str(), __LINE__, f.Tell()); \ return; \ } void cWSSCompact::cPAKFile::SynchronizeFile(void) { cFile f; if (!f.Open(m_FileName, cFile::fmWrite)) { LOGERROR("Cannot open PAK file \"%s\" for writing", m_FileName.c_str()); return; } WRITE(m_PakVersion); WRITE(m_ChunkVersion); short NumChunks = (short)m_ChunkHeaders.size(); WRITE(NumChunks); for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { WRITE(**itr); } if (f.Write(m_DataContents.data(), m_DataContents.size()) != (int)m_DataContents.size()) { LOGERROR("cWSSCompact: ERROR writing chunk contents to file \"%s\" (line %d); file offset %d", m_FileName.c_str(), __LINE__, f.Tell()); return; } m_NumDirty = 0; }