Skip to content

Commit 0505cdd

Browse files
JoeMattclaude
andcommitted
Use libretro SRAM interface for save data persistence
Remove core-managed file I/O from eeprom.c and memtrack.c. Save data is now persisted entirely by the frontend via retro_get_memory_data() and retro_get_memory_size() using RETRO_MEMORY_SAVE_RAM. This fixes save files going to the wrong directory (issue #8) by letting the frontend control the save path and filename (.srm). Save buffer layout: - Regular carts: 256 bytes (128 cart EEPROM + 128 CDROM EEPROM), stored big-endian for cross-platform compatibility - Memory Track cart (CRC 0xFDF37F47): 128K raw NVRAM Changes: - eeprom.c: Remove file I/O, expose cdromEEPROM for packing - memtrack.c: Remove file I/O, MTInit/MTDone are now no-ops - libretro.c: Add endian-safe pack/unpack for EEPROM save buffer, return correct buffer and size for Memory Track vs regular carts, unpack frontend-loaded save data on first retro_run() - settings.h: Remove unused EEPROMPath and romName fields Fixes #8. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 267ad53 commit 0505cdd

4 files changed

Lines changed: 96 additions & 208 deletions

File tree

libretro.c

Lines changed: 81 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,21 @@ uint32_t *videoBuffer = NULL;
3030
int game_width = 0;
3131
int game_height = 0;
3232

33-
extern uint16_t eeprom_ram[];
33+
extern uint16_t eeprom_ram[64];
34+
extern uint16_t cdromEEPROM[64];
35+
extern uint8_t mtMem[0x20000];
36+
extern uint32_t jaguarMainROMCRC32;
37+
38+
/* Combined save buffer for RETRO_MEMORY_SAVE_RAM.
39+
* Layout: [cart EEPROM 128 bytes][CDROM EEPROM 128 bytes]
40+
* For Memory Track cart (CRC 0xFDF37F47): mtMem is used directly (128K).
41+
*
42+
* EEPROM data is stored big-endian on disk (high byte first per 16-bit word)
43+
* to match the Jaguar's native byte order. This is handled by pack/unpack
44+
* functions so the in-memory uint16_t arrays work on any host. */
45+
#define EEPROM_SAVE_SIZE 256 /* 128 bytes cart + 128 bytes CDROM */
46+
#define MT_SAVE_SIZE 0x20000 /* 128K Memory Track */
47+
static uint8_t eeprom_save_buf[EEPROM_SAVE_SIZE];
3448

3549
static retro_video_refresh_t video_cb;
3650
static retro_input_poll_t input_poll_cb;
@@ -39,6 +53,7 @@ static retro_environment_t environ_cb;
3953
retro_audio_sample_batch_t audio_batch_cb;
4054

4155
static bool libretro_supports_bitmasks = false;
56+
static bool save_data_needs_unpack = false;
4257

4358
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
4459
void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
@@ -719,25 +734,7 @@ static void update_input(void)
719734
}
720735
}
721736

722-
static void extract_basename(char *buf, const char *path, size_t size)
723-
{
724-
char *ext = NULL;
725-
const char *base = strrchr(path, '/');
726-
if (!base)
727-
base = strrchr(path, '\\');
728-
if (!base)
729-
base = path;
730-
731-
if (*base == '\\' || *base == '/')
732-
base++;
733-
734-
strncpy(buf, base, size - 1);
735-
buf[size - 1] = '\0';
736-
737-
ext = strrchr(buf, '.');
738-
if (ext)
739-
*ext = '\0';
740-
}
737+
741738

