Skip to content

Commit b408304

Browse files
JoeMattclaude
andcommitted
Implement save state support (retro_serialize/retro_unserialize)
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 #93 Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent f182ad0 commit b408304

15 files changed

Lines changed: 951 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ Core options defined in `libretro_core_options.h` control blitter mode, BIOS usa
6464

6565
### Known Limitations
6666

67-
- No save state support
6867
- Blitter not fully cycle-accurate (some games need fast blitter mode)
6968
- Bus contention between processors not emulated
7069
- Vertical count (VC) register behavior not fully accurate

libretro.c

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "joystick.h"
1818
#include "settings.h"
1919
#include "tom.h"
20+
#include "state.h"
2021

2122
#define SAMPLERATE 48000
2223
#define BUFPAL 1920
@@ -776,17 +777,102 @@ void retro_set_controller_port_device(unsigned port, unsigned device)
776777

777778
size_t retro_serialize_size(void)
778779
{
779-
return 0;
780+
return STATE_SIZE;
780781
}
781782

782783
bool retro_serialize(void *data, size_t size)
783784
{
784-
return false;
785+
uint8_t *buf;
786+
uint32_t magic, version, flags, reserved;
787+
788+
if (!data || size < STATE_SIZE)
789+
return false;
790+
791+
buf = (uint8_t *)data;
792+
793+
/* Header */
794+
magic = STATE_MAGIC;
795+
version = STATE_VERSION;
796+
flags = 0;
797+
reserved = 0;
798+
STATE_SAVE_VAR(buf, magic);
799+
STATE_SAVE_VAR(buf, version);
800+
STATE_SAVE_VAR(buf, flags);
801+
STATE_SAVE_VAR(buf, reserved);
802+
803+
/* Large memory blocks */
804+
STATE_SAVE_BUF(buf, jaguarMainRAM, 0x200000); /* 2 MB main RAM */
805+
STATE_SAVE_BUF(buf, tomRam8, 0x4000); /* 16 KB TOM registers */
806+
807+
extern uint8_t jerry_ram_8[];
808+
STATE_SAVE_BUF(buf, jerry_ram_8, 0x10000); /* 64 KB JERRY registers */
809+
810+
/* Jaguar misc state */
811+
extern bool lowerField;
812+
STATE_SAVE_VAR(buf, lowerField);
813+
814+
/* Module state */
815+
buf += M68KStateSave(buf);
816+
buf += GPUStateSave(buf);
817+
buf += DSPStateSave(buf);
818+
buf += BlitterStateSave(buf);
819+
buf += EventStateSave(buf);
820+
buf += EepromStateSave(buf);
821+
buf += JERRYStateSave(buf);
822+
buf += TOMStateSave(buf);
823+
buf += CDROMStateSave(buf);
824+
buf += JoystickStateSave(buf);
825+
buf += MTStateSave(buf);
826+
buf += DACStateSave(buf);
827+
828+
return true;
785829
}
786830

787831
bool retro_unserialize(const void *data, size_t size)
788832
{
789-
return false;
833+
const uint8_t *buf;
834+
uint32_t magic, version, flags, reserved;
835+
836+
if (!data || size < STATE_SIZE)
837+
return false;
838+
839+
buf = (const uint8_t *)data;
840+
841+
/* Validate header */
842+
STATE_LOAD_VAR(buf, magic);
843+
STATE_LOAD_VAR(buf, version);
844+
STATE_LOAD_VAR(buf, flags);
845+
STATE_LOAD_VAR(buf, reserved);
846+
847+
if (magic != STATE_MAGIC || version != STATE_VERSION)
848+
return false;
849+
850+
/* Large memory blocks */
851+
STATE_LOAD_BUF(buf, jaguarMainRAM, 0x200000);
852+
STATE_LOAD_BUF(buf, tomRam8, 0x4000);
853+
854+
extern uint8_t jerry_ram_8[];
855+
STATE_LOAD_BUF(buf, jerry_ram_8, 0x10000);
856+
857+
/* Jaguar misc state */
858+
extern bool lowerField;
859+
STATE_LOAD_VAR(buf, lowerField);
860+
861+
/* Module state */
862+
buf += M68KStateLoad(buf);
863+
buf += GPUStateLoad(buf);
864+
buf += DSPStateLoad(buf);
865+
buf += BlitterStateLoad(buf);
866+
buf += EventStateLoad(buf);
867+
buf += EepromStateLoad(buf);
868+
buf += JERRYStateLoad(buf);
869+
buf += TOMStateLoad(buf);
870+
buf += CDROMStateLoad(buf);
871+
buf += JoystickStateLoad(buf);
872+
buf += MTStateLoad(buf);
873+
buf += DACStateLoad(buf);
874+
875+
return true;
790876
}
791877

