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:
parent
bae54ee7bd
commit
69a4ee5383
@ -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"
|
||||
>
|
||||
|
@ -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>
|
||||
|
@ -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
620
source/NBT.cpp
Normal 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
201
source/NBT.h
Normal 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);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -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
338
source/WSSAnvil.cpp
Normal 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
95
source/WSSAnvil.h
Normal 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"; }
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -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"
|
||||
@ -271,6 +272,7 @@ 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 cWSSAnvil (m_World));
|
||||
m_Schemas.push_back(new cWSSForgetful(m_World));
|
||||
// Add new schemas here
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user