[UE4 Code Wiki] Ex: Custom Save Systems, Write Any Data to Compressed Binary Files

https://www.mediafire.com/convkey/7d2b/fr75kbso2kkazru6g.jpg

Dear Community,

I have been hard at work making UE4 C++ Code tutorials!

I have added 28 pages of entirely code-focused tutorials, including both .h and .cpp,

to the UE4 Wiki Code Page!


**Tutorial Sample**

Here is most of a tutorial that is the kind of code and information I am posting on the UE4 Wiki Code Page! 

**Custom Save System To Binary Files**

I've been planning to do this tutorial for months,

I will try to keep it as succinct as possible!

**Using what I explain in this tutorial you can write your own custom save systems wherein you**



```

- write out literally any game-related data you want
- read back in this data any time you want, from hard-disk
- compress (ZLIB) these files to minimize usage of the end-user's computer hard disk space

```



I've already been writing out and loading back in levels that are zipped binary data files for my in-game editor.

I am currently able to save literally any custom data I want, including dynamic arrays, for all my custom classes.

I've developed a streamlined method for doing all this by overloading a specific UE4 C++ operator, so yay thank you thank you Epic for C++ access!

Two Levels of Conversions

When you want to save custom variable data for your custom save system,

there are TWO major steps involved

Step 1 = variable format → binary array (serialized by Archive class of UE4)
Step 2 = Binary array → hard disk

These steps are then done in reverse to read back in data from hard disk.


**Binary Array = TArray<uint8>**

Binary data is represented in a very UE4 C++ friendly looking way as a dynamic array of uint8.

So any time you see TArray<uint8> in my code in this tutorial, that literally means "Binary Array" from UE4 C++ standpoint.

//Abbreviated to fit the new forum character limit

See UE4 Wiki Code Page


**Archive.h and ArchiveBase.h**

See Archive.h and ArchiveBase.h for all the info you need about getting from your varibles and custom class data to binary format (serialized data)

FileManager.h

All the functions you need to

  • create directories
  • delete directories
  • create files
  • delete files
  • get a listing of all files in a given path
  • get al isting of all folders in a given path
  • get the age of a file

and more

are found in FileManager.h

You access these functions from anywhere using



if(GFileManager) GFileManager->TheFunction()



**BufferArchive**

The buffer archive is both a binary array (TArray<uint8>), and a MemoryWriter

**Archive.h**



```


/**
 * Buffer archiver.
 */
class FBufferArchive : public FMemoryWriter, public TArray<uint8>
{


```



Because of this multiple-inheritance, the BufferArchive is my preferred way to write data to binary file.

As my code will show, because the GFileManager wants to receive a TArray<uint8>, not a MemoryArchive.

Review my Steps 1 and 2 to see why this is such an awesome class. :)

Thanks UE4 Devs!

FMemoryReader

To read Data back from a binary array, that is retrieved by the FileManager, you need a MemoryReader

Archive.h



/**
 * Archive for reading arbitrary data from the specified memory location
 */
class FMemoryReader : public FMemoryArchive
{
public:



The << Operator

The BufferArchive/Binary Array needs to retrieve your game's variable data, how do you tell it what you want stored as binary?

The << operator!


**Variable -> Binary**

Here's how you would put an FVector into a BufferArchive to then be saved to hard disk.

//in player controller class


```


FBufferArchive ToBinary;
ToBinary << GetPawn()->GetActorLocation(); //save player location to hard disk

//save ToBinary to hard disk using File Manager, 
//see complete code samples below


```



**Binary -> Variable**

Here's how you would retrieve an FVector from a TArray<uint8> as retrieved by GFileManager.

/

```

/TheBinaryArray was already obtained from FileManager, 
//see code below for full examples

//need to supply a variable to be filled with the data
FVector ToBeFilledWithData;

FMemoryReader Ar = FMemoryReader(TheBinaryArray, true); //true, free data after done
Ar.Seek(0); //make sure we at the beginning

Ar << ToBeFilledWithData;

```



The Hardest Concept of UE4 C++ Custom Save System

Please notice something in my code above!!!

Compare these two lines



ToBinary << GetPawn()->GetActorLocation();
Ar << ToBeFilledWithData;


The hardest concept for me about the UE4 archive system was the fact that the << operator could mean