792878
void retro_cheat_reset(void)

src/blitter.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3427,3 +3427,129 @@ Dbinh[7] := NAN2 (dbinh\[7], di7t[2], phrase_mode);*/
34273427
}
34283428

34293429
#endif
3430+
3431+
3432+
/* Save state serialization for Blitter */
3433+
3434+
#include "state.h"
3435+
3436+
size_t BlitterStateSave(uint8_t *buf)
3437+
{
3438+
uint8_t *start = buf;
3439+
3440+
STATE_SAVE_BUF(buf, blitter_ram, sizeof(blitter_ram));
3441+
STATE_SAVE_VAR(buf, src);
3442+
STATE_SAVE_VAR(buf, dst);
3443+
STATE_SAVE_VAR(buf, misc);
3444+
STATE_SAVE_VAR(buf, a1ctl);
3445+
STATE_SAVE_VAR(buf, mode);
3446+
STATE_SAVE_VAR(buf, ity);
3447+
STATE_SAVE_VAR(buf, zop);
3448+
STATE_SAVE_VAR(buf, op);
3449+
STATE_SAVE_VAR(buf, ctrl);
3450+
STATE_SAVE_VAR(buf, a1_addr);
3451+
STATE_SAVE_VAR(buf, a2_addr);
3452+
STATE_SAVE_VAR(buf, a1_zoffs);
3453+
STATE_SAVE_VAR(buf, a2_zoffs);
3454+
STATE_SAVE_VAR(buf, xadd_a1_control);
3455+
STATE_SAVE_VAR(buf, xadd_a2_control);
3456+
STATE_SAVE_VAR(buf, a1_pitch);
3457+
STATE_SAVE_VAR(buf, a2_pitch);
3458+
STATE_SAVE_VAR(buf, n_pixels);
3459+
STATE_SAVE_VAR(buf, n_lines);
3460+
STATE_SAVE_VAR(buf, a1_x);
3461+
STATE_SAVE_VAR(buf, a1_y);
3462+
STATE_SAVE_VAR(buf, a1_width);
3463+
STATE_SAVE_VAR(buf, a2_x);
3464+
STATE_SAVE_VAR(buf, a2_y);
3465+
STATE_SAVE_VAR(buf, a2_width);
3466+
STATE_SAVE_VAR(buf, a2_mask_x);
3467+
STATE_SAVE_VAR(buf, a2_mask_y);
3468+
STATE_SAVE_VAR(buf, a1_xadd);
3469+
STATE_SAVE_VAR(buf, a1_yadd);
3470+
STATE_SAVE_VAR(buf, a2_xadd);
3471+
STATE_SAVE_VAR(buf, a2_yadd);
3472+
STATE_SAVE_VAR(buf, a1_phrase_mode);
3473+
STATE_SAVE_VAR(buf, a2_phrase_mode);
3474+
STATE_SAVE_VAR(buf, a1_step_x);
3475+
STATE_SAVE_VAR(buf, a1_step_y);
3476+
STATE_SAVE_VAR(buf, a2_step_x);
3477+
STATE_SAVE_VAR(buf, a2_step_y);
3478+
STATE_SAVE_VAR(buf, outer_loop);
3479+
STATE_SAVE_VAR(buf, inner_loop);
3480+
STATE_SAVE_VAR(buf, a2_psize);
3481+
STATE_SAVE_VAR(buf, a1_psize);
3482+
STATE_SAVE_VAR(buf, gouraud_add);
3483+
STATE_SAVE_BUF(buf, gd_i, sizeof(gd_i));
3484+
STATE_SAVE_BUF(buf, gd_c, sizeof(gd_c));
3485+
STATE_SAVE_VAR(buf, gd_ia);
3486+
STATE_SAVE_VAR(buf, gd_ca);
3487+
STATE_SAVE_VAR(buf, colour_index);
3488+
STATE_SAVE_VAR(buf, zadd);
3489+
STATE_SAVE_BUF(buf, z_i, sizeof(z_i));
3490+
STATE_SAVE_VAR(buf, a1_clip_x);
3491+
STATE_SAVE_VAR(buf, a1_clip_y);
3492+
3493+
return (size_t)(buf - start);
3494+
}
3495+
3496+
3497+
size_t BlitterStateLoad(const uint8_t *buf)
3498+
{
3499+
const uint8_t *start = buf;
3500+
3501+
STATE_LOAD_BUF(buf, blitter_ram, sizeof(blitter_ram));
3502+
STATE_LOAD_VAR(buf, src);
3503+
STATE_LOAD_VAR(buf, dst);
3504+
STATE_LOAD_VAR(buf, misc);
3505+
STATE_LOAD_VAR(buf, a1ctl);
3506+
STATE_LOAD_VAR(buf, mode);
3507+
STATE_LOAD_VAR(buf, ity);
3508+
STATE_LOAD_VAR(buf, zop);
3509+
STATE_LOAD_VAR(buf, op);
3510+
STATE_LOAD_VAR(buf, ctrl);
3511+
STATE_LOAD_VAR(buf, a1_addr);
3512+
STATE_LOAD_VAR(buf, a2_addr);
3513+
STATE_LOAD_VAR(buf, a1_zoffs);
3514+
STATE_LOAD_VAR(buf, a2_zoffs);
3515+
STATE_LOAD_VAR(buf, xadd_a1_control);
3516+
STATE_LOAD_VAR(buf, xadd_a2_control);
3517+
STATE_LOAD_VAR(buf, a1_pitch);
3518+
STATE_LOAD_VAR(buf, a2_pitch);
3519+
STATE_LOAD_VAR(buf, n_pixels);
3520+
STATE_LOAD_VAR(buf, n_lines);
3521+
STATE_LOAD_VAR(buf, a1_x);
3522+
STATE_LOAD_VAR(buf, a1_y);
3523+
STATE_LOAD_VAR(buf, a1_width);
3524+
STATE_LOAD_VAR(buf, a2_x);
3525+
STATE_LOAD_VAR(buf, a2_y);
3526+
STATE_LOAD_VAR(buf, a2_width);
3527+
STATE_LOAD_VAR(buf, a2_mask_x);
3528+
STATE_LOAD_VAR(buf, a2_mask_y);
3529+
STATE_LOAD_VAR(buf, a1_xadd);
3530+
STATE_LOAD_VAR(buf, a1_yadd);
3531+
STATE_LOAD_VAR(buf, a2_xadd);
3532+
STATE_LOAD_VAR(buf, a2_yadd);
3533+
STATE_LOAD_VAR(buf, a1_phrase_mode);
3534+
STATE_LOAD_VAR(buf, a2_phrase_mode);
3535+
STATE_LOAD_VAR(buf, a1_step_x);
3536+
STATE_LOAD_VAR(buf, a1_step_y);
3537+
STATE_LOAD_VAR(buf, a2_step_x);
3538+
STATE_LOAD_VAR(buf, a2_step_y);
3539+
STATE_LOAD_VAR(buf, outer_loop);
3540+
STATE_LOAD_VAR(buf, inner_loop);
3541+
STATE_LOAD_VAR(buf, a2_psize);
3542+
STATE_LOAD_VAR(buf, a1_psize);
3543+
STATE_LOAD_VAR(buf, gouraud_add);
3544+
STATE_LOAD_BUF(buf, gd_i, sizeof(gd_i));
3545+
STATE_LOAD_BUF(buf, gd_c, sizeof(gd_c));
3546+
STATE_LOAD_VAR(buf, gd_ia);
3547+
STATE_LOAD_VAR(buf, gd_ca);
3548+
STATE_LOAD_VAR(buf, colour_index);
3549+
STATE_LOAD_VAR(buf, zadd);
3550+
STATE_LOAD_BUF(buf, z_i, sizeof(z_i));
3551+
STATE_LOAD_VAR(buf, a1_clip_x);
3552+
STATE_LOAD_VAR(buf, a1_clip_y);
3553+
3554+
return (size_t)(buf - start);
3555+
}

