summaryrefslogblamecommitdiffstats
path: root/src/WorldStorage/SchematicFileSerializer.cpp
blob: e07d462dfe49e800d8b5b26ef5f9a8b1cea77c24 (plain) (tree)
1
2
3
4
5
6
7
8
9
 



                                                                                                                            



                               
                                    
                                 
                         



 
 






                                      




                                                                                                                     






















                                                                                                                  
                                                                                

                            
                                                                                                          

















                                                                                                                         
                                                                                                                  





                                                                                              
                                                      

 




 
                                                                                                                 
 
                               
                                              
                                                                                                        
         

                                                                           
         

                         
                                      
                           
         

                                                                                     
         












                                                                                                              
         

                                                                                                                                   
         




                                                       
                                                                                                  

                             
                             
         
                                                                                               




                             




 
                                                                                                     









                                                                                               
                                                                    




                                                                                               
                    





 
                                                                                                 
 

                                                                                            
         
                                                                           





                                                                                                                                                    


                                                                       
            



                                                                                       


                                                                                                



                                                                                    



                             


                                                      
                                                                                                             













                                                                                                     

                                                                                                                                      
        











                                                                                              
                                                 


            
                                                                                                                

         
                                          

                                                            
         

                                                                                                                    
                  
                                                                
         
                                                                                   


                            

                                                                    
                 

                                                                                                                            
                          
                                                                        
                 
                                                                                           





                    





                                                                                    


                                                                            


                                               
                                                                                                                                     







                                                                          
                                                                                                                                   






                                                                        


                                                             
 












                                                       

// SchematicFileSerializer.cpp

// Implements the cSchematicFileSerializer class representing the interface to load and save cBlockArea to a .schematic file

#include "Globals.h"

#include "OSSupport/GZipFile.h"
#include "FastNBT.h"
#include "SchematicFileSerializer.h"
#include "../StringCompression.h"
#include "../SelfTests.h"





#ifdef SELF_TEST

static class cSchematicStringSelfTest
{
public:
	cSchematicStringSelfTest(void)
	{
		cSelfTests::Get().Register(cSelfTests::SelfTestFunction(&Test), "Schematic-to-string serialization");
	}

	static void Test(void)
	{
		cBlockArea ba;
		ba.Create(21, 256, 21);
		ba.RelLine(0, 0, 0, 9, 8, 7, cBlockArea::baTypes | cBlockArea::baMetas, E_BLOCK_WOODEN_STAIRS, 1);
		AString Schematic;
		if (!cSchematicFileSerializer::SaveToSchematicString(ba, Schematic))
		{
			assert_test(!"Schematic failed to save!");
		}
		cBlockArea ba2;
		if (!cSchematicFileSerializer::LoadFromSchematicString(ba2, Schematic))
		{
			assert_test(!"Schematic failed to load!");
		}
	}
} g_SelfTest;

#endif






////////////////////////////////////////////////////////////////////////////////
// cSchematicFileSerializer:

bool cSchematicFileSerializer::LoadFromSchematicFile(cBlockArea & a_BlockArea, const AString & a_FileName)
{
	// Un-GZip the contents:
	AString Contents;
	cGZipFile File;
	if (!File.Open(a_FileName, cGZipFile::fmRead))
	{
		LOG("Cannot open the schematic file \"%s\".", a_FileName.c_str());
		return false;
	}
	int NumBytesRead = File.ReadRestOfFile(Contents);
	if (NumBytesRead < 0)
	{
		LOG("Cannot read GZipped data in the schematic file \"%s\", error %d", a_FileName.c_str(), NumBytesRead);
		return false;
	}
	File.Close();
	
	// Parse the NBT:
	cParsedNBT NBT(std::basic_string<Byte>(reinterpret_cast<const Byte *>(Contents.data()), Contents.size()));
	if (!NBT.IsValid())
	{
		LOG("Cannot parse the NBT in the schematic file \"%s\".", a_FileName.c_str());
		return false;
	}
	
	return LoadFromSchematicNBT(a_BlockArea, NBT);
}






bool cSchematicFileSerializer::LoadFromSchematicString(cBlockArea & a_BlockArea, const AString & a_SchematicData)
{
	// Uncompress the data:
	std::basic_string<Byte> UngzippedData;
	if (UncompressStringGZIP(a_SchematicData.data(), a_SchematicData.size(), UngzippedData) != Z_OK)
	{
		LOG("%s: Cannot unGZip the schematic data.", __FUNCTION__);
		return false;
	}

	// Parse the NBT:
	cParsedNBT NBT(UngzippedData);
	if (!NBT.IsValid())
	{
		LOG("%s: Cannot parse the NBT in the schematic data.", __FUNCTION__);
		return false;
	}
	
	return LoadFromSchematicNBT(a_BlockArea, NBT);
}





