Gamebuino Save format

Creations

Sorunome

1 year ago

This will explain you how to use the gamebuino save format, the gb.save interface. First, this will explain the simple usage and after that the advanced, more customizable usage with config-gamebuino.h

Save Structure

The savefile is structured in blocks. A block can hold a number or a buffer. By default the savefile has 64 blocks.

Simple number usage

Handling numbers is very easily done:

gb.save.set(0, 42); // saves 42 in block 0
gb.save.set(1, -1337); // saves -1337 in block 1
int i = gb.save.get(0); // fetches block 0, which is 42
int j = gb.save.get(1); // fetches block 0, which is -1337
gb.save.del(0); // deletes block 0

Numbers default to zero (configurable, see advanced usage)

Blob usage

Now, instead of storing a number in a block, you can also store a blob. By default the size of a blob is 32 bytes.

Well, what is a blob? A blob can be literetally anything! You can store strings, buffers, objects or just about anything you'd want to store.

gb.save.set(2, "Foxes are awesome!"); // stores the string "Foxes are awesome!" in block 2

CustomObject obj;
gb.save.set(3, obj); // stores obj

void* buffer = malloc(10);
gb.save.set(4, buffer, 10); // stores a buffer of length 10

if these functions return true setting was possible, if they return false then something went wrong.

Now, how do you fetch those blobs?

char string[32];
gb.save.get(2, string);
gb.display.print(string); // prints block 2 as string, so "Foxes are awesome!"

CustomObject obj;
gb.save.get(3, obj); // fetches the custom object

void* buf = malloc(10);
gb.save.get(4, buf, 10); // fetches up to 10 bytes from block 4

if these blocks don't exist yet the buffer is filled with zeros (configurable, see advanced usage)

Important notice

Please note that after setting it is enforced wether the block is an int or a blob. For example:

// this will error
gb.save.set(0, 42);
gb.save.set(0, "blah"); // error, block is an int!!

// this will also error
gb.save.set(0, 42);
char name[10];
gb.save.get(0, name); // error, block is an int!

// this will work, though
gb.save.set(0, 42);
gb.save.del(0); // we delete the block
gb.save.set(0, "Foxies!");

Floats etc.

You can easily store floats / signed ints etc. as ints by casting them:

gb.save.set(0, (int32_t)3.14159);
float pi = (float)gb.save.get(0);

gb.save.set(1, (int32_t)true);
bool stuff = (bool)gb.save.get(1);


Advanced Usage

The basic idea of advanced usage is to pre-define which block is an int and which one is a blob and to allow custom defaults. For this we need to create an array of the type SaveDefault, passing that then on to gb.save.config:

const SaveDefault savefileDefaults[] = {
  { /* data */ },
  { /* data */ },
};

void setup() {
  gb.begin();

  // set the save defaults
  gb.save.config(savefileDefaults);
}

Now, there are multiple different SaveDefault data things possible. The first argument is always the block number to configure, the second one which type that block must be (SAVETYPE_INT or SAVETYPE_BLOB). After that, it depends on if you use int or blob:

SAVETYPE_INT

As third argument you have to set the default value. Fourth one is always zero. So these all are valid SaveDefault data things:

{ 0, SAVETYPE_INT, 9999, 0 }
{ 1, SAVETYPE_INT, -42, 0 }

SAVETYPE_BLOB

Blobs must have a third and optionally a fourth argument. With only three arguments the third one is the max length of the blob (fourth one must be zero again), with four arguments the third one is the default and the third one the max length. For the default data, you see below how that needs to be set.

The length of a blob can only be up to ~2^32 bytes*!

So these are valid blob constructors:

{ 2, SAVETYPE_BLOB, 20, 0 }
{ 3, SAVETYPE_BLOB, {.ptr="Fox"}, 20 }

Example

Ok, that was a lot to take in. So here is an example taking full use of all the features:

const SaveDefault savefileDefaults[] = {
  { SAVE_PLAYERNAME, SAVETYPE_BLOB, {.ptr="Link"}, 20 },
  { SAVE_HEARTS, SAVETYPE_INT, 3, 0 },
  { SAVE_SOMEBUFFER, SAVETYPE_BLOB, 25, 0 },
};

void setup() {
  gb.begin();

  gb.save.config(savefileDefaults);
}

// somewhere later in the code
char name[20];
gb.save.get(SAVE_PLAYERNAME, name);
gb.display.print(name); // prints "Link"

int hearts = gb.save.get(SAVE_HEARTS); // returns 3


I sure hope this helped you to understand how to use the save API to its full extend!


*: While a blob can be up to ~2^32 bytes large, the total payload may not exceed 2^32-1 bytes. Payload is all blob sizes added together. A blob is always four bytes larger than you set it, because it is prefixed with its size.

View full creation

NeoTechni

NEW 8 months ago

Please note that after setting it is enforced weather the block is an int or a blob.

*whether

dreamer3

NEW 8 months ago

Out of curiosity since games typically won't be sharing save data what is the advantage of using this format vs just defining and using your own custom save format?

Sorunome

8 months ago

Well, with your own save format you'd have to think yourself about how to save things etc. This API provides abstraction to the point that you can just

gb.save.set(0, someObject);

and all the hard lifting is done by the library

Sorunome

NEW 8 months ago

dreamer3 dreamer3

Well, with your own save format you'd have to think yourself about how to save things etc. This API provides abstraction to the point that you can just

gb.save.set(0, someObject);

and all the hard lifting is done by the library

You must be logged in in order to post a message on the forum

Log in