summaryrefslogblamecommitdiffstats
path: root/src/WorldStorage/FastNBT.h
blob: a2fe1cd51a189af7c2238699699a08b6d35987f0 (plain) (tree)
1
2
3
4
5
6
7
8






                                                                                                                
                                                                       


                                                                                                                  
                                                                             








                                                                
                          
                      








































                                                                                                                       



                            
        
                                                                                       
                                                                                                 




                                     
        
                                                               
                               
                               
                                
                               

                                   



                                                      


         
                                                                                             
                               
                               
                                
                               


                                             


                                                      










                                                                                                                                                      



                                                                                                               



                
                                                                  


                                                     
                                                     
                                               
 

                                                                                                
        

                                                                                               
        

                                                                                                 
        

                                                                                                 


                                                           
                                                 
         


                                                             
         
 

                                                
                                                
         


                                                                                  

         


                                                                                          



                                                                               

                                                                                                         
 

                                                                                                              
        
                                                                                                  
        
                                                                                                                 
                                                    
         
                                                                              
                                                                                                                                                                        

         
                                                                                        
                                                        
         

                                                                                                                               

         
                                                                                         
                                                 
         

                                                                      

         
                                                                                        
                                               
         

                                                                                         

         
                                                                                        

                                             

                                                                                                   

         
                                                                                         

                                              
                                                                               
                

                                                                                                                               

                                                                              

                                   
                
                                                                                            


                                          

         
                                                                                          

                                                



                                                                                                                                

                                   
 

                                                                                                     

         
                                                                                          
                                                    
         
                                                                                



                                                                                                                       


                           
                                                                                            
                                                  
         



                                                                                                                        


                           
          
                                        
                                          



                                                                         
                     

                         
                                                                                                                                                     











                                                                                                                  
                                                           













                                                                           
                                                                                              

                                                                                              
                                                                                          



                                                                     
                                                                                 







                                                         
                                                                             
                                                                
                                                                       

           
                                                                                                                   




                                                                                                                    
                                         


                                                                                                        
                                                               


                                                                      


                                                                                                                       


                                                           
                                                                      
                                                                                          











                                                          

// FastNBT.h

// Interfaces to the fast NBT parser and writer

/*
The fast parser parses the data into a vector of cFastNBTTag structures. These structures describe the NBT tree,
but themselves are allocated in a vector, thus minimizing reallocation.
The structures have a minimal constructor, setting all member "pointers" to "invalid".

The fast writer doesn't need a NBT tree structure built beforehand, it is commanded to open, append and close tags
(just like XML); it keeps the internal tag stack and reports errors in usage.
It directly outputs a string containing the serialized NBT data.
*/





#pragma once

#include "../Endianness.h"
#include "../Option.h"





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
} ;





/** This structure is used for all NBT tags.
It contains indices to the parent array of tags, building the NBT tree this way.
Also contains indices into the data stream being parsed, used for values;
NO dynamically allocated memory is used!
Structure (all with the tree structure it describes) supports moving in memory (std::vector reallocation)
*/
struct cFastNBTTag
{
public:
	
	eTagType m_Type;
	
	// The following members are indices into the data stream. m_DataLength == 0 if no data available
	// They must not be pointers, because the datastream may be copied into another AString object in the meantime.
	size_t m_NameStart;
	size_t m_NameLength;
	size_t m_DataStart;
	size_t m_DataLength;
	
	// The following members are indices into the array returned; none if not valid
	// They must not be pointers, because pointers would not survive std::vector reallocation
	Option<size_t> m_Parent;
	Option<size_t> m_PrevSibling;
	Option<size_t> m_NextSibling;
	Option<size_t> m_FirstChild;
	Option<size_t> m_LastChild;
	
	cFastNBTTag(eTagType a_Type, Option<size_t> a_Parent) :
		m_Type(a_Type),
		m_NameStart(0),
		m_NameLength(0),
		m_DataStart(0),
		m_DataLength(0),
		m_Parent(a_Parent),
		m_PrevSibling(Option<size_t>::None()),
		m_NextSibling(Option<size_t>::None()),
		m_FirstChild(Option<size_t>::None()),
		m_LastChild(Option<size_t>::None())
	{
	}

	cFastNBTTag(eTagType a_Type, Option<size_t> a_Parent, Option<size_t> a_PrevSibling) :
		m_Type(a_Type),
		m_NameStart(0),
		m_NameLength(0),
		m_DataStart(0),
		m_DataLength(0),
		m_Parent(a_Parent),
		m_PrevSibling(a_PrevSibling),
		m_NextSibling(Option<size_t>::None()),
		m_FirstChild(Option<size_t>::None()),
		m_LastChild(Option<size_t>::None())
	{
	}
} ;





/** Parses and contains the parsed data
Also implements data accessor functions for tree traversal and value getters
The data pointer passed in the constructor is assumed to be valid throughout the object's life. Care must be taken not to initialize from a temporary.
The parser decomposes the input data into a tree of tags that is stored as an array of cFastNBTTag items,
and accessing the tree is done by using the array indices for tags. Each tag stores the indices for its parent,
first child, last child, prev sibling and next sibling, a value of -1 indicates that the indice is not valid.
Each primitive tag also stores the length of the contained data, in bytes.
*/
class cParsedNBT
{
public:
	explicit cParsedNBT(const std::basic_string<Byte> & data);
	
