summaryrefslogtreecommitdiffstats
path: root/source/WSSAnvil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/WSSAnvil.cpp')
-rw-r--r--source/WSSAnvil.cpp338
1 files changed, 338 insertions, 0 deletions
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<cNBTTree> 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);
+}
+
+
+
+