Updated 7 November 2026

Save states

C8B_API has no save-state functions. Save states are a plugin_call convention: the Lua functions plugin.saveState() and friends call reserved ids of your plugin. Implement these ids to support save states.

Contents

Reserved call ids

Lua Calls your plugin with Your plugin returns
plugin.saveState() → string or nil call(20), then call(22) 20: size in bytes (> 0) of a state you wrote to your own buffer; 22: the address of that buffer
plugin.loadState(s) → boolean call(21, ptr, len) 0 = success
plugin.fullSaveState() → string or nil call(30), then call(32) as 20/22, for the full state
plugin.fullLoadState(s) → boolean call(31, ptr, len) 0 = success

The compact state (20/21/22) is meant for netplay synchronisation; the full state (30/31/32) is a complete snapshot.

Do not use ids 20, 21, 22, 30, 31 or 32 for anything else. If your plugin does not support save states, return -1 for these ids, and do not call the save-state functions from Lua for that plugin.

On the console the functions return nil or false when no plugin is ready.

Buffer lifetime

  • Saving: Lua copies your buffer into a string during call(22) / call(32). The buffer only needs to stay valid until that call returns.
  • Loading: ptr points into the Lua string and is valid only during call(21) / call(31). Copy what you need.

Example

static uint8_t s_state[64 * 1024];
static int64_t s_state_size;

int64_t plugin_call(uint8_t id, const int64_t* args, uint8_t argc) {
    switch (id) {
    case 20: s_state_size = write_state(s_state, sizeof s_state); return s_state_size;
    case 22: return (int64_t)(intptr_t)s_state;
    case 21: return read_state((const void*)(intptr_t)args[0], (size_t)args[1]) ? 0 : -1;
    default: return -1;
    }
}
local snapshot = plugin.saveState()
-- ...
if snapshot then plugin.loadState(snapshot) end

Game save files (saved games, battery RAM) are ordinary files written with file_open, file_write, file_rename and file_mkdir, not save states.