Skip to content

Implement save state support (retro_serialize/retro_unserialize)#104

Open
JoeMatt wants to merge 2 commits intolibretro:masterfrom
Provenance-Emu:libretro/feature/save-states
Open

Implement save state support (retro_serialize/retro_unserialize)#104
JoeMatt wants to merge 2 commits intolibretro:masterfrom
Provenance-Emu:libretro/feature/save-states

Conversation

@JoeMatt
Copy link
Copy Markdown
Collaborator

@JoeMatt JoeMatt commented Apr 4, 2026

Summary

  • Implements full save state serialization for all Jaguar hardware modules, enabling retro_serialize() / retro_unserialize() support
  • Each hardware module (68K, GPU, DSP, Blitter, Event, EEPROM, JERRY, TOM, CD-ROM, Joystick, Memory Track, DAC) provides StateSave/StateLoad functions that serialize internal static state into a flat byte buffer
  • Fixed ~2.4 MB state size with versioned header (magic VJSS, version 1) for forward compatibility
  • Function pointers in the event system are serialized via a callback registry (maps pointers to integer IDs)
  • 68K CPU pc_p/pc_oldp raw pointers saved as offsets into jagMemSpace and reconstructed on load
  • GPU/DSP active register bank pointers saved as bank flags (0/1) and reconstructed on load

Closes #93

Architecture

[4 bytes] Magic: 0x564A5353 ("VJSS")
[4 bytes] Version: 1
[4 bytes] Flags (reserved)
[4 bytes] Reserved
[2 MB]   Main RAM
[16 KB]  TOM registers
[64 KB]  JERRY registers
[1 byte] lowerField flag
[var]    68K CPU state (regs + pointer offsets + local statics)
[var]    GPU state (RAM + registers + bank flag)
[var]    DSP state (RAM + registers + pipeline + scoreboard)
[var]    Blitter state (RAM + ~35 control vars + gouraud/Z state)
[var]    Event queues (2×32 events with callback IDs)
[var]    EEPROM state machine
[var]    JERRY timers + interrupt state
[var]    TOM timers + interrupt state
[var]    CD-ROM state
[var]    Joystick state
[var]    Memory Track (128 KB + state)
[var]    DAC buffer state

Files Changed

  • New: src/state.h — header with magic/version constants, helper macros, extern declarations
  • Modified: 12 module files with StateSave/StateLoad functions added at end
  • Modified: libretro.cretro_serialize_size, retro_serialize, retro_unserialize implemented

Test plan

  • Build on Linux (GCC) and macOS (Clang) via CI
  • Load a cart game → play 10 sec → save state → play 10 sec → load state → verify game resumes
  • Test rewind in RetroArch (enable rewind, verify smooth forward/backward)
  • Round-trip test: serialize → unserialize → serialize → compare buffers byte-for-byte
  • Test with CD game if available

🤖 Generated with Claude Code

Add full save state serialization for all Jaguar hardware modules.
Each module provides StateSave/StateLoad functions that serialize
their internal static state into a flat byte buffer (~2.4 MB fixed).

Modules serialized: 68K CPU (with pointer-to-offset fixup for pc_p),
GPU, DSP, Blitter, Event system (with callback registry for function
pointer serialization), EEPROM, JERRY timers, TOM timers, CD-ROM,
Joystick, Memory Track, and DAC.

Closes libretro#93

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 4, 2026 02:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements libretro save state support by adding per-module state serialization/deserialization functions and wiring them into retro_serialize() / retro_unserialize() with a versioned header.

Changes:

  • Added src/state.h with save-state constants and buffer read/write helper macros.
  • Implemented StateSave/StateLoad routines across hardware modules (CPU, GPU/DSP, blitter, timers, input, etc.).
  • Implemented retro_serialize_size(), retro_serialize(), and retro_unserialize() in libretro.c.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
