From 69a4ee53830121714927c37e4f0b9a357cd03957 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Wed, 7 Mar 2012 11:28:24 +0000 Subject: Added the Anvil level format (MCA) support, read-only for the time being. Now MCS can read your worlds from the official server :) git-svn-id: http://mc-server.googlecode.com/svn/trunk@380 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/WSSAnvil.cpp | 338 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 source/WSSAnvil.cpp (limited to 'source/WSSAnvil.cpp') diff --git a/source/WSSAnvil.cpp b/source/WSSAnvil.cpp new file mode 100644 index 000000000..ebec8bdc6 --- /dev/null +++ b/source/WSSAnvil.cpp @@ -0,0 +1,338 @@ + +// WSSAnvil.cpp + +// Implements the cWSSAnvil class representing the Anvil world storage scheme + +#include "Globals.h" +#include "WSSAnvil.h" +#include "cWorld.h" +#include "zlib.h" +#include "NBT.h" +#include "BlockID.h" + + + + + +/** Maximum number of MCA files that are cached in memory. +Since only the header is actually in the memory, this number can be high, but still, each file means an OS FS handle. +*/ +#define MAX_MCA_FILES 32 + +/// The maximum size of an inflated chunk +#define CHUNK_INFLATE_MAX 128 KiB + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWSSAnvil: + +cWSSAnvil::~cWSSAnvil() +{ + cCSLock Lock(m_CS); + for (cMCAFiles::iterator itr = m_Files.begin(); itr != m_Files.end(); ++itr) + { + delete *itr; + } // for itr - m_Files[] +} + + + + + +bool cWSSAnvil::LoadChunk(const cChunkCoords & a_Chunk) +{ + AString ChunkData; + if (!GetChunkData(a_Chunk, ChunkData)) + { + // The reason for failure is already printed in GetChunkData() + return false; + } + + return LoadChunkFromData(a_Chunk, ChunkData); +} + + + + + +bool cWSSAnvil::SaveChunk(const cChunkCoords & a_Chunk) +{ + // TODO: We're read-only for now + return false; +} + + + + + +bool cWSSAnvil::GetChunkData(const cChunkCoords & a_Chunk, AString & a_Data) +{ + cCSLock Lock(m_CS); + cMCAFile * File = LoadMCAFile(a_Chunk); + if (File == NULL) + { + return false; + } + return File->GetChunkData(a_Chunk, a_Data); +} + + + + + +cWSSAnvil::cMCAFile * cWSSAnvil::LoadMCAFile(const cChunkCoords & a_Chunk) +{ + // ASSUME m_CS is locked + + const int RegionX = (int)(floorf((float)a_Chunk.m_ChunkX / 32.0f)); + const int RegionZ = (int)(floorf((float)a_Chunk.m_ChunkZ / 32.0f)); + + // Is it already cached? + for (cMCAFiles::iterator itr = m_Files.begin(); itr != m_Files.end(); ++itr) + { + if (((*itr) != NULL) && ((*itr)->GetRegionX() == RegionX) && ((*itr)->GetRegionZ() == RegionZ)) + { + // Move the file to front and return it: + cMCAFile * f = *itr; + if (itr != m_Files.begin()) + { + m_Files.erase(itr); + m_Files.push_front(f); + } + return f; + } + } + + // Load it anew: + AString FileName; + Printf(FileName, "%s/r.%d.%d.mca", m_World->GetName().c_str(), RegionX, RegionZ); + cMCAFile * f = new cMCAFile(FileName, RegionX, RegionZ); + if (f == NULL) + { + return NULL; + } + m_Files.push_front(f); + + // If there are too many MCA files cached, delete the last one used: + if (m_Files.size() > MAX_MCA_FILES) + { + delete m_Files.back(); + m_Files.pop_back(); + } + return f; +} + + + + + +bool cWSSAnvil::LoadChunkFromData(const cChunkCoords & a_Chunk, const AString & a_Data) +{ + // Decompress the data: + char Uncompressed[CHUNK_INFLATE_MAX]; + z_stream strm; + strm.zalloc = (alloc_func)NULL; + strm.zfree = (free_func)NULL; + strm.opaque = NULL; + inflateInit(&strm); + strm.next_out = (Bytef *)Uncompressed; + strm.avail_out = sizeof(Uncompressed); + strm.next_in = (Bytef *)a_Data.data(); + strm.avail_in = a_Data.size(); + inflateReset(&strm); + int res = inflate(&strm, Z_FINISH); + inflateEnd(&strm); + if (res != Z_STREAM_END) + { + return false; + } + + // Parse the NBT data: + std::auto_ptr Tree(cNBTParser::Parse(Uncompressed, strm.total_out)); + if (Tree.get() == NULL) + { + return false; + } + + // Load the data from NBT: + return LoadChunkFromNBT(a_Chunk, *Tree.get()); +} + + + + + +bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, cNBTTag & a_NBT) +{ + // The data arrays, in MCA-native y/z/x ordering (will be reordered for the final chunk data) + char BlockData[cChunk::c_NumBlocks]; + char MetaData[cChunk::c_NumBlocks / 2]; + char BlockLight[cChunk::c_NumBlocks / 2]; + char SkyLight[cChunk::c_NumBlocks / 2]; + + memset(BlockData, E_BLOCK_AIR, sizeof(BlockData)); + memset(MetaData, 0, sizeof(MetaData)); + memset(BlockLight, 0, sizeof(BlockLight)); + memset(SkyLight, 0xff, sizeof(SkyLight)); // By default, data not present in the NBT means air, which means full skylight + + // Load the blockdata, blocklight and skylight: + cNBTList * Sections = (cNBTList *)a_NBT.FindChildByPath("Level\\Sections"); + if ((Sections == NULL) || (Sections->GetType() != cNBTTag::TAG_List) || (Sections->GetChildrenType() != cNBTTag::TAG_Compound)) + { + return false; + } + const cNBTTags & LevelSections = Sections->GetChildren(); + for (cNBTTags::const_iterator itr = LevelSections.begin(); itr != LevelSections.end(); ++itr) + { + int y = 0; + cNBTByte * SectionY = (cNBTByte *)((*itr)->FindChildByName("Y")); + if ((SectionY == NULL) || (SectionY->GetType() != cNBTTag::TAG_Byte) || (SectionY->m_Value < 0) || (SectionY->m_Value > 15)) + { + continue; + } + y = SectionY->m_Value; + cNBTByteArray * baBlocks = (cNBTByteArray *)((*itr)->FindChildByName("Blocks")); + if ((baBlocks != NULL) && (baBlocks->GetType() == cNBTTag::TAG_ByteArray) && (baBlocks->m_Value.size() == 4096)) + { + memcpy(&(BlockData[y * 4096]), baBlocks->m_Value.data(), 4096); + } + cNBTByteArray * baMetaData = (cNBTByteArray *)((*itr)->FindChildByName("Data")); + if ((baMetaData != NULL) && (baMetaData->GetType() == cNBTTag::TAG_ByteArray) && (baMetaData->m_Value.size() == 2048)) + { + memcpy(&(MetaData[y * 2048]), baMetaData->m_Value.data(), 2048); + } + cNBTByteArray * baSkyLight = (cNBTByteArray *)((*itr)->FindChildByName("SkyLight")); + if ((baSkyLight != NULL) && (baSkyLight->GetType() == cNBTTag::TAG_ByteArray) && (baSkyLight->m_Value.size() == 2048)) + { + memcpy(&(SkyLight[y * 2048]), baSkyLight->m_Value.data(), 2048); + } + cNBTByteArray * baBlockLight = (cNBTByteArray *)((*itr)->FindChildByName("BlockLight")); + if ((baBlockLight != NULL) && (baBlockLight->GetType() == cNBTTag::TAG_ByteArray) && (baBlockLight->m_Value.size() == 2048)) + { + memcpy(&(BlockLight[y * 2048]), baBlockLight->m_Value.data(), 2048); + } + } // for itr - LevelSections[] + + cEntityList Entities; + cBlockEntityList BlockEntities; + + // TODO: Load the entities from NBT + + // Reorder the chunk data - walk the MCA-formatted data sequentially and copy it into the right place in the ChunkData: + char ChunkData[cChunk::c_BlockDataSize]; + memset(ChunkData, 0, sizeof(ChunkData)); + int Index = 0; // Index into the MCA-formatted data, incremented sequentially + for (int y = 0; y < cChunk::c_ChunkHeight; y++) for (int z = 0; z < cChunk::c_ChunkWidth; z++) for (int x = 0; x < cChunk::c_ChunkWidth; x++) + { + ChunkData[cChunk::MakeIndex(x, y, z)] = BlockData[Index]; + Index++; + } // for y/z/x + char * ChunkMeta = ChunkData + cChunk::c_NumBlocks; + Index = 0; + for (int y = 0; y < cChunk::c_ChunkHeight; y++) for (int z = 0; z < cChunk::c_ChunkWidth; z++) for (int x = 0; x < cChunk::c_ChunkWidth; x++) + { + cChunk::SetNibble(ChunkMeta, x, y, z, MetaData[Index / 2] >> ((Index % 2) * 4)); + Index++; + } // for y/z/x + char * ChunkBlockLight = ChunkMeta + cChunk::c_NumBlocks / 2; + Index = 0; + for (int y = 0; y < cChunk::c_ChunkHeight; y++) for (int z = 0; z < cChunk::c_ChunkWidth; z++) for (int x = 0; x < cChunk::c_ChunkWidth; x++) + { + cChunk::SetNibble(ChunkBlockLight, x, y, z, BlockLight[Index / 2] >> ((Index % 2) * 4)); + Index++; + } // for y/z/x + char * ChunkSkyLight = ChunkBlockLight + cChunk::c_NumBlocks / 2; + Index = 0; + for (int y = 0; y < cChunk::c_ChunkHeight; y++) for (int z = 0; z < cChunk::c_ChunkWidth; z++) for (int x = 0; x < cChunk::c_ChunkWidth; x++) + { + cChunk::SetNibble(ChunkSkyLight, x, y, z, SkyLight[Index / 2] >> ((Index % 2) * 4)); + Index++; + } // for y/z/x + + m_World->ChunkDataLoaded(a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ, ChunkData, Entities, BlockEntities); + return true; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWSSAnvil::cMCAFile: + +cWSSAnvil::cMCAFile::cMCAFile(const AString & a_FileName, int a_RegionX, int a_RegionZ) : + m_RegionX(a_RegionX), + m_RegionZ(a_RegionZ), + m_File(a_FileName, cFile::fmRead), + m_FileName(a_FileName) +{ + if (!m_File.IsOpen()) + { + return; + } + + // Load the header: + if (m_File.Read(m_Header, sizeof(m_Header)) != sizeof(m_Header)) + { + LOGWARNING("Cannot read MCA header from file \"%s\", chunks in that file will be lost", m_FileName.c_str()); + m_File.Close(); + return; + } +} + + + + + +bool cWSSAnvil::cMCAFile::GetChunkData(const cChunkCoords & a_Chunk, AString & a_Data) +{ + if (!m_File.IsOpen()) + { + return false; + } + int LocalX = a_Chunk.m_ChunkX % 32; + if (LocalX < 0) + { + LocalX = 32 + LocalX; + } + int LocalZ = a_Chunk.m_ChunkZ % 32; + if (LocalZ < 0) + { + LocalZ = 32 + LocalZ; + } + unsigned ChunkLocation = ntohl(m_Header[LocalX + 32 * LocalZ]); + unsigned ChunkOffset = ChunkLocation >> 8; + unsigned ChunkLen = ChunkLocation & 0xff; + + m_File.Seek(ChunkOffset * 4096); + + int ChunkSize = 0; + if (m_File.Read(&ChunkSize, 4) != 4) + { + return false; + } + ChunkSize = ntohl(ChunkSize); + char CompressionType = 0; + if (m_File.Read(&CompressionType, 1) != 1) + { + return false; + } + if (CompressionType != 2) + { + // Chunk is in an unknown compression + return false; + } + ChunkSize--; + + // HACK: This depends on the internal knowledge that AString's data() function returns the internal buffer directly + a_Data.assign(ChunkSize, '\0'); + return (m_File.Read((void *)a_Data.data(), ChunkSize) == ChunkSize); +} + + + + -- cgit v1.2.3