From 69a4ee53830121714927c37e4f0b9a357cd03957 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Wed, 7 Mar 2012 11:28:24 +0000 Subject: [PATCH] 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 --- VC2008/MCServer.vcproj | 16 + VC2010/MCServer.vcxproj | 4 + VC2010/MCServer.vcxproj.filters | 4 + source/NBT.cpp | 620 ++++++++++++++++++++++++++++++++ source/NBT.h | 201 +++++++++++ source/StringCompression.cpp | 2 +- source/WSSAnvil.cpp | 338 +++++++++++++++++ source/WSSAnvil.h | 95 +++++ source/WorldStorage.cpp | 4 +- 9 files changed, 1282 insertions(+), 2 deletions(-) create mode 100644 source/NBT.cpp create mode 100644 source/NBT.h create mode 100644 source/WSSAnvil.cpp create mode 100644 source/WSSAnvil.h diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index c647ec267..b55517642 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -565,6 +565,14 @@ RelativePath="..\source\MersenneTwister.h" > + + + + @@ -1646,6 +1654,14 @@ RelativePath="..\source\WorldStorage.h" > + + + + 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 @@ + @@ -485,6 +486,7 @@ + @@ -602,6 +604,7 @@ + @@ -664,6 +667,7 @@ + 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 @@ Simulator\cSimulator\cRedstoneSimulator + + @@ -1419,6 +1421,8 @@ Simulator\cSimulator\cRedstoneSimulator + + 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 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 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 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 & 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 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 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