libretro.c Adds libretro save/load entrypoints and orchestrates module serialization.
src/state.h Defines state header constants and helper macros; declares module save/load APIs.
src/m68000/m68kinterface.c Serializes 68K core state, including pointer reconstruction via offsets.
src/gpu.c Serializes GPU RAM/register state and active register bank.
src/dsp.c Serializes DSP RAM/register state plus pipeline/scoreboard.
src/blitter.c Serializes blitter register file and control variables.
src/event.c Serializes event queues using callback-ID registry.
src/eeprom.c Serializes EEPROM state-machine variables.
src/jerry.c Serializes JERRY timer/interrupt-related state.
src/tom.c Serializes TOM timer/interrupt-related state and video dimensions.
src/cdrom.c Serializes CD-ROM emulation buffers and control state.
src/memtrack.c Serializes Memory Track contents and state variables.
src/joystick.c Serializes joystick RAM and button state arrays.
src/dac.c Serializes DAC buffer counters/flags.
CLAUDE.md Updates documentation to remove “No save state support” limitation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +808 to +822
/* Module state */
buf += M68KStateSave(buf);
buf += GPUStateSave(buf);
buf += DSPStateSave(buf);
buf += BlitterStateSave(buf);
buf += EventStateSave(buf);
buf += EepromStateSave(buf);
buf += JERRYStateSave(buf);
buf += TOMStateSave(buf);
buf += CDROMStateSave(buf);
buf += JoystickStateSave(buf);
buf += MTStateSave(buf);
buf += DACStateSave(buf);

return true;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retro_serialize() always returns true without verifying that the combined header + memory blocks + module StateSave writes exactly STATE_SIZE bytes. If STATE_SIZE is miscomputed (or changes), this can leave trailing bytes uninitialized (non-deterministic savestates / potential data leak) or overrun the provided buffer. Track bytes written and (a) return false if it exceeds size, and (b) zero-fill any remaining bytes so the entire STATE_SIZE is deterministic.

Copilot uses AI. Check for mistakes.
Comment on lines +440 to +453
size_t EepromStateSave(uint8_t *buf)
{
uint8_t *start = buf;

STATE_SAVE_VAR(buf, jerry_ee_state);
STATE_SAVE_VAR(buf, jerry_ee_op);
STATE_SAVE_VAR(buf, jerry_ee_rstate);
STATE_SAVE_VAR(buf, jerry_ee_address_data);
STATE_SAVE_VAR(buf, jerry_ee_address_cnt);
STATE_SAVE_VAR(buf, jerry_ee_data);
STATE_SAVE_VAR(buf, jerry_ee_data_cnt);
STATE_SAVE_VAR(buf, jerry_writes_enabled);
STATE_SAVE_VAR(buf, jerry_ee_direct_jump);

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EepromStateSave/Load currently serializes only the EEPROM state-machine variables, but not the actual EEPROM contents (eeprom_ram / cdromEEPROM) or presence flags. This means loading a savestate will not restore EEPROM data to the saved point in time. Include the EEPROM data arrays (and any related flags needed to interpret them) in the serialized state.

Copilot uses AI. Check for mistakes.
Comment on lines +157 to +175
size_t JoystickStateSave(uint8_t *buf)
{
uint8_t *start = buf;

STATE_SAVE_BUF(buf, joystick_ram, sizeof(joystick_ram));
STATE_SAVE_BUF(buf, joypad0Buttons, sizeof(joypad0Buttons));
STATE_SAVE_BUF(buf, joypad1Buttons, sizeof(joypad1Buttons));

return (size_t)(buf - start);
}

size_t JoystickStateLoad(const uint8_t *buf)
{
const uint8_t *start = buf;

STATE_LOAD_BUF(buf, joystick_ram, sizeof(joystick_ram));
STATE_LOAD_BUF(buf, joypad0Buttons, sizeof(joypad0Buttons));
STATE_LOAD_BUF(buf, joypad1Buttons, sizeof(joypad1Buttons));

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JoystickStateSave/Load restores joystick_ram and button arrays, but does not restore the derived enable flags (audioEnabled/joysticksEnabled). Since those flags are set from writes to joystick_ram, a loaded state can end up with joystick_ram saying enabled while the booleans remain stale. Either serialize these booleans too, or recompute them from joystick_ram after STATE_LOAD_BUF.

Copilot uses AI. Check for mistakes.
- Zero-fill remaining bytes in retro_serialize() for deterministic
  save states and add bounds check against STATE_SIZE overflow
- Include eeprom_ram[64] and cdromEEPROM[64] in EepromStateSave/Load
  so EEPROM contents are preserved across save/load cycles
- Include audioEnabled and joysticksEnabled flags in JoystickStateSave/Load
  so derived state from joystick_ram writes is not stale after load

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@JoeMatt
Copy link
Copy Markdown
Collaborator Author

JoeMatt commented Apr 4, 2026

Addressed all three Copilot review comments in ea50415:

  1. retro_serialize bounds check — Added overflow check and zero-fill of remaining bytes for deterministic savestates
  2. EEPROM data arrays — Now includes eeprom_ram[64] and cdromEEPROM[64] in EepromStateSave/Load
  3. Joystick derived flags — Now includes audioEnabled and joysticksEnabled in JoystickStateSave/Load

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Implement save states (retro_serialize/unserialize)

2 participants