  • getting data out of the archive and putting it into the variable

or

  • putting data from the variable into the archived binary format

depending on the context!

So that’s why I recommend you name your BufferArchive something like ToBinary,

and your MemoryReader something totally different,

you can only discern the difference between writing to binary and reading from binary

based on the context as you show it in your code, as the << operator will tell you nothing from a simple glance.


**Writing Your Function to Be Two-Way**

The critical advantage of this system though is that you can write a single function that works both ways.

So you can write a single function that loads data from file, or saves to file.

But why would you want this?

Because:
**===================================================
The order of how you write out data to binary file must be the EXACT SAME ORDER that you read it back in!
===================================================**


The computer does not have any way of knowing, nor does UE4, what the correct order of variable data should be.

You are responsible for telling the computer and UE4 to read data back in in the same order it was written out to file.

Thus, having a single function that both reads and writes, using the multi-purpose << operator, is the safest thing you can do to ensure consistency of writing/reading binary data.

**==========================
Two-Way Save System Function
==========================**



```


**.h**

//FArchive is shared base class for FBufferArchive and FMemoryReader
void SaveLoadData(FArchive& Ar, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);

**.cpp**

//I am using controller class for convenience, use any class you want

//SaveLoadData
void YourControllerClass::SaveLoadData(FArchive& Ar,
  int32& SaveDataInt32,
  FVector& SaveDataVector,
  TArray<FRotator>& SaveDataRotatorArray
)
{
	Ar << SaveDataInt32;
	Ar << SaveDataVector;
	Ar << SaveDataRotatorArray;
}


```



**Saving**

Make a BufferArchive and pass it in, it is a Binary Array and also an FArchive



```


FBufferArchive ToBinary;
SaveLoadData(ToBinary,NumGemsCollected,PlayerLocation,ArrayOfRotationsOfTheStars);
//save the binary array / FBufferArchive to hard disk, see below


```



**Loading**

//TheBinaryArray already retrieved from file, see full code sapmle


```


FMemoryReader FromBinary = FMemoryReader(TheBinaryArray, true); //true, free data after done
FromBinary.Seek(0);
SaveLoadData(FromBinary,NumGemsCollected,PlayerLocation,ArrayOfRotationsOfTheStars);


```



**Summary**

Use this setup to avoid crashes due to reading data not in same order that you wrote it to disk!

this two way functionality of UE4 << operator saves the day!

Thanks Epic Devs!

Saving Compressed Binary Files



bool ControllerClass::SaveGameDataToFileCompressed(const FString& FullFilePath, 
	int32& SaveDataInt32,
	FVector& SaveDataVector,
	TArray<FRotator>& SaveDataRotatorArray
){
	FBufferArchive ToBinary;
	SaveLoadData(ToBinary,NumGemsCollected,PlayerLocation,ArrayOfRotationsOfTheStars); 
	
	//Pre Compressed Size
	ClientMessage("~ PreCompressed Size ~");
	ClientMessage(FString::FromInt(ToBinary.Num()));
	
	//~~~
	
	//~~~ Compress File ~~~
	//tmp compressed data array
	TArray<uint8> CompressedData;
	FArchiveSaveCompressedProxy Compressor = 
		FArchiveSaveCompressedProxy(CompressedData, ECompressionFlags::COMPRESS_ZLIB);
	
	//Send entire binary array/archive to compressor
	Compressor << ToBinary;
	
	//send archive serialized data to binary array
	Compressor.Flush();
	
	//~~~
	
	//Compressed Size
	ClientMessage("~ Compressed Size ~");
	ClientMessage(FString::FromInt(CompressedData.Num()));
	
	
	if (!GFileManager) return false;
	
	//vibes to file, return successful or not
	if (FFileHelper::SaveArrayToFile(CompressedData, * FullFilePath)) 
	{
		//~~~ Free Binary Arrays ~~~
		Compressor.FlushCache();
		CompressedData.Empty();
		
		ToBinary.FlushCache();
		ToBinary.Empty();
		
		//~~~ Close Buffer ~~~
		ToBinary.Close();
		
		ClientMessage("File Save Success!");
		
		return true;
		//~~~~~~~~~~~~~~~
	}
	else
	{
		//~~~ Free Binary Arrays ~~~
                Compressor.FlushCache();
		CompressedData.Empty();

		ToBinary.FlushCache();
		ToBinary.Empty();
		
		//~~~ Close Buffer ~~~
		ToBinary.Close();
		
		ClientMessage("File Could Not Be Saved!");
		
		return false;
		//~~~~~~~~~~~~~~~
	}
}



**Loading Compressed Binary Files**

See [UE4 Wiki Code Page](https://wiki.unrealengine.com/Category:Code)

**Enjoy!**

Have fun making your very own custom save game system!

♥

Rama

Its possible to save an entire Actor using this method (for example to save a inventory Item)

Couldn’t you just save a series of Ints in Blueprints? Seems like a simpler option if all you need to save is an Inventory System.

What about Actors which have more than “just” an inventory? For example a RPG character with skills, cooldowns and so on?
Would be interesting to know if Rama’s solution up there would be suited for things like that ^^

You can save out literally any data you want to hard disk using my binary save system!

I have an in-game editor for my game, and I am writing out all the properties of the actors that I want to save for very large sets of actors this way!


**Saving Save Game Screenshots To Hard Disk**

For Solus, I save out screen shots of the game taken at the time of saving, compress them to binary file, and load them back as Texture 2d!

The result is that I can display compressed binary file pictures in-game on the PDA, and thus each save slot has a picture associated with it like in AAA games!

:)

Rama

This seems really useful. Thanks!

Can anyone share a little more about how portable reading/saving files is? For example:

  1. If I want to have resource files (say a CSV file or TXT file or something) where should I save these files so that they will be packaged with my game when I build it for lots of different platforms? Should I put it into Content folder?
  2. If I read in a resource file (again a CSV or TXT file or something) from say the Content folder, is this going to work once I build it for lots of other platforms? How do I specify paths appropriately to work on other platforms? For example, I can use C:\MyProject\Content\data.csv as a path on windows and that might work but what will happen if I build my game for the iPad which doesn’t have a C drive?

Thanks,
-X

whats the total size of your memory reader after you pass this to it?

FMemoryReader FromBinary = FMemoryReader(TheBinaryArray, true);

I get a value of 51 after loading my file in the binary array. The file contains a single struct with 4 properties in it.
Its messes up the seek positions.
Anyone got any ideas?

I still not get it, I just want create a node to write a external binary file on disk, this node receive a binary array inside blueprint and save on the disk, and another node to load the same file. I already see the tutorial on UE4 Wiki, but I don’t understand which order I have input all codes.

Hi,
I’m having problems trying to “pass” or copy the data in an FBufferArchive to a variable that is using an external library. The idea is to serialize the save game data into a variable (FBufferArchive) in order to pass it to a variable (Vector<uint8_t>) in the library I’m using to be able to recover that info later and load the save game data using a FMemoryReader.



SaveGame.h

FBufferArchive  ToBinary;

SaveGame->Serialize(ToBinary);

#if  PLATFORM

Vector<uint8_t> SaveData;
*(reinterpret_cast<FBufferArchive*>(SaveData())) = ToBinary; // That doesn't work because I can't use the operator = with a FBufferArchive
...
...

#endif




GameInstance.h

#if  PLATFORM

var inital_state = GetInintialState(); //I'm putting this in that way because I don't know if I can put the real API calls.

if(!initial_state.data.empty()
{
    FBufferArchive RecoverBinaryData;
    RecoverBinaryData = *(reinterpret_cast<FBufferArchive*>(initial_state.data)); // Of course this also doesn't work.

    FMemoryReader FromBinary = FMemoryReader(RecoverBinaryData, true);
    FromBinary.Seek(0);
    SaveGame->Serialize(FromBinary);
}

#endif




I tried everything I could related with the big three (FBufferArchive, FMemoryReader and FArchive) but I can’t assign the info of the bufferarchive to another variable in order to set the API variable…

What am I missing? Any idea of how I could get this work?!

Thanks.

@Rama is it possible you can fill out this forum post with the full article’s info(and maybe update it :wink: )? Couldnt find the full article in the wayback machine since wiki is dead now…

@Oldsiren you can still access the page, from here: https://www.ue4community.wiki/Legacy…d_Binary_Files.

The link you posted is also broken… is there another place I can find it? or maybe @Rama knows?