bool cSchematicFileSerializer::SaveToSchematicFile(const cBlockArea & a_BlockArea, const AString & a_FileName)
{
	// Serialize into NBT data:
	AString NBT = SaveToSchematicNBT(a_BlockArea);
	if (NBT.empty())
	{
		LOG("%s: Cannot serialize the area into an NBT representation for file \"%s\".", __FUNCTION__, a_FileName.c_str());
		return false;
	}
	
	// Save to file
	cGZipFile File;
	if (!File.Open(a_FileName, cGZipFile::fmWrite))
	{
		LOG("%s: Cannot open file \"%s\" for writing.", __FUNCTION__, a_FileName.c_str());
		return false;
	}
	if (!File.Write(NBT))
	{
		LOG("%s: Cannot write data to file \"%s\".", __FUNCTION__, a_FileName.c_str());
		return false;
	}
	return true;
}






bool cSchematicFileSerializer::SaveToSchematicString(const cBlockArea & a_BlockArea, AString & a_Out)
{
	// Serialize into NBT data:
	AString NBT = SaveToSchematicNBT(a_BlockArea);
	if (NBT.empty())
	{
		LOG("%s: Cannot serialize the area into an NBT representation.", __FUNCTION__);
		return false;
	}
	
	// Gzip the data:
	int res = CompressStringGZIP(NBT.data(), NBT.size(), a_Out);
	if (res != Z_OK)
	{
		LOG("%s: Cannot Gzip the area data NBT representation: %d", __FUNCTION__, res);
		return false;
	}
	return true;
}





bool cSchematicFileSerializer::LoadFromSchematicNBT(cBlockArea & a_BlockArea, cParsedNBT & a_NBT)
{
	auto TMaterials = a_NBT.FindChildByName(a_NBT.GetRoot(), "Materials");
	if ((TMaterials.HasValue()) && (a_NBT.GetType(TMaterials.GetValue()) == TAG_String))
	{
		AString Materials = a_NBT.GetString(TMaterials.GetValue());
		if (Materials.compare("Alpha") != 0)
		{
			LOG("Materials tag is present and \"%s\" instead of \"Alpha\". Possibly a wrong-format schematic file.", Materials.c_str());
			return false;
		}
	}
	auto TSizeX = a_NBT.FindChildByName(a_NBT.GetRoot(), "Width");
	auto TSizeY = a_NBT.FindChildByName(a_NBT.GetRoot(), "Height");
	auto TSizeZ = a_NBT.FindChildByName(a_NBT.GetRoot(), "Length");
	if (
		(!TSizeX.HasValue()) || (!TSizeY.HasValue()) || (!TSizeZ.HasValue()) ||
		(a_NBT.GetType(TSizeX.GetValue()) != TAG_Short) ||
		(a_NBT.GetType(TSizeY.GetValue()) != TAG_Short) ||
		(a_NBT.GetType(TSizeZ.GetValue()) != TAG_Short)
	)
	{
		LOG("Dimensions are missing from the schematic file (%d, %d, %d), (%d, %d, %d)",
			TSizeX.GetValue(), TSizeY.GetValue(), TSizeZ.GetValue(),
			(TSizeX.HasValue()) ? a_NBT.GetType(TSizeX.GetValue()) : -1,
			(TSizeY.HasValue()) ? a_NBT.GetType(TSizeY.GetValue()) : -1,
			(TSizeZ.HasValue()) ? a_NBT.GetType(TSizeZ.GetValue()) : -1
		);
		return false;
	}
	
	int SizeX = a_NBT.GetShort(TSizeX.GetValue());
	int SizeY = a_NBT.GetShort(TSizeY.GetValue());
	int SizeZ = a_NBT.GetShort(TSizeZ.GetValue());
	if ((SizeX < 1) || (SizeX > 65535) || (SizeY < 1) || (SizeY > 256) || (SizeZ < 1) || (SizeZ > 65535))
	{
		LOG("Dimensions are invalid in the schematic file: %d, %d, %d", SizeX, SizeY, SizeZ);
		return false;
	}
	
	int TBlockTypes = a_NBT.FindChildByName(a_NBT.GetRoot(), "Blocks");
	int TBlockMetas = a_NBT.FindChildByName(a_NBT.GetRoot(), "Data");
	if ((TBlockTypes < 0) || (a_NBT.GetType(TBlockTypes) != TAG_ByteArray))
	{
		LOG("BlockTypes are invalid in the schematic file: %d", TBlockTypes);
		return false;
	}
	bool AreMetasPresent = (TBlockMetas > 0) && (a_NBT.GetType(TBlockMetas) == TAG_ByteArray);
	
	a_BlockArea.Clear();
	a_BlockArea.SetSize(SizeX, SizeY, SizeZ, AreMetasPresent ? (cBlockArea::baTypes | cBlockArea::baMetas) : cBlockArea::baTypes);
	
	int TOffsetX = a_NBT.FindChildByName(a_NBT.GetRoot(), "WEOffsetX");
	int TOffsetY = a_NBT.FindChildByName(a_NBT.GetRoot(), "WEOffsetY");
	int TOffsetZ = a_NBT.FindChildByName(a_NBT.GetRoot(), "WEOffsetZ");
	
	if (
		(TOffsetX < 0) || (TOffsetY < 0) || (TOffsetZ < 0) ||
		(a_NBT.GetType(TOffsetX) != TAG_Int) ||
		(a_NBT.GetType(TOffsetY) != TAG_Int) ||
		(a_NBT.GetType(TOffsetZ) != TAG_Int)
	)
	{
		// Not every schematic file has an offset, so we shoudn't give a warn message.
		a_BlockArea.SetWEOffset(0, 0, 0);
	}
	else
	{
		a_BlockArea.SetWEOffset(a_NBT.GetInt(TOffsetX), a_NBT.GetInt(TOffsetY), a_NBT.GetInt(TOffsetZ));
	}

	// Copy the block types and metas:
	size_t NumTypeBytes = a_BlockArea.GetBlockCount();
	if (a_NBT.GetDataLength(TBlockTypes) < NumTypeBytes)
	{
		LOG("BlockTypes truncated in the schematic file (exp %u, got %u bytes). Loading partial.",
			static_cast<unsigned>(NumTypeBytes), static_cast<unsigned>(a_NBT.GetDataLength(TBlockTypes))
		);
		NumTypeBytes = a_NBT.GetDataLength(TBlockTypes);
	}
	memcpy(a_BlockArea.m_BlockTypes, a_NBT.GetData(TBlockTypes), NumTypeBytes);
	
	if (AreMetasPresent)
	{
		size_t NumMetaBytes = a_BlockArea.GetBlockCount();
		if (a_NBT.GetDataLength(TBlockMetas) < NumMetaBytes)
		{
			LOG("BlockMetas truncated in the schematic file (exp %u, got %u bytes). Loading partial.",
				static_cast<unsigned>(NumMetaBytes), static_cast<unsigned>(a_NBT.GetDataLength(TBlockMetas))
			);
			NumMetaBytes = a_NBT.GetDataLength(TBlockMetas);
		}
		memcpy(a_BlockArea.m_BlockMetas, a_NBT.GetData(TBlockMetas), NumMetaBytes);
	}
	
	return true;
}