	bool IsValid(void) const {return m_IsValid; }
	
	/** Returns the root tag of the hierarchy. */
	size_t GetRoot(void) const {return 0; }

	/** Returns the first child of the specified tag, or none if none / not applicable. */
	Option<size_t> GetFirstChild (size_t a_Tag) const { return m_Tags[a_Tag].m_FirstChild; }
	
	/** Returns the last child of the specified tag, or  none if none / not applicable. */
	Option<size_t> GetLastChild  (size_t a_Tag) const { return m_Tags[a_Tag].m_LastChild; }
	
	/** Returns the next sibling of the specified tag, or none. */
	Option<size_t> GetNextSibling(size_t a_Tag) const { return m_Tags[a_Tag].m_NextSibling; }
	
	/** Returns the previous sibling of the specified tag, or none if none. */
	Option<size_t> GetPrevSibling(size_t a_Tag) const { return m_Tags[a_Tag].m_PrevSibling; }
	
	/** Returns the length of the tag's data, in bytes.
	Not valid for Compound or List tags! */
	size_t GetDataLength (size_t a_Tag) const
	{
		ASSERT(m_Tags[a_Tag].m_Type != TAG_List);
		ASSERT(m_Tags[a_Tag].m_Type != TAG_Compound);
		return m_Tags[a_Tag].m_DataLength;
	}

	/** Returns the data stored in this tag.
	Not valid for Compound or List tags! */
	const Byte * GetData(size_t a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type != TAG_List);
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type != TAG_Compound);
		return m_Data + m_Tags[static_cast<size_t>(a_Tag)].m_DataStart;
	}
	
	/** Returns the direct child tag of the specified name, or none if no such tag. */
	Option<size_t> FindChildByName(size_t a_Tag, const AString & a_Name) const

	{
		return FindChildByName(a_Tag, a_Name.c_str(), a_Name.length());
	}
	
	/** Returns the direct child tag of the specified name, or none if no such tag. */
	Option<size_t> FindChildByName(size_t a_Tag, const char * a_Name, size_t a_NameLength = 0) const;

	/** Returns the child tag of the specified path (Name1 / Name2 / Name3...), or none if no such tag. */
	Option<size_t> FindTagByPath(size_t a_Tag, const AString & a_Path) const;
	
	eTagType GetType(size_t a_Tag) const { return m_Tags[static_cast<size_t>(a_Tag)].m_Type; }
	
	/** Returns the children type for a List tag; undefined on other tags. If list empty, returns TAG_End. */
	eTagType GetChildrenType(size_t a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_List);
		return m_Tags[static_cast<size_t>(a_Tag)].m_FirstChild.To<eTagType>([this](size_t FirstChildValue) { return m_Tags[FirstChildValue].m_Type; }, TAG_End);
	}
	
	/** Returns the value stored in a Byte tag. Not valid for any other tag type. */
	inline unsigned char GetByte(size_t a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_Byte);
		return static_cast<unsigned char>(m_Data[static_cast<size_t>(m_Tags[static_cast<size_t>(a_Tag)].m_DataStart)]);
	}
	
	/** Returns the value stored in a Short tag. Not valid for any other tag type. */
	inline Int16 GetShort(size_t a_Tag) const
	{
		ASSERT(m_Tags[a_Tag].m_Type == TAG_Short);
		return GetBEShort(m_Data + m_Tags[a_Tag].m_DataStart);
	}

	/** Returns the value stored in an Int tag. Not valid for any other tag type. */
	inline Int32 GetInt(size_t a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_Int);
		return GetBEInt(m_Data + m_Tags[static_cast<size_t>(a_Tag)].m_DataStart);
	}

	/** Returns the value stored in a Long tag. Not valid for any other tag type. */
	inline Int64 GetLong(int a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_Long);
		return NetworkToHostLong8(m_Data + m_Tags[static_cast<size_t>(a_Tag)].m_DataStart);
	}

	/** Returns the value stored in a Float tag. Not valid for any other tag type. */
	inline float GetFloat(int a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_Float);
		
		// Cause a compile-time error if sizeof(float) != 4
		// If your platform produces a compiler error here, you'll need to add code that manually decodes 32-bit floats
		char Check1[5 - sizeof(float)];  // Fails if sizeof(float) > 4
		char Check2[sizeof(float) - 3];  // Fails if sizeof(float) < 4
		UNUSED_VAR(Check1);
		UNUSED_VAR(Check2);
		
		Int32 i = GetBEInt(m_Data + m_Tags[static_cast<size_t>(a_Tag)].m_DataStart);
		float f;
		memcpy(&f, &i, sizeof(f));
		return f;
	}
	
	/** Returns the value stored in a Double tag. Not valid for any other tag type. */
	inline double GetDouble(int a_Tag) const
	{
		// Cause a compile-time error if sizeof(double) != 8
		// If your platform produces a compiler error here, you'll need to add code that manually decodes 64-bit doubles
		char Check1[9 - sizeof(double)];  // Fails if sizeof(double) > 8
		char Check2[sizeof(double) - 7];  // Fails if sizeof(double) < 8
		UNUSED_VAR(Check1);
		UNUSED_VAR(Check2);

		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_Double);
		return NetworkToHostDouble8(m_Data + m_Tags[static_cast<size_t>(a_Tag)].m_DataStart);
	}
	
	/** Returns the value stored in a String tag. Not valid for any other tag type. */
	inline AString GetString(size_t a_Tag) const
	{
		ASSERT(m_Tags[static_cast<size_t>(a_Tag)].m_Type == TAG_String);
		size_t length = m_Tags[static_cast<size_t>(a_Tag)].m_DataLength;
		const Byte * DataStart = m_Data + m_Tags[a_Tag].m_DataStart;
		AString res(length, 0);
		std::transform(DataStart, DataStart + length, res.begin(), [](Byte c){ return static_cast<char>(c); });
		return res;
	}
	
	/** Returns the tag's name. For tags that are not named, returns an empty string. */
	inline AString GetName(size_t a_Tag) const
	{
		size_t length = m_Tags[a_Tag].m_NameLength;
		const Byte * NameStart = m_Data + m_Tags[a_Tag].m_NameStart;
		AString res(length, 0);
		std::transform(NameStart, NameStart + length, res.begin(), [](Byte c) { return static_cast<char>(c); });
		return res;
	}
	
