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"
|
RelativePath="..\source\MersenneTwister.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\source\NBT.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\source\NBT.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\source\StackWalker.cpp"
|
RelativePath="..\source\StackWalker.cpp"
|
||||||
>
|
>
|
||||||
@ -1646,6 +1654,14 @@
|
|||||||
RelativePath="..\source\WorldStorage.h"
|
RelativePath="..\source\WorldStorage.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\source\WSSAnvil.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\source\WSSAnvil.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\source\WSSCompact.cpp"
|
RelativePath="..\source\WSSCompact.cpp"
|
||||||
>
|
>
|
||||||
|
@ -421,6 +421,7 @@
|
|||||||
<ClCompile Include="..\source\cLog.cpp" />
|
<ClCompile Include="..\source\cLog.cpp" />
|
||||||
<ClCompile Include="..\source\Bindings.cpp" />
|
<ClCompile Include="..\source\Bindings.cpp" />
|
||||||
<ClCompile Include="..\source\main.cpp" />
|
<ClCompile Include="..\source\main.cpp" />
|
||||||
|
<ClCompile Include="..\source\NBT.cpp" />
|
||||||
<ClCompile Include="..\source\packets\cPacket.cpp" />
|
<ClCompile Include="..\source\packets\cPacket.cpp" />
|
||||||
<ClCompile Include="..\source\packets\cPacket_13.cpp" />
|
<ClCompile Include="..\source\packets\cPacket_13.cpp" />
|
||||||
<ClCompile Include="..\source\packets\cPacket_AddToInventory.cpp" />
|
<ClCompile Include="..\source\packets\cPacket_AddToInventory.cpp" />
|
||||||
@ -485,6 +486,7 @@
|
|||||||
<ClCompile Include="..\source\Vector3f.cpp" />
|
<ClCompile Include="..\source\Vector3f.cpp" />
|
||||||
<ClCompile Include="..\source\Vector3i.cpp" />
|
<ClCompile Include="..\source\Vector3i.cpp" />
|
||||||
<ClCompile Include="..\source\WorldStorage.cpp" />
|
<ClCompile Include="..\source\WorldStorage.cpp" />
|
||||||
|
<ClCompile Include="..\source\WSSAnvil.cpp" />
|
||||||
<ClCompile Include="..\source\WSSCompact.cpp" />
|
<ClCompile Include="..\source\WSSCompact.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -602,6 +604,7 @@
|
|||||||
<ClInclude Include="..\source\Bindings.h" />
|
<ClInclude Include="..\source\Bindings.h" />
|
||||||
<ClInclude Include="..\source\Defines.h" />
|
<ClInclude Include="..\source\Defines.h" />
|
||||||
<ClInclude Include="..\source\MCSocket.h" />
|
<ClInclude Include="..\source\MCSocket.h" />
|
||||||
|
<ClInclude Include="..\source\NBT.h" />
|
||||||
<ClInclude Include="..\Source\PacketID.h" />
|
<ClInclude Include="..\Source\PacketID.h" />
|
||||||
<ClInclude Include="..\source\packets\cPacket.h" />
|
<ClInclude Include="..\source\packets\cPacket.h" />
|
||||||
<ClInclude Include="..\source\packets\cPacket_13.h" />
|
<ClInclude Include="..\source\packets\cPacket_13.h" />
|
||||||
@ -664,6 +667,7 @@
|
|||||||
<ClInclude Include="..\source\Vector3f.h" />
|
<ClInclude Include="..\source\Vector3f.h" />
|
||||||
<ClInclude Include="..\source\Vector3i.h" />
|
<ClInclude Include="..\source\Vector3i.h" />
|
||||||
<ClInclude Include="..\source\WorldStorage.h" />
|
<ClInclude Include="..\source\WorldStorage.h" />
|
||||||
|
<ClInclude Include="..\source\WSSAnvil.h" />
|
||||||
<ClInclude Include="..\source\WSSCompact.h" />
|
<ClInclude Include="..\source\WSSCompact.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -919,6 +919,8 @@
|
|||||||
<Filter>Simulator\cSimulator\cRedstoneSimulator</Filter>
|
<Filter>Simulator\cSimulator\cRedstoneSimulator</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\source\ChunkSender.cpp" />
|
<ClCompile Include="..\source\ChunkSender.cpp" />
|
||||||
|
<ClCompile Include="..\source\WSSAnvil.cpp" />
|
||||||
|
<ClCompile Include="..\source\NBT.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\source\cServer.h">
|
<ClInclude Include="..\source\cServer.h">
|
||||||
@ -1419,6 +1421,8 @@
|
|||||||
<Filter>Simulator\cSimulator\cRedstoneSimulator</Filter>
|
<Filter>Simulator\cSimulator\cRedstoneSimulator</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\source\ChunkSender.h" />
|
<ClInclude Include="..\source\ChunkSender.h" />
|
||||||
|
<ClInclude Include="..\source\WSSAnvil.h" />
|
||||||
|
<ClInclude Include="..\source\NBT.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\source\AllToLua.pkg">
|
<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)
|
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!
|
// 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 "Globals.h"
|
||||||
#include "WorldStorage.h"
|
#include "WorldStorage.h"
|
||||||
#include "WSSCompact.h"
|
#include "WSSCompact.h"
|
||||||
|
#include "WSSAnvil.h"
|
||||||
#include "cWorld.h"
|
#include "cWorld.h"
|
||||||
#include "cChunkGenerator.h"
|
#include "cChunkGenerator.h"
|
||||||
#include "cEntity.h"
|
#include "cEntity.h"
|
||||||
@ -270,7 +271,8 @@ void cWorldStorage::UnqueueSave(const cChunkCoords & a_Chunk)
|
|||||||
void cWorldStorage::InitSchemas(void)
|
void cWorldStorage::InitSchemas(void)
|
||||||
{
|
{
|
||||||
// The first schema added is considered the default
|
// 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));
|
m_Schemas.push_back(new cWSSForgetful(m_World));
|
||||||
// Add new schemas here
|
// Add new schemas here
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user