742739
/************************************
743740
* libretro implementation
@@ -897,9 +894,7 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
897894

898895
bool retro_load_game(const struct retro_game_info *info)
899896
{
900-
char slash;
901897
unsigned i;
902-
const char *save_dir = NULL;
903898
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
904899

905900
struct retro_input_descriptor desc[] = {
@@ -981,32 +976,6 @@ bool retro_load_game(const struct retro_game_info *info)
981976

982977
check_variables();
983978

984-
// Get eeprom path info
985-
// > Handle Windows nonsense...
986-
#if defined(_WIN32)
987-
slash = '\\';
988-
#else
989-
slash = '/';
990-
#endif
991-
// > Get save path
992-
vjs.EEPROMPath[0] = '\0';
993-
if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir) && save_dir)
994-
{
995-
if (strlen(save_dir) > 0)
996-
{
997-
sprintf(vjs.EEPROMPath, "%s%c", save_dir, slash);
998-
}
999-
}
1000-
// > Get ROM name
1001-
if (info->path != NULL)
1002-
{
1003-
extract_basename(vjs.romName, info->path, sizeof(vjs.romName));
1004-
}
1005-
else
1006-
{
1007-
vjs.romName[0] = '\0';
1008-
}
1009-
1010979
JaguarInit(); // set up hardware
1011980
memcpy(jagMemSpace + 0xE00000,
1012981
((vjs.biosType == BT_K_SERIES) ? jaguarBootROM : jaguarBootROM2),
@@ -1023,6 +992,11 @@ bool retro_load_game(const struct retro_game_info *info)
1023992
JaguarLoadFile((uint8_t*)info->data, info->size);
1024993
JaguarReset();
1025994

995+
/* The frontend will load .srm data into our save buffer (returned by
996+
* retro_get_memory_data) after this function returns but before the
997+
* first retro_run(). We unpack it on the first frame. */
998+
save_data_needs_unpack = true;
999+
10261000
return true;
10271001
}
10281002

@@ -1055,22 +1029,63 @@ unsigned retro_api_version(void)
10551029
return RETRO_API_VERSION;
10561030
}
10571031

1032+
/* Pack EEPROM uint16_t arrays into the save buffer (big-endian).
1033+
* Called before the frontend reads save data. */
1034+
static void eeprom_pack_save_buf(void)
1035+
{
1036+
unsigned i;
1037+
for (i = 0; i < 64; i++)
1038+
{
1039+
eeprom_save_buf[(i * 2) + 0] = eeprom_ram[i] >> 8;
1040+
eeprom_save_buf[(i * 2) + 1] = eeprom_ram[i] & 0xFF;
1041+
}
1042+
for (i = 0; i < 64; i++)
1043+
{
1044+
eeprom_save_buf[128 + (i * 2) + 0] = cdromEEPROM[i] >> 8;
1045+
eeprom_save_buf[128 + (i * 2) + 1] = cdromEEPROM[i] & 0xFF;
1046+
}
1047+
}
1048+
1049+
/* Unpack the save buffer back into EEPROM uint16_t arrays.
1050+
* Called after the frontend loads save data. */
1051+
static void eeprom_unpack_save_buf(void)
1052+
{
1053+
unsigned i;
1054+
for (i = 0; i < 64; i++)
1055+
eeprom_ram[i] = ((uint16_t)eeprom_save_buf[(i * 2) + 0] << 8)
1056+
| eeprom_save_buf[(i * 2) + 1];
1057+
for (i = 0; i < 64; i++)
1058+
cdromEEPROM[i] = ((uint16_t)eeprom_save_buf[128 + (i * 2) + 0] << 8)
1059+
| eeprom_save_buf[128 + (i * 2) + 1];
1060+
}
1061+
10581062
void *retro_get_memory_data(unsigned type)
10591063
{
1060-
if(type == RETRO_MEMORY_SYSTEM_RAM)
1064+
if (type == RETRO_MEMORY_SYSTEM_RAM)
10611065
return jaguarMainRAM;
1062-
else if (type == RETRO_MEMORY_SAVE_RAM)
1063-
return eeprom_ram;
1064-
else return NULL;
1066+
if (type == RETRO_MEMORY_SAVE_RAM)
1067+
{
1068+
/* Memory Track cart uses 128K NVRAM directly */
1069+
if (jaguarMainROMCRC32 == 0xFDF37F47)
1070+
return mtMem;
1071+
/* Regular carts: pack EEPROM into endian-safe buffer */
1072+
eeprom_pack_save_buf();
1073+
return eeprom_save_buf;
1074+
}
1075+
return NULL;
10651076
}
10661077

