From 69a4ee53830121714927c37e4f0b9a357cd03957 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Wed, 7 Mar 2012 11:28:24 +0000 Subject: Added the Anvil level format (MCA) support, read-only for the time being. Now MCS can read your worlds from the official server :) git-svn-id: http://mc-server.googlecode.com/svn/trunk@380 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/NBT.cpp | 620 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 source/NBT.cpp (limited to 'source/NBT.cpp') 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; +} + + + + + -- cgit v1.2.3