1
0

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
This commit is contained in:
madmaxoft@gmail.com 2012-03-07 11:28:24 +00:00
parent bae54ee7bd
commit 69a4ee5383
9 changed files with 1282 additions and 2 deletions

View File

@ -565,6 +565,14 @@
RelativePath="..\source\MersenneTwister.h"
>
</File>
<File
RelativePath="..\source\NBT.cpp"
>
</File>
<File
RelativePath="..\source\NBT.h"
>
</File>
<File
RelativePath="..\source\StackWalker.cpp"
>
@ -1646,6 +1654,14 @@
RelativePath="..\source\WorldStorage.h"
>
</File>
<File
RelativePath="..\source\WSSAnvil.cpp"
>
</File>
<File
RelativePath="..\source\WSSAnvil.h"
>
</File>
<File
RelativePath="..\source\WSSCompact.cpp"
>

View File

@ -421,6 +421,7 @@
<ClCompile Include="..\source\cLog.cpp" />
<ClCompile Include="..\source\Bindings.cpp" />
<ClCompile Include="..\source\main.cpp" />
<ClCompile Include="..\source\NBT.cpp" />
<ClCompile Include="..\source\packets\cPacket.cpp" />
<ClCompile Include="..\source\packets\cPacket_13.cpp" />
<ClCompile Include="..\source\packets\cPacket_AddToInventory.cpp" />
@ -485,6 +486,7 @@
<ClCompile Include="..\source\Vector3f.cpp" />
<ClCompile Include="..\source\Vector3i.cpp" />
<ClCompile Include="..\source\WorldStorage.cpp" />
<ClCompile Include="..\source\WSSAnvil.cpp" />
<ClCompile Include="..\source\WSSCompact.cpp" />
</ItemGroup>
<ItemGroup>
@ -602,6 +604,7 @@
<ClInclude Include="..\source\Bindings.h" />
<ClInclude Include="..\source\Defines.h" />
<ClInclude Include="..\source\MCSocket.h" />
<ClInclude Include="..\source\NBT.h" />
<ClInclude Include="..\Source\PacketID.h" />
<ClInclude Include="..\source\packets\cPacket.h" />
<ClInclude Include="..\source\packets\cPacket_13.h" />
@ -664,6 +667,7 @@
<ClInclude Include="..\source\Vector3f.h" />
<ClInclude Include="..\source\Vector3i.h" />
<ClInclude Include="..\source\WorldStorage.h" />
<ClInclude Include="..\source\WSSAnvil.h" />
<ClInclude Include="..\source\WSSCompact.h" />
<ClInclude Include="resource.h" />
</ItemGroup>

View File

@ -919,6 +919,8 @@
<Filter>Simulator\cSimulator\cRedstoneSimulator</Filter>
</ClCompile>
<ClCompile Include="..\source\ChunkSender.cpp" />
<ClCompile Include="..\source\WSSAnvil.cpp" />
<ClCompile Include="..\source\NBT.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\source\cServer.h">
@ -1419,6 +1421,8 @@
<Filter>Simulator\cSimulator\cRedstoneSimulator</Filter>
</ClInclude>
<ClInclude Include="..\source\ChunkSender.h" />
<ClInclude Include="..\source\WSSAnvil.h" />
<ClInclude Include="..\source\NBT.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\source\AllToLua.pkg">

620
source/NBT.cpp Normal file
View File

