Turn it on
- Open Project Settings > Plugins > Rolly Save > Protection.
- Tick Protect Saves.
- Press Generate Key. It writes a random key of 64 hex digits into Protection Key and saves it to
Config/DefaultGame.ini. - Keep that key for the life of the game. A save protected with a key the build does not hold never loads.
Each save is then encrypted (AES-256 in counter mode, with a new random nonce per chunk at every save) and signed (HMAC-SHA1 over the whole file, kept in its header field PROT). The work runs on worker threads with the rest of the save’s encoding; with protection off nothing runs and the files carry no extra bytes.
| Setting | Default | What it does |
|---|---|---|
| Protect Saves | off | Protects every save to storage. Memory slots and rewind points never leave memory and are not protected. |
| Protection Key | empty | The key new saves are protected with: 64 hex digits. |
| Previous Protection Keys | empty | Keys saves were protected with before, at most 16. Saves under them load, and their next save uses Protection Key. |
| Protection Key Provider | none | A C++ class whose keys replace the two above (below). |
| Accept Unprotected Saves | on | Saves written without protection, such as those from before Protect Saves was on, load, and their next save protects them. Off: they count as changed outside the game. |
| Changed Saves | Use Other Copy | What a load does with a save changed outside the game (below). |
A setting that cannot be used writes saves unprotected, with Accept Unprotected Saves counted as on, and a setup error in Data Validation of Project Settings and the Message Log: Protect Saves on without a key (NoKey), a key that is not 64 hex digits or is all zeros (BadKey), more than 16 previous keys (TooManyKeys, the first 16 are read), or a provider that gives no keys (ProviderFailed).
A save changed outside the game
Every file Rolly Save writes has two copies (see Check that saves survive a crash). A load checks the newest copy’s checksums, then its signature, before it decodes anything. A copy whose checksums hold and whose signature does not was changed outside the game by a tool that rewrote the checksums, such as a save editor that knows the format:
| Changed Saves | The newest copy changed | The newest copy changed, and the other changed too, damaged or missing |
|---|---|---|
| Use Other Copy | The other copy, the backup, loads. The loading notice says “This save was changed outside the game, so its backup loaded.”, the result has bFromBackup and an Issues line ChangedOutsideGame, and On Save Changed Outside Game fires with Loaded false. |
Nothing loads: status Tampered, “This save was changed outside the game.” A part save of that slot saves nothing, with the same status. |
| Load and Report | The changed copy loads, with the Issues line, and On Save Changed Outside Game fires with Loaded true, so your game can flag the player. | The newest loads, as above. |
The event fires before anything of the save is set: Cancel Load there stops the load, and only On Load Cancelled follows.
Only slot loads report a changed copy. A profile follows Changed Saves the same way, with no notice, no Issues line and no event, so a game cannot flag a profile edited outside it.
A copy whose checksums broke counts as damaged, and the usual backup rules apply, with no Issues line, notice or event of a changed save: a byte changed by hand in a hex editor breaks them, as damage on a failing disk does, and a copy cannot tell the two apart. Either way the edit does not load. The next save writes over the changed copy, protected again.
Saves from before protection
With Accept Unprotected Saves on, the default, saves your players made before an update that turns protection on keep loading, and each is protected at its next save. While it is on, a save editor that knows the format can remove a file’s protection field, and the file then loads as an unprotected save: its data is still encrypted, but nothing checks it any more. Turn it off once those saves are old enough not to matter: an unprotected file then counts as changed.
Change the key
Press Generate Key again. The key it replaces moves to the front of Previous Protection Keys, so saves protected with it keep loading and are protected with the new key at their next save. One press is one undo step. With 16 previous keys already listed, the replaced key goes first and the oldest becomes the 17th, which is no longer read: the setup error TooManyKeys says only the first 16 are read.
Remove a key only once no player’s save has its newest copy under it: a slot not saved since the key was replaced needs that key to load. A slot saved once since keeps only its backup under the old key, and its next save writes over that backup.
A copy protected with a key the build no longer holds is Locked. A slot whose newest copy is Locked does not load and is not saved (status ProtectionKeyMissing, “This save needs a key this game version does not have.”), so that copy is kept, and the Message Log warns once per slot (KeyMissing). A slot whose backup alone is Locked loads its newest copy, and its next save writes over the backup, as every save writes over the backup. Add the key back to Previous Protection Keys to load and save a slot whose newest copy needs it.
Keep the keys in code
Protection Key sits in the cooked config, readable in a packaged game unless your project encrypts its .ini files in the pak. To keep the keys in the game’s binary instead, derive a C++ class from URollySaveProtectionKeyProvider, override GetKeys, and pick the class in Protection Key Provider:
UCLASS()
class UMyGameKeys : public URollySaveProtectionKeyProvider
{
GENERATED_BODY()
public:
virtual bool GetKeys(FString& OutCurrentKey, TArray<FString>& OutPreviousKeys) const override
{
OutCurrentKey = TEXT("<64 hex digits>");
return true;
}
};Rolly Save calls it on the class default object, on the game thread, the first time it protects or reads a save and after Project Settings change. Its keys replace Protection Key and Previous Protection Keys; Protect Saves still turns protection on. A Blueprint class is not used.
Give the provider every key your saves use so far: the key in Protection Key, as its current or a previous key, and each of Previous Protection Keys, or saves protected with them turn Locked. Then clear Protection Key and Previous Protection Keys: a key left there still ships in the cooked config.
Tools and imports
rolly.save.verify <slot>names a changed copy “changed outside the game (tampered)” and a copy under an unknown key “protected with a key this build does not hold (locked)”.- The Save Inspector reads protected saves with the project’s keys, shows the signature as the Protection tag row of a copy’s checks, and writes its edits protected, marked as edited (see Inspect, compare and edit saves).
- A JSON export holds the values in the clear; a JSON import, from your game or the Save Inspector, is stored protected (see Export and import saves as JSON).
- Import Rolly Slot from File and from Bytes refuse a changed file with
Tampered, whatever Changed Saves says, since an imported file has no other copy to stand in for it, and a file under an unknown key withProtectionKeyMissing(see Rename, export and import saves). - The slot index is protected like a save; its entries for each slot hold no signature of their own.
What it stops
Protection stops casual editing. The key ships with the game, so anyone who extracts it can still change a save. The left column holds with Accept Unprotected Saves off; while it is on, a save editor that knows the format can remove the protection field and change the data (above).
| Stops | Does not stop |
|---|---|
| Hex editing of a save: the copy counts as damaged, as it does without protection | Memory editors such as Cheat Engine changing values in play |
| Save editors that know the format but not the key: the copy counts as changed outside the game | Anyone who extracts the key from the config or the binary |
| Loading a copy whose sequence number was forged, or that holds a chunk moved in from another file: it counts as changed | A forged sequence number’s effect on the slot’s numbering: a copy forged to the highest number still stops later saves of that slot |
| Reading the saved data without the key | Reading a save’s header: its title, place, save and play time, picture and metadata stay readable, so slot lists need no key |
| Putting back an older valid copy or file (single-slot runs guard against it, see Play single-slot runs) | |
| Sharing saves between players of the same game, which share the key | |
| Modified game executables |
Saves are not bound to one player’s account: there is no stable account ID without online services, which version 1.1 adds.
C++
URollySaveSubsystem::OnSaveChangedOutsideGame (FRollySaveChangedOutsideGameEvent: the slot and whether the changed copy loaded) is the event; ERollySaveStatus::Tampered and ProtectionKeyMissing are the statuses. URollySaveProtectionKeyProvider (Protection/RollySaveProtectionKeyProvider.h) gives keys from code. For tools of your own, FRollySaveProtection (File/RollySaveProtection.h) holds a key set, and FRollySaveFileWriteOptions::Protection and FRollySaveFileReadOptions::Protection protect and read files; URollySaveProjectSettings::GetProtection gives the project’s.