Skip to content

Commit d1869d4

Browse files
authored
Merge pull request #102 from Provenance-Emu/libretro/fix/save-file-paths
Fix save file paths: use libretro SRAM interface
2 parents 267ad53 + 2c449e2 commit d1869d4

10 files changed

Lines changed: 942 additions & 210 deletions

File tree

.github/workflows/regression-test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ jobs:
7777
chmod +x "${MINIRETRO_BIN}"
7878
./test/regression_test.sh ./${{ matrix.config.core }}
7979
80+
- name: Run SRAM interface tests
81+
run: ./test/sram_test.sh ./${{ matrix.config.core }}
82+
8083
- name: Upload diff artifacts
8184
if: failure()
8285
uses: actions/upload-artifact@v4

.github/workflows/release.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: [ 'v*' ]
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
build:
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
config:
16+
- platform: linux-x86_64
17+
artifact: virtualjaguar_libretro.so
18+
os: ubuntu-latest
19+
cc: gcc
20+
cxx: g++
21+
22+
- platform: linux-aarch64
23+
artifact: virtualjaguar_libretro.so
24+
os: ubuntu-24.04-arm
25+
cc: gcc
26+
cxx: g++
27+
28+
- platform: macos-arm64
29+
artifact: virtualjaguar_libretro.dylib
30+
os: macos-latest
31+
cc: clang
32+
cxx: clang++
33+
34+
- platform: windows-x86_64
35+
artifact: virtualjaguar_libretro.dll
36+
os: windows-latest
37+
cc: gcc
38+
cxx: g++
39+
shell: 'msys2 {0}'
40+
41+
name: build-${{ matrix.config.platform }}
42+
runs-on: ${{ matrix.config.os }}
43+
44+
defaults:
45+
run:
46+
shell: ${{ matrix.config.shell || 'bash' }}
47+
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- name: Set up MSYS2
52+
if: runner.os == 'Windows'
53+
uses: msys2/setup-msys2@v2
54+
with:
55+
msystem: MINGW64
56+
update: false
57+
install: >-
58+
mingw-w64-x86_64-gcc
59+
make
60+
61+
- name: Build
62+
run: make -j4 CC=${{ matrix.config.cc }} CXX=${{ matrix.config.cxx }}
63+
64+
- name: Package
65+
run: |
66+
mkdir -p dist
67+
cp ${{ matrix.config.artifact }} dist/virtualjaguar_libretro-${{ matrix.config.platform }}${SUFFIX}
68+
env:
69+
SUFFIX: ${{ matrix.config.platform == 'windows-x86_64' && '.dll' || (matrix.config.platform == 'macos-arm64' && '.dylib' || '.so') }}
70+
71+
- name: Upload artifact
72+
uses: actions/upload-artifact@v4
73+
with:
74+
name: virtualjaguar_libretro-${{ matrix.config.platform }}
75+
path: dist/
76+
if-no-files-found: error
77+
78+
release:
79+
needs: build
80+
runs-on: ubuntu-latest
81+
steps:
82+
- uses: actions/checkout@v4
83+
84+
- name: Download all artifacts
85+
uses: actions/download-artifact@v4
86+
with:
87+
path: artifacts/
88+
89+
- name: Create release
90+
env:
91+
GH_TOKEN: ${{ github.token }}
92+
run: |
93+
TAG="${GITHUB_REF_NAME}"
94+
# Collect all built binaries
95+
find artifacts/ -type f | sort
96+
97+
gh release create "${TAG}" \
98+
--title "${TAG}" \
99+
--generate-notes \
100+
artifacts/virtualjaguar_libretro-*/*

libretro.c

Lines changed: 75 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,22 @@ 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 uint8_t mtMem[0x20000];
35+
extern uint32_t jaguarMainROMCRC32;
36+
extern void (*eeprom_dirty_cb)(void);
37+
38+
/* Save buffer for RETRO_MEMORY_SAVE_RAM.
39+
* Regular carts: 128 bytes (64 x 16-bit EEPROM words, big-endian packed).
40+
* Memory Track cart (CRC 0xFDF37F47): mtMem is used directly (128K).
41+
*
42+
* The save buffer is kept in sync on every EEPROM write via eeprom_dirty_cb,
43+
* so frontends that cache the pointer always see current data. */
44+
#define EEPROM_SAVE_SIZE 128 /* 64 x 16-bit words, big-endian */
45+
#define MT_SAVE_SIZE 0x20000 /* 128K Memory Track */
46+
static uint8_t eeprom_save_buf[EEPROM_SAVE_SIZE];
47+
static void eeprom_pack_save_buf(void);
48+
static void eeprom_unpack_save_buf(void);
3449

3550
static retro_video_refresh_t video_cb;
3651
static retro_input_poll_t input_poll_cb;
@@ -39,6 +54,7 @@ static retro_environment_t environ_cb;
3954
retro_audio_sample_batch_t audio_batch_cb;
4055