10671078
size_t retro_get_memory_size(unsigned type)
10681079
{
1069-
if(type == RETRO_MEMORY_SYSTEM_RAM)
1080+
if (type == RETRO_MEMORY_SYSTEM_RAM)
10701081
return 0x200000;
1071-
else if (type == RETRO_MEMORY_SAVE_RAM)
1072-
return 128;
1073-
else return 0;
1082+
if (type == RETRO_MEMORY_SAVE_RAM)
1083+
{
1084+
if (jaguarMainROMCRC32 == 0xFDF37F47)
1085+
return MT_SAVE_SIZE;
1086+
return EEPROM_SAVE_SIZE;
1087+
}
1088+
return 0;
10741089
}
10751090

10761091
void retro_init(void)
@@ -1097,6 +1112,16 @@ void retro_run(void)
10971112
{
10981113
bool updated = false;
10991114

1115+
/* On the first frame, unpack save data that the frontend loaded
1116+
* into our RETRO_MEMORY_SAVE_RAM buffer after retro_load_game(). */
1117+
if (save_data_needs_unpack)
1118+
{
1119+
save_data_needs_unpack = false;
1120+
if (jaguarMainROMCRC32 != 0xFDF37F47)
1121+
eeprom_unpack_save_buf();
1122+
/* Memory Track: mtMem was written directly, no unpack needed. */
1123+
}
1124+
11001125
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
11011126
check_variables();
11021127

src/eeprom.c

Lines changed: 9 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -18,52 +18,15 @@
1818
#include <stdlib.h>
1919
#include <string.h> // For memset
2020

21-
#include <streams/file_stream.h>
22-
#include "settings.h"
23-
24-
extern uint32_t jaguarMainROMCRC32;
25-
2621
uint16_t eeprom_ram[64];
27-
static uint16_t cdromEEPROM[64];
22+
uint16_t cdromEEPROM[64];
2823

2924
// Private function prototypes
30-
RFILE* rfopen(const char *path, const char *mode);
31-
int rfclose(RFILE* stream);
32-
int64_t rfwrite(void const* buffer,
33-
size_t elem_size, size_t elem_count, RFILE* stream);
34-
int64_t rfread(void* buffer,
35-
size_t elem_size, size_t elem_count, RFILE* stream);
36-
3725
static void EEPROMSave(void);
3826
static void eeprom_set_di(uint32_t state);
3927
static void eeprom_set_cs(uint32_t state);
4028
static uint32_t eeprom_get_do(void);
4129

42-
static void WriteEEPROMToFile(RFILE * file, uint16_t * ram)
43-
{
44-
unsigned i;
45-
uint8_t buffer[128];
46-
47-
for(i = 0; i < 64; i++)
48-
{
49-
buffer[(i * 2) + 0] = ram[i] >> 8;
50-
buffer[(i * 2) + 1] = ram[i] & 0xFF;
51-
}
52-
53-
rfwrite(buffer, 1, 128, file);
54-
}
55-
56-
// Read/write EEPROM files to disk in an endian safe manner
57-
static void ReadEEPROMFromFile(RFILE *file, uint16_t * ram)
58-
{
59-
unsigned i;
60-
uint8_t buffer[128];
61-
size_t ignored = rfread(buffer, 1, 128, file);
62-
63-
for(i = 0; i < 64; i++)
64-
ram[i] = (buffer[(i * 2) + 0] << 8) | buffer[(i * 2) + 1];
65-
}
66-
6730
enum { EE_STATE_START = 1, EE_STATE_OP_A, EE_STATE_OP_B, EE_STATE_0, EE_STATE_1,
6831
EE_STATE_2, EE_STATE_3, EE_STATE_0_0, EE_READ_ADDRESS, EE_STATE_0_0_0,
6932
EE_STATE_0_0_1, EE_STATE_0_0_2, EE_STATE_0_0_3, EE_STATE_0_0_1_0, EE_READ_DATA,
@@ -81,62 +44,21 @@ static uint16_t jerry_ee_data_cnt = 16;
8144
static uint16_t jerry_writes_enabled = 0;
8245
static uint16_t jerry_ee_direct_jump = 0;
8346

84-
static char eeprom_filename[MAX_PATH];
85-
static char cdromEEPROMFilename[MAX_PATH];
86-
static bool haveEEPROM = false;
87-
static bool haveCDROMEEPROM = false;
88-
8947

9048
void EepromInit(void)
9149
{
92-
RFILE * fp;
93-
94-
/* No need for EEPROM for the Memory Track device */
95-
if (jaguarMainROMCRC32 == 0xFDF37F47)
96-
return;
97-
98-
/* Get EEPROM file names */
99-
if (strlen(vjs.romName) > 0)
100-
{
101-
sprintf(eeprom_filename, "%s%s.srm", vjs.EEPROMPath, vjs.romName);
102-
sprintf(cdromEEPROMFilename, "%s%s.cdrom.srm", vjs.EEPROMPath, vjs.romName);
103-
}
104-
else
105-
{
106-
// Use old CRC fallback...
107-
sprintf(eeprom_filename, "%s%08X.srm", vjs.EEPROMPath, (unsigned int)jaguarMainROMCRC32);
108-
sprintf(cdromEEPROMFilename, "%s%08X.cdrom.srm", vjs.EEPROMPath, (unsigned int)jaguarMainROMCRC32);
109-
}
110-
111-
/* Handle regular cartridge EEPROM */
112-
fp = rfopen(eeprom_filename, "rb");
113-
114-
if (fp)
115-
{
116-
ReadEEPROMFromFile(fp, eeprom_ram);
117-
rfclose(fp);
118-
haveEEPROM = true;
119-
}
120-
121-
// Handle JagCD EEPROM
122-
fp = rfopen(cdromEEPROMFilename, "rb");
123-
124-
if (fp)
125-
{
126-
ReadEEPROMFromFile(fp, cdromEEPROM);
127-
rfclose(fp);
128-
haveCDROMEEPROM = true;
129-
}
50+
/* EEPROM data is now loaded/saved by the frontend via
51+
* retro_get_memory_data(RETRO_MEMORY_SAVE_RAM). No file I/O here. */
13052
}
13153

13254

13355
void EepromReset(void)
13456
{
135-
if (!haveEEPROM)
136-
memset(eeprom_ram, 0xFF, 64 * sizeof(uint16_t));
137-
138-
if (!haveCDROMEEPROM)
139-
memset(cdromEEPROM, 0xFF, 64 * sizeof(uint16_t));
57+
/* Fill with 0xFF (blank EEPROM state). If the frontend has
58+
* previously saved data, it will overwrite this via the
59+
* RETRO_MEMORY_SAVE_RAM interface before the first frame. */
60+
memset(eeprom_ram, 0xFF, 64 * sizeof(uint16_t));
61+
memset(cdromEEPROM, 0xFF, 64 * sizeof(uint16_t));
14062
}
14163

14264

@@ -147,23 +69,7 @@ void EepromDone(void)
14769

14870
static void EEPROMSave(void)
14971
{
150-
// Write out regular cartridge EEPROM data
151-
RFILE * fp = rfopen(eeprom_filename, "wb");
152-
153-
if (fp)
154-
{
155-
WriteEEPROMToFile(fp, eeprom_ram);
156-
rfclose(fp);
157-
}
158-
159-
// Write out JagCD EEPROM data
160-
fp = rfopen(cdromEEPROMFilename, "wb");
161-
162-
if (fp)
163-
{
164-
WriteEEPROMToFile(fp, cdromEEPROM);
165-
rfclose(fp);
166-
}
72+
/* No-op: the frontend persists SRAM via retro_get_memory_data. */
16773
}
16874

16975

0 commit comments

Comments
 (0)