DefSave - Easy Persistent Save/Load

This should still be tested more before used in real projects. I’ll be doing so in near future.

Some features I plan to add are automatic backup system (auto backup multiple save files and keep loading older versions until one works) / version depreciation (if save data version is older than current depreciated version then it’s cleared and set to default) / basic encryption or just a little more obfuscation.

8 Likes

Cranking out the assets! Great job @Pkeod!

2 Likes

I’m adding basic key based XOR obfuscation now. Only works with ASCII right now but I’ll be adding UTF-8 support soon.

For encryption, going to go with AES.

Turning on obfuscation and encryption will both be options. Neither will protect against people determined to get into or modify the files but it will prevent most normal people from casually modifying even more so. You don’t want to store sensitive info in the files either it will just be an option for devs who don’t want their saved games to be modified so easily.

1 Like

Using zlib.inflate() and zlib.deflate() for the individual keys and values would probably work as well.

2 Likes

DefSave now has a Profile extension.

5 Likes

I plan to use this in my game.

Do you think saving on mobile devices is fast enough to do it on the fly in an action packed game?
In my game, you can deploy ‘smart bombs’ which have a very limited quantity. Would calling defsave.save mid-fight be feasible, or do I risk running into slight file system waits doing so?

1 Like

That depends entirely on how much data you are saving. Your best bet is to test and see if there is an impact. Nothing DefSave does should have more of an impact over using the features it uses directly, it is purely a convenience tool.

If you are saving small changes in large files then it would be better to save those small changes in their own files if possible.

DefSave’s concept of “files” internally puts everything into one big blob if I remember right so you would want to save more often saves into smaller instances of DefSave probably. I’ll read the source in a bit to check… “Files” should be separate in DefSave as far as IO goes so you should only need one instance of DefSave for multiple files.

If you want absolute fastest performance you would need to make a custom solution for your data types with low level IO.

I don’t think built in file IO is async, may be worth someone making a native extension which can do that.

tl;dr test and see if there are issues. With flash memory it should be too fast to notice for small data even with no async IO.

3 Likes

I tried this using my Samsung Galaxy S7 and it works perfectly to immediately at firing off my smart bomb save this onto the DefSave profile, to flash memory. No visible hiccup whatsoever.

I wonder if there’s a risk of that for older devices? Probably not, is my guess.

2 Likes

There is a breaking change in v.1.2.0 with get() which now returns nil on a nil key value instead of returning an empty table. This makes more sense to me now. A version 1.1.0 is still available with the old behavior if needed.

Apparently it was nil a long time ago then was set to returning an empty table for some reason. I don’t remember why or if that was even necessary. For anyone who uses this please let me know if there are issues. :thinking:

4 Likes

Imho, I think a breaking change warrants a “bigger bump” in version number than a minor version?

4 Likes

About a week ago I had to spend quite a lot of time debugging the save/load in Fates of Ort because I wanted/expected nil instead of an empty table… So I guess what I’m saying is I’m okay with the change :laughing:

4 Likes

You are right, fixed! :slight_smile:

1 Like

Hello @Pkeod

I’m using DefSave. I couldn’t find the location of the saved file in Android…

It works just fine, but I couldn’t find the location of the saved file! I launched a search on my mobile but nothing (I don’t have sd-card and not using any cloud service), I checked carefully on “Android/data/com.”: nothing.

It has to be saved somewhere because it’s working (my app keeps track of unlocked levels after restart). Any idea why I’m not able to find it?

Here’s the summary of the code I’m using:

SAVED = {}
	--- LOAD FILE ---
	defsave.appname = "golf_tour_v1"
	defsave.load("saved")
	SAVED.level_info	= defsave.get("saved", "level_info")
	if SAVED.level_info == nil then 
		SAVED.level_info = {}
		print ("SAVED: first time playing the game")
		...
		defsave.set("saved", "level_info", SAVED.level_info)
		defsave.save_all()
	end

By the way, the same code works on Windows with the location as usual : “C:\Users…\AppData\Roaming\golf_tour_v1”

If you run a debug build on the device and connect to it from the editor and then print the path of the save data it should be able to tell you where it is saving. I don’t know if your phone being rooted or not impacting how easy it would be to get that data. You might need to do something like

adb shell "run-as com.your.app sh -c 'cat /data/data/com.your.app/files/save_data'" > ./save_data
pause
2 Likes

How can I check if the encryption and obfuscation is working properly?
And what to put in the key fields, do I just generate 256-bit encryption keys?

Afaik the encryption is not implemented at all :confused: Only obfuscation - I have some examples, but on other machine, will try to get back to you tomorrow. I once tried to use it, so I had in mind there wasn’t encryption.

EDIT: Yes, on Github there is no encryption implemented in defsave.lua

EDIT2:
A very simple encryption function could look like this:

function encrypt(input, key)
    local result = ""
    for i = 1, input:len() do
        local char = input:sub(i, i)
        local key_char = key:sub(i % key:len() + 1, i % key:len() + 1)
        local char_code = char:byte()
        local key_code = key_char:byte()
        local encrypted_code = char_code + key_code
        result = result .. string.char(encrypted_code % 256)
    end
    return result
end

It adds the correspoding character of the key. So to decrypt it back you could simply replace the + with - in line local encrypted_code = char_code - key_code

This is a very simple approach, but if you don’t want something more finesse, it could be enough :smiley:

If you want something more secure, we would need to look for some real world encryption methods, I bet there are some written in Lua to easily adapt here.

I think even obfuscation function in defsave looks better than this simple example :sweat_smile:

1 Like

I have a note to add encryption to https://github.com/defold/extension-crypt/blob/master/crypt/src/crypt.cpp. Specifically dmCrypt::Encrypt and Decrypt: API reference (dmCrypt)

A good set of pure Lua encryption can be found in lua-lockbox: https://github.com/somesocks/lua-lockbox

4 Likes

How should I store securely players data like high scores on Android/iOS games?

The question is how important it is to store it securely? Are you creating a multiplayer game where players can gain an advantage by hacking their scores? If that is not the care then why not save it as clear text?

One option is to use Google Play Game Services on Android and Apple Game Center on iOS to store scores, achievements etc

1 Like

Challenging singleplayer game with leaderboards, achievements etc. Maybe storing game settings locally and use Google/Apple cloud save for player score etc?