@ -0,0 +1,620 @@
// NBT.cpp
// Implements the classes used for NBT representation, parsing and serializing
/*
This file has been retrofitted from a different project of mine (_Xoft(o)) and melded here, so it is not strictyl MCS-styled
Also the project used error codes, which MCS doesn't need, so they were declared locally only.
The code is not strictly safe, it could leak pointers when exceptions are thrown. It could use a RAII redesign.
*/
#include "Globals.h"
#include "NBT.h"
#include "Endianness.h"
// Error codes
// Unused by MCS
enum
{
ERROR_PRIVATE_BASE = 0x00040000,
ERROR_PRIVATE_NBTPARSER_BADTYPE, // The parsed type is not recognized
ERROR_PRIVATE_NBTPARSER_INVALIDLENGTH, // The parsed name has an invalid length (negative-length string etc.)
ERROR_PRIVATE_NBTPARSER_UNEXPECTEDTAG, // The parser has encountered a tag that should not be at such a point
ERROR_PRIVATE_NBTPARSER_TOOSHORT, // The parser was invoked on data that is too short
ERROR_PRIVATE_NBT_UNINITIALIZEDLIST, // NBTList needs its ChildType set before adding items to it
ERROR_PRIVATE_NBT_TYPEMISMATCH, // NBTList must have all of its children of the same type
ERROR_PRIVATE_NBT_UNEXPECTEDTAG, // NBTList and NBTCompound cannot contain a TAG_End
ERROR_PRIVATE_NBT_BADTYPE, // NBTList's children type cannot be set to such a value
ERROR_PRIVATE_NBT_LISTNOTEMPTY, // NBTList must be empty to allow setting children type
ERROR_PRIVATE_NBT_INDEXOUTOFRANGE, // Requesting an item with invalid index from a list or a compound
ERROR_PRIVATE_UNKNOWN, // Unknown error
} ;
#ifndef ERROR_SUCCESS
// This constant is actually #defined in the WinAPI; it cannot be put into the above enum, but needs to be #defined (for *nix)
#define ERROR_SUCCESS 0
#endif // ERROR_SUCCCESS
#ifndef ERROR_NOT_ENOUGH_MEMORY
// This constant is actually #defined in the WinAPI; it cannot be put into the above enum, but needs to be #defined (for *nix)
#define ERROR_NOT_ENOUGH_MEMORY 8
#endif // ERROR_NOT_ENOUGH_MEMORY
#define RETURN_INT_IF_FAILED(X) {int r = X; if (r != ERROR_SUCCESS) return r; }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cNBTTag:
cNBTTag * cNBTTag::CreateTag(cNBTTag * a_Parent, eTagType a_Type, const AString & a_Name)
{
// Creates a new instance of a tag specified by a_Type, uses the correct class
switch (a_Type)
{
case TAG_Byte: return new cNBTByte (a_Parent, a_Name);
case TAG_Short: return new cNBTShort (a_Parent, a_Name);
case TAG_Int: return new cNBTInt (a_Parent, a_Name);
case TAG_Long: return new cNBTLong (a_Parent, a_Name);
case TAG_Float: return new cNBTFloat (a_Parent, a_Name);
case TAG_Double: return new cNBTDouble (a_Parent, a_Name);
case TAG_ByteArray: return new cNBTByteArray(a_Parent, a_Name);
case TAG_String: return new cNBTString (a_Parent, a_Name);
case TAG_List: return new cNBTList (a_Parent, a_Name);
case TAG_Compound: return new cNBTCompound (a_Parent, a_Name);
default:
{
ASSERT("Unknown TAG type requested" == NULL);
return NULL;
}
}
}
cNBTTag * cNBTTag::FindChildByPath(const AString & iPath)
{
size_t PrevIdx = 0;
size_t idx = iPath.find('\\');
cNBTTag * res = this;
while ((res != NULL) && (idx != AString::npos))
{
res = res->FindChildByName(AString(iPath, PrevIdx, idx - PrevIdx));
PrevIdx = idx + 1;
idx = iPath.find('\\', PrevIdx);
}
if (res != NULL)
{
res = res->FindChildByName(AString(iPath, PrevIdx));
}
return res;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cNBTList:
void cNBTList::Clear(void)
{
for (cNBTTags::iterator itr = m_Children.begin(); itr != m_Children.end(); ++itr)
{
delete *itr;
}
m_Children.clear();
}
int cNBTList::Add(cNBTTag * iTag)
{
// Catch usage errors while debugging:
ASSERT(m_ChildrenType != TAG_End);
ASSERT(iTag->GetType() == m_ChildrenType);
// Catch errors while running:
if (m_ChildrenType == TAG_End)
{
return ERROR_PRIVATE_NBT_UNINITIALIZEDLIST;
}
if (iTag->GetType() != m_ChildrenType)
{
return ERROR_PRIVATE_NBT_TYPEMISMATCH;
}
m_Children.push_back(iTag);
return ERROR_SUCCESS;
}
int cNBTList::SetChildrenType(cNBTTag::eTagType a_Type)
{
// Catch usage errors while debugging:
ASSERT(a_Type != TAG_End); // Invalid, though not specifically in the NBT spec
ASSERT(m_Children.size() == 0); // Can change only when empty
// Catch runtime errors:
if (a_Type == TAG_End)
{
return ERROR_PRIVATE_NBT_BADTYPE;
}
if (m_Children.size() != 0)
{
return ERROR_PRIVATE_NBT_LISTNOTEMPTY;
}
m_ChildrenType = a_Type;
return ERROR_SUCCESS;
}
cNBTTag * cNBTList::GetChildByIdx(size_t iIndex)
{
// Catch usage errors while debugging:
ASSERT((iIndex >= 0) && (iIndex < m_Children.size()));
// Catch runtime errors:
if ((iIndex < 0) || (iIndex >= m_Children.size()))
{
return NULL;
}
return m_Children[iIndex];
}
cNBTTag * cNBTList::FindChildByName(const AString & a_Name)
{
for (cNBTTags::iterator itr = m_Children.begin(); itr != m_Children.end(); ++itr)
{
if ((*itr)->GetName() == a_Name)
{
return *itr;
}
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cNBTCompound:
void cNBTCompound::Clear(void)
{
for (cNBTTags::iterator itr = m_Children.begin(); itr != m_Children.end(); ++itr)
{
delete *itr;
}
m_Children.clear();
}
int cNBTCompound::Add(cNBTTag * iTag)
{
// Catch usage errors while debugging:
ASSERT(iTag->GetType() != TAG_End);
// Catch runtime errors:
if (iTag->GetType() == TAG_End)
{
return ERROR_PRIVATE_NBT_UNEXPECTEDTAG;
}
m_Children.push_back(iTag);
return ERROR_SUCCESS;
}
cNBTTag * cNBTCompound::GetChildByIdx(size_t iIndex)
{
// Catch usage errors while debugging:
ASSERT((iIndex >= 0) && (iIndex < m_Children.size()));
// Catch runtime errors:
if ((iIndex < 0) || (iIndex >= m_Children.size()))
{
return NULL;
}
return m_Children[iIndex];
}
cNBTTag * cNBTCompound::FindChildByName(const AString & a_Name)
{
for (cNBTTags::iterator itr = m_Children.begin(); itr != m_Children.end(); ++itr)
{
if ((*itr)->GetName() == a_Name)
{
return *itr;
}
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cNBTParser:
int cNBTParser::ReadByte(const char ** a_Data, int * a_Length, char & a_Value)
{
if (*a_Length < 1)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_Value = **a_Data;
*a_Data += 1;
*a_Length -= 1;
return ERROR_SUCCESS;
}
int cNBTParser::ReadInt16(const char ** a_Data, int * a_Length, Int16 & a_Value)
{
if (*a_Length < 2)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_Value = ntohs(*((Int16 *)*a_Data));
*a_Data += 2;
*a_Length -= 2;
return ERROR_SUCCESS;
}
int cNBTParser::ReadInt32(const char ** a_Data, int * a_Length, Int32 & a_Value)
{
if (*a_Length < 4)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_Value = ntohl(*((Int32 *)*a_Data));
*a_Data += 4;
*a_Length -= 4;
return ERROR_SUCCESS;
}
int cNBTParser::ReadInt64(const char ** a_Data, int * a_Length, Int64 & a_Value)
{
if (*a_Length < 8)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_Value = NetworkToHostLong8(*a_Data);
*a_Data += 8;
*a_Length -= 8;
return ERROR_SUCCESS;
}
int cNBTParser::ReadFloat(const char ** a_Data, int * a_Length, float & a_Value)
{
if (*a_Length < 4)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
// Read as a 32-bit integer, converting endianness, then reinterpret as float:
Int32 tmp = ntohl(*((Int32 *)*a_Data));
a_Value = *((float *)&tmp);
*a_Data += 4;
*a_Length -= 4;
return ERROR_SUCCESS;
}
int cNBTParser::ReadDouble(const char ** a_Data, int * a_Length, double & a_Value)
{
if (*a_Length < 8)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_Value = NetworkToHostDouble8(a_Data);
*a_Data += 8;
*a_Length -= 8;
return ERROR_SUCCESS;
}
int cNBTParser::ReadByteArray(const char ** a_Data, int * a_Length, AString & a_String)
{
// Reads the short-counted string, adjusts a_Data and a_Length accordingly
Int32 Len;
RETURN_INT_IF_FAILED(ReadInt32(a_Data, a_Length, Len));
if (Len < 0)
{
return ERROR_PRIVATE_NBTPARSER_INVALIDLENGTH;
}
if (*a_Length < Len)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_String.assign(*a_Data, Len);
*a_Data += Len;
*a_Length -= Len;
return ERROR_SUCCESS;
}
int cNBTParser::ReadString(const char ** a_Data, int * a_Length, AString & a_String)
{
// Reads the short-counted string, adjusts a_Data and a_Length accordingly
if (*a_Length < 2)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
Int16 val = *((Int16 *)*a_Data);
Int16 Len = ntohs(val);
if (Len < 0)
{
return ERROR_PRIVATE_NBTPARSER_INVALIDLENGTH;
}
*a_Data += 2;
*a_Length -= 2;
if (*a_Length < Len)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
a_String.assign(*a_Data, Len);
*a_Data += Len;
*a_Length -= Len;
return ERROR_SUCCESS;
}
int cNBTParser::ReadList(const char ** a_Data, int * a_Length, cNBTList * a_List)
{
// Reads a_List's contents from a_Data; up to a_Length bytes may be used; adjusts a_Data and a_Length for after the list
Int32 ItemCount;
RETURN_INT_IF_FAILED(ReadInt32(a_Data, a_Length, ItemCount));
for (Int32 i = 0; i < ItemCount; i++)
{
cNBTTag * child = NULL;
RETURN_INT_IF_FAILED(ReadTag(a_Data, a_Length, a_List->GetChildrenType(), "", a_List, &child));
if (child == NULL)
{
return ERROR_PRIVATE_UNKNOWN;
}
RETURN_INT_IF_FAILED(a_List->Add(child));
} // for i - Items[]
return ERROR_SUCCESS;
}
int cNBTParser::ReadCompound(const char ** a_Data, int * a_Length, cNBTCompound * a_Compound)
{
// Reads a_Compound's contents from a_Data; up to a_Length bytes may be used; adjusts a_Data and a_Length for after the compound
while (true)
{
char TagType = **a_Data;
*a_Data += 1;
*a_Length -= 1;
if (TagType == cNBTTag::TAG_End)
{
return ERROR_SUCCESS;
}
AString Name;
RETURN_INT_IF_FAILED(ReadString(a_Data, a_Length, Name));
cNBTTag * child = NULL;
RETURN_INT_IF_FAILED(ReadTag(a_Data, a_Length, (cNBTTag::eTagType)TagType, Name, a_Compound, &child));
if (child == NULL)
{
return ERROR_PRIVATE_UNKNOWN;
}
RETURN_INT_IF_FAILED(a_Compound->Add(child));
} // while (true)
}
int cNBTParser::ReadIntArray(const char ** a_Data, int * a_Length, cNBTIntArray * a_Array)
{
if (*a_Length < 4)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
int Count = ntohl(*((int *)*a_Data));
*a_Data += 4;
*a_Length -= 4;
if (*a_Length < 4 * Count)
{
return ERROR_PRIVATE_NBTPARSER_TOOSHORT;
}
for (int i = 0; i < Count; i++)
{
int Value = ntohl(*((int *)*a_Data));
a_Array->Add(Value);
*a_Data += 4;
}
*a_Length -= 4 * Count;
return ERROR_SUCCESS;
}
#define CASE_SIMPLE_TAG(TAGTYPE,CTYPE,FUNC) \
case cNBTTag::TAG_##TAGTYPE: \
{ \
CTYPE val; \
RETURN_INT_IF_FAILED(Read##FUNC(a_Data, a_Length, val)); \
*a_Tag = new cNBT##TAGTYPE(a_Parent, a_Name, val); \
if (*a_Tag == NULL) \
{ \
return ERROR_NOT_ENOUGH_MEMORY; \
} \
return ERROR_SUCCESS;\
}
int cNBTParser::ReadTag(const char ** a_Data, int * a_Length, cNBTTag::eTagType a_Type, const AString & a_Name, cNBTTag * a_Parent, cNBTTag ** a_Tag)
{
switch (a_Type)
{
CASE_SIMPLE_TAG(Byte, char, Byte)
CASE_SIMPLE_TAG(Short, Int16, Int16)
CASE_SIMPLE_TAG(Int, Int32, Int32)
CASE_SIMPLE_TAG(Long, Int64, Int64)
CASE_SIMPLE_TAG(Float, float, Float)
CASE_SIMPLE_TAG(Double, double, Double)
CASE_SIMPLE_TAG(ByteArray, AString, ByteArray)
CASE_SIMPLE_TAG(String, AString, String)
case cNBTTag::TAG_List:
{
char ItemType;
RETURN_INT_IF_FAILED(ReadByte (a_Data, a_Length, ItemType));
cNBTList * List = new cNBTList(a_Parent, a_Name);
if (List == NULL)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
RETURN_INT_IF_FAILED(List->SetChildrenType((cNBTTag::eTagType)ItemType));
RETURN_INT_IF_FAILED(ReadList(a_Data, a_Length, List));
*a_Tag = List;
return ERROR_SUCCESS;
}
case cNBTTag::TAG_Compound:
{
cNBTCompound * Compound = new cNBTCompound(a_Parent, a_Name);
if (Compound == NULL)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
RETURN_INT_IF_FAILED(ReadCompound(a_Data, a_Length, Compound));
*a_Tag = Compound;
return ERROR_SUCCESS;
}
case cNBTTag::TAG_IntArray:
{
cNBTIntArray * Array = new cNBTIntArray(a_Parent, a_Name);
if (Array == NULL)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
RETURN_INT_IF_FAILED(ReadIntArray(a_Data, a_Length, Array));
*a_Tag = Array;
return ERROR_SUCCESS;
}
default:
{
ASSERT(!"Unhandled NBT tag type");
break;
}
} // switch (iType)
return ERROR_PRIVATE_NBTPARSER_BADTYPE;
}
cNBTTree * cNBTParser::Parse(const char * a_Data, int a_Length)
{
// Creates a NBT from a_Data
if (a_Length < 3)
{
return NULL;
}
if (a_Data[0] != cNBTTag::TAG_Compound)
{
return NULL;
}
a_Data++;
a_Length--;
AString Name;
if (ReadString(&a_Data, &a_Length, Name) != 0)
{
return NULL;
}
std::auto_ptr<cNBTCompound> Root(new cNBTCompound(NULL, Name));
if (Root.get() == NULL)
{
return NULL;
}
if (ReadCompound(&a_Data, &a_Length, Root.get()) == 0)
{
return Root.release();
}
return NULL;
}

201
source/NBT.h Normal file
View File

@ -0,0 +1,201 @@
// NBT.h
// Interfaces to the classes used for NBT representation, parsing and serializing
#pragma once
typedef long long Int64;
typedef int Int32;
typedef short Int16;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Representation classes:
class cNBTTag abstract // The base class for all NBT tags
{
public:
enum eTagType
{
TAG_Min = 0, // The minimum value for a tag type
TAG_End = 0,
TAG_Byte = 1,
TAG_Short = 2,
TAG_Int = 3,
TAG_Long = 4,
TAG_Float = 5,
TAG_Double = 6,
TAG_ByteArray = 7,
TAG_String = 8,
TAG_List = 9,
TAG_Compound = 10,
TAG_IntArray = 11,
TAG_Max = 11, // The maximum value for a tag type
} ;
protected:
cNBTTag * m_Parent;
eTagType m_Type;
AString m_Name; // tag name, in UTF-8
public:
cNBTTag(cNBTTag * a_Parent, eTagType a_Type) : m_Parent(a_Parent), m_Type(a_Type) {}
cNBTTag(cNBTTag * a_Parent, eTagType a_Type, const AString & a_Name) : m_Parent(a_Parent), m_Type(a_Type), m_Name(a_Name) {}
virtual ~cNBTTag() {} // Force a virtual destructor
cNBTTag * GetParent(void) const {return m_Parent; }
eTagType GetType (void) const {return m_Type; }
const AString & GetName (void) const {return m_Name; }
void SetName (const AString & a_Name) {m_Name = a_Name; }
static cNBTTag * CreateTag(cNBTTag * a_Parent, eTagType a_Type, const AString & a_Name); // Creates a new instance of a tag specified by iType, uses the correct class
virtual cNBTTag * FindChildByName(const AString & a_Name) {return NULL; }
cNBTTag * FindChildByPath(const AString & a_Path);
} ;
typedef cNBTTag cNBTTree;
typedef std::vector<cNBTTag *> cNBTTags;
#define DECLARE_SIMPLE_TAG(TAG,CTYPE) \
class cNBT##TAG : \
public cNBTTag \
{ \
public: \
cNBT##TAG(cNBTTag * a_Parent) : cNBTTag(a_Parent, TAG_##TAG) {} \
cNBT##TAG(cNBTTag * a_Parent, const AString & a_Name) : cNBTTag(a_Parent, TAG_##TAG, a_Name) {} \
cNBT##TAG(cNBTTag * a_Parent, const AString & a_Name, const CTYPE & a_Value) : cNBTTag(a_Parent, TAG_##TAG, a_Name), m_Value(a_Value) {} \
CTYPE m_Value; \
}
DECLARE_SIMPLE_TAG(Byte, char);
DECLARE_SIMPLE_TAG(Short, Int16);
DECLARE_SIMPLE_TAG(Int, Int32);
DECLARE_SIMPLE_TAG(Long, Int64);
DECLARE_SIMPLE_TAG(Float, float);
DECLARE_SIMPLE_TAG(Double, double);
DECLARE_SIMPLE_TAG(ByteArray, AString); // Represent the array as a string for easier manipulation
DECLARE_SIMPLE_TAG(String, AString);
class cNBTList :
public cNBTTag
{
cNBTTags m_Children;
eTagType m_ChildrenType;
public:
cNBTList(cNBTTag * a_Parent) : cNBTTag(a_Parent, TAG_List), m_ChildrenType(TAG_End) {}
cNBTList(cNBTTag * a_Parent, const AString & a_Name) : cNBTTag(a_Parent, TAG_List, a_Name), m_ChildrenType(TAG_End) {}
virtual ~cNBTList() {Clear(); }
void Clear (void);
int Add (cNBTTag * a_Tag);
cNBTTag * GetChildByIdx (size_t a_Index);
const cNBTTags & GetChildren (void) const {return m_Children; }
size_t GetChildrenCount(void) const {return m_Children.size(); }
virtual cNBTTag * FindChildByName (const AString & a_Name) override;
int SetChildrenType(eTagType a_Type); // Only valid when list empty
eTagType GetChildrenType(void) const {return m_ChildrenType; }
} ;
class cNBTCompound :
public cNBTTag
{
cNBTTags m_Children;
public:
cNBTCompound(cNBTTag * a_Parent) : cNBTTag(a_Parent, TAG_Compound) {}
cNBTCompound(cNBTTag * a_Parent, const AString & a_Name) : cNBTTag(a_Parent, TAG_Compound, a_Name) {}
virtual ~cNBTCompound() {Clear(); }
void Clear (void);
int Add (cNBTTag * a_Tag);
cNBTTag * GetChildByIdx (size_t a_Index);
const cNBTTags & GetChildren (void) const {return m_Children; }
size_t GetChildrenCount(void) const {return m_Children.size(); }
virtual cNBTTag * FindChildByName (const AString & a_Name) override;
} ;
class cNBTIntArray :
public cNBTTag
{
typedef cNBTTag super;
std::vector<int> m_Values;
public:
cNBTIntArray(cNBTTag * a_Parent) : super(a_Parent, TAG_IntArray) {}
cNBTIntArray(cNBTTag * a_Parent, const AString & a_Name) : super(a_Parent, TAG_IntArray, a_Name) {}
void Clear(void) {m_Values.clear(); }
void Add (int a_Value) {m_Values.push_back(a_Value); }
int Get (int a_Index) const {return m_Values[a_Index]; }
int Size (void) const {return m_Values.size(); }
const std::vector<int> & GetValues(void) const {return m_Values; }
} ;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The parser:
class cNBTParser
{
static int ReadTag (const char ** Data, int * Length, cNBTTag::eTagType iType, const AString & a_Name, cNBTTag * iParent, cNBTTag ** oTag); // Helper
static int ReadByte (const char ** Data, int * Length, char & a_Value);
static int ReadInt16 (const char ** Data, int * Length, Int16 & a_Value);
static int ReadInt32 (const char ** Data, int * Length, Int32 & a_Value);
static int ReadInt64 (const char ** Data, int * Length, Int64 & a_Value);
static int ReadFloat (const char ** Data, int * Length, float & a_Value);
static int ReadDouble (const char ** Data, int * Length, double & a_Value);
static int ReadByteArray(const char ** Data, int * Length, AString & a_Value);
static int ReadString (const char ** Data, int * Length, AString & a_Value);
static int ReadList (const char ** Data, int * Length, cNBTList * a_List);
static int ReadCompound (const char ** Data, int * Length, cNBTCompound * a_Compound);
static int ReadIntArray (const char ** Data, int * Length, cNBTIntArray * a_Array);
public:
/// Returns the parsed tree, or NULL on failure
static cNBTTree * Parse(const char * a_Data, int a_Length);
} ;

View File

@ -33,7 +33,7 @@ int CompressString(const char * a_Data, int a_Length, AString & a_Compressed)
/// Uncompresses a_Data into a_Decompressed; returns Z_XXX error constants same as zlib's decompress()
/// Uncompresses a_Data into a_Decompressed; returns Z_XXX error constants same as zlib's uncompress()
int UncompressString(const char * a_Data, int a_Length, AString & a_Uncompressed, int a_UncompressedSize)
{
// HACK: We're assuming that AString returns its internal buffer in its data() call and we're overwriting that buffer!

338
source/WSSAnvil.cpp Normal file
View File

@ -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);
}

95
source/WSSAnvil.h Normal file
View File

@ -0,0 +1,95 @@
// WSSAnvil.h
// Interfaces to the cWSSAnvil class representing the Anvil world storage scheme
#pragma once
#include "WorldStorage.h"
enum
{
// The MCA header is 8 KiB
MCA_HEADER_SIZE = 8192,
} ;
// fwd: "NBT.h"
class cNBTTag;
class cWSSAnvil :
public cWSSchema
{
typedef cWSSchema super;
public:
cWSSAnvil(cWorld * a_World) : super(a_World) {}
virtual ~cWSSAnvil();
protected:
class cMCAFile
{
public:
cMCAFile(const AString & a_FileName, int a_RegionX, int a_RegionZ);
bool GetChunkData(const cChunkCoords & a_Chunk, AString & a_Data);
int GetRegionX (void) const {return m_RegionX; }
int GetRegionZ (void) const {return m_RegionZ; }
const AString & GetFileName(void) const {return m_FileName; }
protected:
int m_RegionX;
int m_RegionZ;
cFile m_File;
AString m_FileName;
// The header, copied from the file so we don't have to seek to it all the time
// First 1024 entries are chunk locations - the 3 + 1 byte sector-offset and sector-count
// The next 1024 entries are chunk timestamps - unused in MCS
unsigned m_Header[MCA_HEADER_SIZE / sizeof(unsigned)];
} ;
typedef std::list<cMCAFile *> cMCAFiles;
cCriticalSection m_CS;
cMCAFiles m_Files; // a MRU cache of MCA files
/// Gets chunk data from the correct file; locks CS as needed
bool GetChunkData(const cChunkCoords & a_Chunk, AString & a_Data);
/// Loads the chunk from the data (no locking needed)
bool LoadChunkFromData(const cChunkCoords & a_Chunk, const AString & a_Data);
/// Loads the chunk from NBT data (no locking needed)
bool LoadChunkFromNBT(const cChunkCoords & a_Chunk, cNBTTag & a_NBT);
/// Gets the correct MCA file either from cache or from disk, manages the m_MCAFiles cache; assumes m_CS is locked
cMCAFile * LoadMCAFile(const cChunkCoords & a_Chunk);
// cWSSchema overrides:
virtual bool LoadChunk(const cChunkCoords & a_Chunk) override;
virtual bool SaveChunk(const cChunkCoords & a_Chunk) override;
virtual const AString GetName(void) const override {return "anvil"; }
} ;

View File

@ -8,6 +8,7 @@
#include "Globals.h"
#include "WorldStorage.h"
#include "WSSCompact.h"
#include "WSSAnvil.h"
#include "cWorld.h"
#include "cChunkGenerator.h"
#include "cEntity.h"
@ -270,7 +271,8 @@ void cWorldStorage::UnqueueSave(const cChunkCoords & a_Chunk)
void cWorldStorage::InitSchemas(void)
{
// The first schema added is considered the default
m_Schemas.push_back(new cWSSCompact(m_World));
m_Schemas.push_back(new cWSSCompact (m_World));
m_Schemas.push_back(new cWSSAnvil (m_World));
m_Schemas.push_back(new cWSSForgetful(m_World));
// Add new schemas here