protected:
	const Byte *             m_Data;
	size_t                   m_Length;
	std::vector<cFastNBTTag> m_Tags;
	bool                     m_IsValid;  // True if parsing succeeded

	// Used while parsing:
	size_t m_Pos;

	bool Parse(void);
	bool ReadString(size_t & a_StringStart, size_t & a_StringLen);  // Reads a simple string (2 bytes length + data), sets the string descriptors
	bool ReadCompound(void);  // Reads the latest tag as a compound
	bool ReadList(eTagType a_ChildrenType);  // Reads the latest tag as a list of items of type a_ChildrenType
	bool ReadTag(void);       // Reads the latest tag, depending on its m_Type setting
} ;





class cFastNBTWriter
{
public:
	cFastNBTWriter(const AString & a_RootTagName = "");
	
	void BeginCompound(const AString & a_Name);
	void EndCompound(void);
	
	void BeginList(const AString & a_Name, eTagType a_ChildrenType);
	void EndList(void);
	
	void AddByte     (const AString & a_Name, unsigned char a_Value);
	void AddShort    (const AString & a_Name, Int16 a_Value);
	void AddInt      (const AString & a_Name, Int32 a_Value);
	void AddLong     (const AString & a_Name, Int64 a_Value);
	void AddFloat    (const AString & a_Name, float a_Value);
	void AddDouble   (const AString & a_Name, double a_Value);
	void AddString   (const AString & a_Name, const AString & a_Value);
	void AddByteArray(const AString & a_Name, const Byte * a_Value, size_t a_NumElements);
	void AddIntArray (const AString & a_Name, const int *  a_Value, size_t a_NumElements);

	void AddByteArray(const AString & a_Name, const std::basic_string<Byte> & a_Value)
	{
		AddByteArray(a_Name, a_Value.data(), a_Value.size());
	}
	
	const std::basic_string<Byte> & GetResult(void) const {return m_Result; }
	
	void Finish(void);
	
protected:

	struct sParent
	{
		int m_Type;   // TAG_Compound or TAG_List
		size_t m_Pos; // for TAG_List, the position of the list count
		int m_Count;  // for TAG_List, the element count
		eTagType m_ItemType;  // for TAG_List, the element type
	} ;
	
	static const int MAX_STACK = 50;  // Highly doubtful that an NBT would be constructed this many levels deep
	
	// These two fields emulate a stack. A raw array is used due to speed issues - no reallocations are allowed.
	sParent m_Stack[MAX_STACK];
	int     m_CurrentStack;
	
	std::basic_string<Byte> m_Result;
	
	bool IsStackTopCompound(void) const { return (m_Stack[m_CurrentStack].m_Type == TAG_Compound); }
	
	void WriteString(const char * a_Data, UInt16 a_Length);
	
	inline void TagCommon(const AString & a_Name, eTagType a_Type)
	{
		// If we're directly inside a list, check that the list is of the correct type:
		ASSERT((m_Stack[m_CurrentStack].m_Type != TAG_List) || (m_Stack[m_CurrentStack].m_ItemType == a_Type));
		
		if (IsStackTopCompound())
		{
			// Compound: add the type and name:
			m_Result.push_back(static_cast<Byte>(a_Type));
			WriteString(a_Name.c_str(), static_cast<UInt16>(a_Name.length()));
		}
		else
		{
			// List: add to the counter
			m_Stack[m_CurrentStack].m_Count++;
		}
	}
} ;