Skip to content

Commit 267ad53

Browse files
authored
Merge pull request #104 from Provenance-Emu/libretro/feature/save-states
Implement save state support (retro_serialize/retro_unserialize)
2 parents 0fc55a2 + b631c1f commit 267ad53

16 files changed

Lines changed: 1114 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: 106 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,112 @@ 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, *start;
786+
size_t written;
787+
uint32_t magic, version, flags, reserved;
788+
789+
if (!data || size < STATE_SIZE)
790+
return false;
791+
792+
start = (uint8_t *)data;
793+
buf = start;
794+
795+
/* Header */
796+
magic = STATE_MAGIC;
797+
version = STATE_VERSION;
798+
flags = 0;
799+
reserved = 0;
800+
STATE_SAVE_VAR(buf, magic);
801+
STATE_SAVE_VAR(buf, version);
802+
STATE_SAVE_VAR(buf, flags);
803+
STATE_SAVE_VAR(buf, reserved);
804+
805+
/* Large memory blocks */
806+
STATE_SAVE_BUF(buf, jaguarMainRAM, 0x200000); /* 2 MB main RAM */
807+
STATE_SAVE_BUF(buf, tomRam8, 0x4000); /* 16 KB TOM registers */
808+
809+
extern uint8_t jerry_ram_8[];
810+
STATE_SAVE_BUF(buf, jerry_ram_8, 0x10000); /* 64 KB JERRY registers */
811+
812+
/* Jaguar misc state */
813+
extern bool lowerField;
814+
STATE_SAVE_VAR(buf, lowerField);
815+
816+
/* Module state */
817+
buf += M68KStateSave(buf);
818+
buf += GPUStateSave(buf);
819+
buf += DSPStateSave(buf);
820+
buf += BlitterStateSave(buf);
821+
buf += EventStateSave(buf);
822+
buf += EepromStateSave(buf);
823+
buf += JERRYStateSave(buf);
824+
buf += TOMStateSave(buf);
825+
buf += CDROMStateSave(buf);
826+
buf += JoystickStateSave(buf);
827+
buf += MTStateSave(buf);
828+
buf += DACStateSave(buf);
829+
830+
written = (size_t)(buf - start);
831+
if (written > STATE_SIZE)
832+
return false;
833+
834+
/* Zero-fill remaining bytes for deterministic save states */
835+
if (written < STATE_SIZE)
836+
memset(buf, 0, STATE_SIZE - written);
837+
838+
return true;
785839
}
786840

787841
bool retro_unserialize(const void *data, size_t size)
788842
{
789-
return false;
843+
const uint8_t *buf;
844+
uint32_t magic, version, flags, reserved;
845+
846+
if (!data || size < STATE_SIZE)
847+
return false;
848+
849+
buf = (const uint8_t *)data;
850+
851+
/* Validate header */
852+
STATE_LOAD_VAR(buf, magic);
853+
STATE_LOAD_VAR(buf, version);
854+
STATE_LOAD_VAR(buf, flags);
855+
STATE_LOAD_VAR(buf, reserved);
856+
857+
if (magic != STATE_MAGIC || version != STATE_VERSION)
858+
return false;
859+
860+
/* Large memory blocks */
861+
STATE_LOAD_BUF(buf, jaguarMainRAM, 0x200000);
862+
STATE_LOAD_BUF(buf, tomRam8, 0x4000);
863+
864+
extern uint8_t jerry_ram_8[];
865+
STATE_LOAD_BUF(buf, jerry_ram_8, 0x10000);
866+
867+
/* Jaguar misc state */
868+
extern bool lowerField;
869+
STATE_LOAD_VAR(buf, lowerField);
870+
871+
/* Module state */
872+
buf += M68KStateLoad(buf);
873+
buf += GPUStateLoad(buf);
874+
buf += DSPStateLoad(buf);
875+
buf += BlitterStateLoad(buf);
876+
buf += EventStateLoad(buf);
877+
buf += EepromStateLoad(buf);
878+
buf += JERRYStateLoad(buf);
879+
buf += TOMStateLoad(buf);
880+
buf += CDROMStateLoad(buf);
881+
buf += JoystickStateLoad(buf);
882+
buf += MTStateLoad(buf);
883+
buf += DACStateLoad(buf);
884+
885+
return true;
790886
}
791887

792888
void retro_cheat_reset(void)
@@ -857,6 +953,13 @@ bool retro_load_game(const struct retro_game_info *info)
857953

858954
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
859955

956+
/* Report that save states are deterministic (no quirks).
957+
* This enables run-ahead and netplay in RetroArch. */
958+
{
959+
uint64_t serialization_quirks = 0;
960+
environ_cb(RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS, &serialization_quirks);
961+
}
962+
860963
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
861964
{
862965
//fprintf(stderr, "Pixel format XRGB8888 not supported by platform, cannot use.\n");

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)