diff options
Diffstat (limited to '')
-rw-r--r-- | VC2008/MCServer.vcproj | 16 | ||||
-rw-r--r-- | VC2010/MCServer.vcxproj | 4 | ||||
-rw-r--r-- | VC2010/MCServer.vcxproj.filters | 4 | ||||
-rw-r--r-- | source/NBT.cpp | 620 | ||||
-rw-r--r-- | source/NBT.h | 201 | ||||
-rw-r--r-- | source/StringCompression.cpp | 2 | ||||
-rw-r--r-- | source/WSSAnvil.cpp | 338 | ||||
-rw-r--r-- | source/WSSAnvil.h | 95 | ||||
-rw-r--r-- | source/WorldStorage.cpp | 4 |
9 files changed, 1282 insertions, 2 deletions
diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index c647ec267..b55517642 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -566,6 +566,14 @@ >
</File>
<File
+ RelativePath="..\source\NBT.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\source\NBT.h"
+ >
+ </File>
+ <File
RelativePath="..\source\StackWalker.cpp"
>
<FileConfiguration
@@ -1647,6 +1655,14 @@ >
</File>
<File
+ RelativePath="..\source\WSSAnvil.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\source\WSSAnvil.h"
+ >
+ </File>
+ <File
RelativePath="..\source\WSSCompact.cpp"
>
</File>
diff --git a/VC2010/MCServer.vcxproj b/VC2010/MCServer.vcxproj index 89662dba8..83515f880 100644 --- a/VC2010/MCServer.vcxproj +++ b/VC2010/MCServer.vcxproj @@ -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>
diff --git a/VC2010/MCServer.vcxproj.filters b/VC2010/MCServer.vcxproj.filters index 4640ff5de..294a954a9 100644 --- a/VC2010/MCServer.vcxproj.filters +++ b/VC2010/MCServer.vcxproj.filters @@ -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">
diff --git a/source/NBT.cpp b/source/NBT.cpp new file mode 100644 index 000000000..c48124cb3 --- /dev/null +++ b/source/NBT.cpp @@ -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;
+}
+
+
+
+
+
diff --git a/source/NBT.h b/source/NBT.h new file mode 100644 index 000000000..5bad0492c --- /dev/null +++ b/source/NBT.h @@ -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);
+} ;
+
+
+
+
diff --git a/source/StringCompression.cpp b/source/StringCompression.cpp index d21248fac..31506604a 100644 --- a/source/StringCompression.cpp +++ b/source/StringCompression.cpp @@ -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!
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);
+}
+
+
+
+
diff --git a/source/WSSAnvil.h b/source/WSSAnvil.h new file mode 100644 index 000000000..f02a30119 --- /dev/null +++ b/source/WSSAnvil.h @@ -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"; }
+} ;
+
+
+
+
diff --git a/source/WorldStorage.cpp b/source/WorldStorage.cpp index 4c433aef4..484b87691 100644 --- a/source/WorldStorage.cpp +++ b/source/WorldStorage.cpp @@ -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
|