src/cdrom.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,3 +1121,66 @@ storew TEMP,(r24)
11211121
11221122
*/
11231123

1124+
#include "state.h"
1125+
1126+
size_t CDROMStateSave(uint8_t *buf)
1127+
{
1128+
uint8_t *start = buf;
1129+
1130+
STATE_SAVE_BUF(buf, cdRam, sizeof(cdRam));
1131+
STATE_SAVE_VAR(buf, cdCmd);
1132+
STATE_SAVE_VAR(buf, cdPtr);
1133+
STATE_SAVE_VAR(buf, haveCDGoodness);
1134+
STATE_SAVE_VAR(buf, min);
1135+
STATE_SAVE_VAR(buf, sec);
1136+
STATE_SAVE_VAR(buf, frm);
1137+
STATE_SAVE_VAR(buf, block);
1138+
STATE_SAVE_BUF(buf, cdBuf, sizeof(cdBuf));
1139+
STATE_SAVE_VAR(buf, cdBufPtr);
1140+
STATE_SAVE_VAR(buf, trackNum);
1141+
STATE_SAVE_VAR(buf, minTrack);
1142+
STATE_SAVE_VAR(buf, maxTrack);
1143+
STATE_SAVE_VAR(buf, currentState);
1144+
STATE_SAVE_VAR(buf, counter);
1145+
STATE_SAVE_VAR(buf, cmdTx);
1146+
STATE_SAVE_VAR(buf, busCmd);
1147+
STATE_SAVE_VAR(buf, rxData);
1148+
STATE_SAVE_VAR(buf, txData);
1149+
STATE_SAVE_VAR(buf, rxDataBit);
1150+
STATE_SAVE_VAR(buf, firstTime);
1151+
STATE_SAVE_BUF(buf, cdBuf2, sizeof(cdBuf2));
1152+
STATE_SAVE_BUF(buf, cdBuf3, sizeof(cdBuf3));
1153+
1154+
return (size_t)(buf - start);
1155+
}
1156+
1157+
size_t CDROMStateLoad(const uint8_t *buf)
1158+
{
1159+
const uint8_t *start = buf;
1160+
1161+
STATE_LOAD_BUF(buf, cdRam, sizeof(cdRam));
1162+
STATE_LOAD_VAR(buf, cdCmd);
1163+
STATE_LOAD_VAR(buf, cdPtr);
1164+
STATE_LOAD_VAR(buf, haveCDGoodness);
1165+
STATE_LOAD_VAR(buf, min);
1166+
STATE_LOAD_VAR(buf, sec);
1167+
STATE_LOAD_VAR(buf, frm);
1168+
STATE_LOAD_VAR(buf, block);
1169+
STATE_LOAD_BUF(buf, cdBuf, sizeof(cdBuf));
1170+
STATE_LOAD_VAR(buf, cdBufPtr);
1171+
STATE_LOAD_VAR(buf, trackNum);
1172+
STATE_LOAD_VAR(buf, minTrack);
1173+
STATE_LOAD_VAR(buf, maxTrack);
1174+
STATE_LOAD_VAR(buf, currentState);
1175+
STATE_LOAD_VAR(buf, counter);
1176+
STATE_LOAD_VAR(buf, cmdTx);
1177+
STATE_LOAD_VAR(buf, busCmd);
1178+
STATE_LOAD_VAR(buf, rxData);
1179+
STATE_LOAD_VAR(buf, txData);
1180+
STATE_LOAD_VAR(buf, rxDataBit);
1181+
STATE_LOAD_VAR(buf, firstTime);
1182+
STATE_LOAD_BUF(buf, cdBuf2, sizeof(cdBuf2));
1183+
STATE_LOAD_BUF(buf, cdBuf3, sizeof(cdBuf3));
1184+
1185+
return (size_t)(buf - start);
1186+
}

src/dac.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,27 @@ uint16_t DACReadWord(uint32_t offset, uint32_t who)
213213

214214
return 0xFFFF; // May need SSTAT as well... (but may be a Jaguar II only feature)
215215
}
216+
217+
#include "state.h"
218+
219+
size_t DACStateSave(uint8_t *buf)
220+
{
221+
uint8_t *start = buf;
222+
223+
STATE_SAVE_VAR(buf, bufferIndex);
224+
STATE_SAVE_VAR(buf, numberOfSamples);
225+
STATE_SAVE_VAR(buf, bufferDone);
226+
227+
return (size_t)(buf - start);
228+
}
229+
230+
size_t DACStateLoad(const uint8_t *buf)
231+
{
232+
const uint8_t *start = buf;
233+
234+
STATE_LOAD_VAR(buf, bufferIndex);
235+
STATE_LOAD_VAR(buf, numberOfSamples);
236+
STATE_LOAD_VAR(buf, bufferDone);
237+
238+
return (size_t)(buf - start);
239+
}

0 commit comments

Comments
 (0)