AString cSchematicFileSerializer::SaveToSchematicNBT(const cBlockArea & a_BlockArea)
{
	cFastNBTWriter Writer("Schematic");
	Writer.AddShort("Width",  static_cast<Int16>(a_BlockArea.m_Size.x));
	Writer.AddShort("Height", static_cast<Int16>(a_BlockArea.m_Size.y));
	Writer.AddShort("Length", static_cast<Int16>(a_BlockArea.m_Size.z));
	Writer.AddString("Materials", "Alpha");
	if (a_BlockArea.HasBlockTypes())
	{
		Writer.AddByteArray("Blocks", reinterpret_cast<const char *>(a_BlockArea.m_BlockTypes), a_BlockArea.GetBlockCount());
	}
	else
	{
		AString Dummy(a_BlockArea.GetBlockCount(), 0);
		Writer.AddByteArray("Blocks", Dummy.data(), Dummy.size());
	}
	if (a_BlockArea.HasBlockMetas())
	{
		Writer.AddByteArray("Data", reinterpret_cast<const char *>(a_BlockArea.m_BlockMetas), a_BlockArea.GetBlockCount());
	}
	else
	{
		AString Dummy(a_BlockArea.GetBlockCount(), 0);
		Writer.AddByteArray("Data", Dummy.data(), Dummy.size());
	}
	
	Writer.AddInt("WEOffsetX", a_BlockArea.m_WEOffset.x);
	Writer.AddInt("WEOffsetY", a_BlockArea.m_WEOffset.y);
	Writer.AddInt("WEOffsetZ", a_BlockArea.m_WEOffset.z);

	// TODO: Save entities and block entities
	Writer.BeginList("Entities", TAG_Compound);
	Writer.EndList();
	Writer.BeginList("TileEntities", TAG_Compound);
	Writer.EndList();
	Writer.Finish();
	
	return Writer.GetResult();
}