4156
static bool libretro_supports_bitmasks = false;
57+
static bool save_data_needs_unpack = false;
4258

4359
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
4460
void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
@@ -719,26 +735,6 @@ static void update_input(void)
719735
}
720736
}
721737

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-
}
741-
742738
/************************************
743739
* libretro implementation
744740
************************************/
@@ -897,9 +893,7 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
897893

898894
bool retro_load_game(const struct retro_game_info *info)
899895
{
900-
char slash;
901896
unsigned i;
902-
const char *save_dir = NULL;
903897
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
904898

905899
struct retro_input_descriptor desc[] = {
@@ -981,31 +975,8 @@ bool retro_load_game(const struct retro_game_info *info)
981975

982976
check_variables();
983977

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-
}
978+
/* Register EEPROM dirty callback so the save buffer stays in sync */
979+
eeprom_dirty_cb = eeprom_pack_save_buf;
1009980

1010981
JaguarInit(); // set up hardware
1011982
memcpy(jagMemSpace + 0xE00000,
@@ -1023,6 +994,11 @@ bool retro_load_game(const struct retro_game_info *info)
1023994
JaguarLoadFile((uint8_t*)info->data, info->size);
1024995
JaguarReset();
1025996

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

@@ -1055,22 +1031,55 @@ unsigned retro_api_version(void)
10551031
return RETRO_API_VERSION;
10561032
}
10571033

1034+
/* Pack eeprom_ram[] into the save buffer (big-endian byte order).
1035+
* Called on every EEPROM write via eeprom_dirty_cb so the buffer
1036+
* is always up-to-date for frontends that cache the pointer. */
1037+
static void eeprom_pack_save_buf(void)
1038+
{
1039+
unsigned i;
1040+
for (i = 0; i < 64; i++)
1041+
{
1042+
eeprom_save_buf[(i * 2) + 0] = eeprom_ram[i] >> 8;
1043+
eeprom_save_buf[(i * 2) + 1] = eeprom_ram[i] & 0xFF;
1044+
}
1045+
}
1046+
1047+
/* Unpack the save buffer back into eeprom_ram[].
1048+
* Called once after the frontend loads .srm data. */
1049+
static void eeprom_unpack_save_buf(void)
1050+
{
1051+
unsigned i;
1052+
for (i = 0; i < 64; i++)
1053+
eeprom_ram[i] = ((uint16_t)eeprom_save_buf[(i * 2) + 0] << 8)
1054+
| eeprom_save_buf[(i * 2) + 1];
1055+
}
1056+
10581057
void *retro_get_memory_data(unsigned type)
10591058
{
1060-
if(type == RETRO_MEMORY_SYSTEM_RAM)
1059+
if (type == RETRO_MEMORY_SYSTEM_RAM)
10611060
return jaguarMainRAM;
1062-
else if (type == RETRO_MEMORY_SAVE_RAM)
1063-
return eeprom_ram;
1064-
else return NULL;
1061+
if (type == RETRO_MEMORY_SAVE_RAM)
1062+
{
1063+
/* Memory Track cart uses 128K NVRAM directly */
1064+
if (jaguarMainROMCRC32 == 0xFDF37F47)
1065+
return mtMem;
1066+
/* Regular carts: return the pre-packed save buffer */
1067+
return eeprom_save_buf;
1068+
}
1069+
return NULL;
10651070
}
10661071

10671072
size_t retro_get_memory_size(unsigned type)
10681073
{
1069-
if(type == RETRO_MEMORY_SYSTEM_RAM)
1074+
if (type == RETRO_MEMORY_SYSTEM_RAM)
10701075
return 0x200000;
1071-
else if (type == RETRO_MEMORY_SAVE_RAM)
1072-
return 128;
1073-
else return 0;
1076+
if (type == RETRO_MEMORY_SAVE_RAM)
1077+
{
1078+
if (jaguarMainROMCRC32 == 0xFDF37F47)
1079+
return MT_SAVE_SIZE;
1080+
return EEPROM_SAVE_SIZE;
1081+
}
1082+
return 0;
10741083
}
10751084

10761085
void retro_init(void)
@@ -1097,6 +1106,16 @@ void retro_run(void)
10971106
{
10981107
bool updated = false;
10991108

1109+
/* On the first frame, unpack save data that the frontend loaded
1110+
* into our RETRO_MEMORY_SAVE_RAM buffer after retro_load_game(). */
1111+
if (save_data_needs_unpack)
1112+
{
1113+
save_data_needs_unpack = false;
1114+
if (jaguarMainROMCRC32 != 0xFDF37F47)
1115+
eeprom_unpack_save_buf();
1116+
/* Memory Track: mtMem was written directly, no unpack needed. */
1117+
}
1118+
11001119
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
11011120
check_variables();
11021121

0 commit comments

Comments
 (0)