Flow D: Save state serialization

SameBoy's save state system mirrors the GB_SECTION layout in gb.h, enabling versioned migration and optional BESS cross-emulator compatibility.

Trigger

  • User hotkey / menu → GB_save_state(gb, path)
  • Rewind → GB_save_state_to_buffer_no_bess each vblank
  • Debugger undo → internal buffer save

On-disk layout

SameBoy native format (sequential): ┌──────────────────────────────────────┐ │ header (magic, version=15) │ │ core_state (CPU registers, flags) │ │ dma │ │ mbc │ │ hram (HRAM + IO registers) │ │ timing │ │ apu │ │ rtc │ │ video (PPU state, not VRAM ptr) │ │ accessory (printer/workboy) │ ├──────────────────────────────────────┤ │ [optional] GB_sgb_t blob if HLE SGB │ ├──────────────────────────────────────┤ │ mbc_ram blob (size-prefixed) │ │ ram blob │ │ vram blob │ ├──────────────────────────────────────┤ │ BESS footer (optional): │ │ NAME, INFO, CORE, XOAM, SGB, RTC │ └──────────────────────────────────────┘
GB_SECTION macro design

From save_state.h, each section in gb.h uses:

GB_SECTION(name,
    // fields...
)

This generates offsetof/size metadata used by save_state_internal to read/write each block independently. STRUCT_VERSION (currently 15) in header validates compatibility on load.

The unsaved section is never serialized — callbacks, ROM pointers, debugger state, rewind buffers are re-established by the frontend after load.

STRUCT_VERSION · Section alignment asserts

Save path — GB_save_state
  1. GB_ASSERT_NOT_RUNNING — must not save mid-instruction
  2. Serialize each section to file via save_state_internal
  3. Append external memory: MBC RAM, WRAM, VRAM with size headers
  4. Optionally write BESS blocks for mGBA/BizHawk interoperability

In-memory variant: GB_save_state_to_buffer / GB_get_save_state_size.

Rewind uses _no_bess variants for smaller/faster snapshots.

Load path — GB_load_state
  1. Read header magic and version — reject if incompatible
  2. Validate model matches or allow cross-model rules
  3. Restore sections in order; remap MBC bank pointers via GB_update_mbc_mappings
  4. Load RAM/VRAM/MBC RAM blobs — validate sizes against cart config
  5. Parse BESS footer if present (import from other emulators)
  6. Re-sync PPU: GB_display_sync, palette caches

GB_get_state_model reads model from file without full load.

Failure returns negative errno-style codes; corrupt files do not partially apply (atomic read into buffer first for memory loads).

BESS format (cross-emulator)

Documented in BESS.md. Key blocks in save_state.c:

  • BESS_CORE_t — registers, IE, IO, RAM/VRAM/OAM buffer descriptors
  • BESS_XOAM_t — extra OAM (CGB)
  • BESS_SGB_t — SGB border/palette state
  • BESS_INFO_t — ROM title + checksum

SameBoy writes BESS footers on save for interoperability; can load BESS from mGBA and others with best-effort semantics.

BESS struct definitions

Battery saves (related but distinct)

GB_save_battery / GB_load_battery in gb.c persist only MBC external RAM + RTC — not full CPU/PPU state. Supports multiple legacy formats (VBA, TPP1, HuC3). Triggered on vblank when battery_dirty or on exit.

Failure modes

  • Version mismatch → load rejected (user must use matching SameBoy version)
  • ROM changed since save → BESS INFO checksum may warn
  • Saving during GB_run → assertion failure
  • Rewind buffer OOM → rewind silently stops pushing (check GB_rewind_pop return)