ssokolow/saveswap

Any tricks to get working with the recent versions of Mupen64 libretro core ?

requeijaum opened this issue · 14 comments

I couldn't convert my Zelda OoT savegame with the existent tools on GitHub.
Only swapping couldn't do the trick whatsoever.

As seen on:

I haven't had time to support that, but the issue is that the libretro core has a custom save format unlike other emulators, including standalone versions of Mupen64 and Mupen64Plus.

According to this comment (which includes a snip of source code), it involves concatenating space for all possible save types together in a single file:

If I'm reading the source correctly, the TL;DR is that you have to identify what kind of save you have, then insert the data in the correct place in a file with this structure:

  • 2048 bytes of space for EEPROM-based save data at offset 0 (starting with the first byte in the file)
  • 131072 bytes of space for memory pak data at offset 2048 (immediately after the EEPROM data)
  • 131072 bytes of space for Flash-based save data at offset 133120 (immediately after the memory pak data)
  • 32768 bytes of space for SRAM-based (battery-backed) save data at offset 264192 (Immediately after the Flash data)

To be perfectly honest, I think it's a stupid, wasteful format and something like this would have been much better for ensuring a single save file, regardless of the game:

  • A byte or two which indicate(s) the presence or absence, type, and amount of on-cartridge save data (eg. the four possible combinations of the first two bits indicate none/eeprom/flash/sram and the remaining bits indicate the amount when multiplied by some constant, since you can't buy memory chips in truly arbitrary sizes.)
  • A byte or two which indicate(s) the presence or absence of mempak data and the amount of it (Same idea, but you only need one high bit for present/absent)
  • The on-cartridge save data, if present
  • The mempak data, if present

No need to have every save file 296960 bytes long, even though three of those fields are mutually exclusive (You never have EEPROM, SRAM, and Flash in the same cartridge), and no need to resort to external details parsed from the ROM or looked up in a database to know for certain which fields are actual data and which are empty when you want to convert back to the standard kind of save files.

Anyway, while I don't have time to write a general-purpose solution for saveswap right now, I can leave this open as a TODO and whip up a one-off hack you can copy-paste into a terminal window that only works for SRAM-based saves like OOT uses. What OS do you use?

As for Kyle Niewiada's blog, I can't say anything about that.

While I try to find time to narrow down the cause of my N64's reset problem, I do my ROM and save file dumping using a Retrode 2 with the optional N64 plugin, so 99% of that is automatic. Once I'd installed a suitably new firmware (v0.23 or newer for N64 saves), dumping my childhood OoT save and getting it to play on normal, non-libretro Mupen64Plus was as simple as:

  1. Clean my OoT cartridge's contacts
  2. Set Retrode's voltage switch to 3.3v (N64 or GBA. The alternative is 5v for all others.)
  3. Plug OoT into the N64 adapter and the N64 adapter into the Retrode's Genesis slot
  4. Plug the Retrode into my PC and double-click the removable disk that appears
  5. If the filenames look wrong or the auto-detection fails, Re-clean the contacts, re-seat all contacts in their sockets, and try again. (May need to repeat this if the contacts are particularly cruddy or have spots of corrosion.)
  6. Click-and-drag the .sra file to my hard drive
  7. Run saveswap on the .sra file
  8. Since I use Linux, copy it to ~/.local/share/mupen64plus/save/Legend of Zelda, The - Ocarina of Time (U) (V1.2) [!].sra where the Linux version of Mupen64Plus expects it to be for my version of OoT.

Heck, it'll even do memory pak dumping that easily as long as you have an N64 adapter with the controller ports populated and an N64 controller to plug into it. (They have trouble maintaining a reliable supply of the controller connectors at the low volumes they need, so, sometimes, you can only buy N64 adapters where you have to source and solder your own controller ports if you want them.)

Thanks for the write-up, @ssokolow !

I'm using macOS El Capitan 10.11.6. I have bash, python3 and other scripting utilities available as a kind of swiss-army knife for quick hacks. Anything in shell script or python3 would be awesome and be used as a start for fixing this (dumb) issue.

My objective is simple: get my working SRAM from Mupen64Plus AE (on Android) out and play on mupen64plus libretro core.

My objective is simple: get my working SRAM from Mupen64Plus AE (on Android) out and play on mupen64plus libretro core.

No byte-swapping should be needed then. Just converting the format.

I'm using macOS El Capitan 10.11.6. I have bash, python3 and other scripting utilities available as a kind of swiss-army knife for quick hacks. Anything in shell script or python3 would be awesome and be used as a start for fixing this (dumb) issue.

As long as it's not Windows, we're good. I can craft a simple script that works on my Linux box and then you can run it on macOS. :)

My first idea is to use dd to create an appropriately-sized file full of zeroes, then to overwrite the appropriate segment with the contents of the SRAM file.

I don't have the libretro core installed right now, so I haven't tested it, but this is my first attempt:

# Generate a properly-sized file filled with zeroes for the combined save file
dd if=/dev/zero iflag=count_bytes count=296960 of=OUTPUT_SAVE_FILE_NAME

# Overwrite the proper region of the file with the contents of the SRAM-only save file
dd if=INPUT_SAVE_FILE_NAME iflag=count_bytes oflag=seek_bytes seek=264192 count=32768 of=OUTPUT_SAVE_FILE_NAME

I will test it...

I'm using MacPorts, therefore: GNU dd is "gdd" - but I think Apple/BSD dd will do the same job.

gdd if=/dev/zero iflag=count_bytes count=296960 of=$OUTPUT_SAVE_FILE_NAME
gdd if=$INPUT_SAVE_FILE_NAME iflag=count_bytes oflag=seek_bytes seek=264192 count=32768 of=$OUTPUT_SAVE_FILE_NAME

Could load the save game using the normal SRA file copied from Mupen64Plus AE and the byte-swapped file. The emu core just skips loading it.

Output from RetroArch v1.7.4 with mupen64plus 2.0-rc2 c9223cc

[libretro INFO] mupen64plus: No version number in 'Core' config section. Setting defaults.
[libretro INFO] EmuThread: M64CMD_ROM_OPEN
[libretro INFO] mupen64plus: Goodname: THE LEGEND OF ZELDA (unknown rom)
[libretro INFO] mupen64plus: Headername: THE LEGEND OF ZELDA
[libretro INFO] mupen64plus: Name: THE LEGEND OF ZELDA 
[libretro INFO] mupen64plus: MD5: 5BD1FE107BF8106B2AB6650ABECD54D6
[libretro INFO] mupen64plus: CRC: ec7011b7 7616d72b
[libretro INFO] mupen64plus: Imagetype: .z64 (native)
[libretro INFO] mupen64plus: Rom size: 33554432 bytes (or 32 Mb or 256 Megabits)
[libretro INFO] mupen64plus: ClockRate = f
[libretro INFO] mupen64plus: Version: 1449
[libretro INFO] mupen64plus: Manufacturer: 43
[libretro INFO] mupen64plus: Cartridge_ID: 4c5a
[libretro INFO] mupen64plus: Country: USA
[libretro INFO] mupen64plus: PC = 80000400
[libretro INFO] mupen64plus: Save type: 5
[libretro INFO] EmuThread: M64CMD_ROM_GET_HEADER
**[INFO] Skipping SRAM load..**
[INFO] Version of libretro API: 1
[INFO] Compiled against API: 1

Line 1302 on https://github.com/libretro/RetroArch/blob/7ffbba618794b3a721ac95ec28feada459102941/command.c produces this message. Is there some hash checking for the loaded ROM?

Line 1429 of https://github.com/libretro/RetroArch/blob/f4f1212372a1b7b344313698ab0da2f494078a1c/tasks/task_save.c generates the boolean value used to check if the SRAM can be loaded...

... and don't know about the core implementation of this.

Verified savegame working with https://bkacjios.github.io/OOT-Save-Converter/ just for the sake of truly knowing that it's the file that I got from Mupen64Plus Android Edition.

zeldaoot-old.sra.zip

Captura de Tela 2019-05-02 às 11 18 36

I also tried:

  • booting the game on RetroArch
  • starting a new game
  • saving it at Kokiri Forest - through the menu
  • quitting RetroArch
  • "zeldaoot.srm" generated by the emulator with my new game
  • writing the old SRA to the new SRM generated now
  • gdd if=/Volumes/320GB/temp/downloads-30mar2019/saveswap/zeldaoot-old.sra iflag=count_bytes oflag=seek_bytes seek=264192 count=32768 of=/Users/requeijaum/Documents/RetroArch/saves/zeldaoot.srm
  • booting up the game
  • seeing that my Kokiri Forest new game was still there
  • since my old save was on Fire Temple... I failed again

Is there any wrong offset? DD didn't overwrite the SRAM "partition" generated by RetroArch with my old one.

Got it

The offset was wrong... so I check my new generated SRM to get the real offset - 0x20800 instead of 0x800 - my first guess - and 0x40800 - your offset.
Changed the value and disabled truncating of the output... and voilà!

gdd if=/dev/zero iflag=count_bytes count=296960 of=zeldinha.srm ; gdd if=/Volumes/320GB/temp/downloads-30mar2019/saveswap/zeldaoot-old.sra  iflag=count_bytes oflag=seek_bytes seek=133120  conv=notrunc  of=zeldinha.srm
mv zeldinha.srm /Users/requeijaum/Documents/RetroArch/saves/zeldaoot.srm

Here is a pic.
Left is generated by RetroArch. Right is the one with the injected SRA from old Mupen64Plus AE.

Captura de Tela 2019-05-02 às 17 48 03

Huh. Well, I did say that I didn't have the RetroArch core installed to test with right now. Thanks for figuring that out.

I'll leave this open as a reminder to myself for when I have time to work on this again.

You must check for the current save file struct, in the mupen64plus core source-code, since the SRAM is not the last piece of the file. Check the other issues referenced by me on this thread.

Thanks, @ssokolow !

I was operating on the assumption that it would detect and transparently upgrade save files from earlier versions, and that the save file format I linked to was correct for an earlier version.

sr2ds commented

@requeijaum, você é o cara!

Thankyou! Work for me! But I use dd

dd if=/dev/zero iflag=count_bytes count=296960 of=zeldinha.srm ; dd if=/Volumes/320GB/temp/downloads-30mar2019/saveswap/zeldaoot-old.sra  iflag=count_bytes oflag=seek_bytes seek=133120  conv=notrunc  of=zeldinha.srm
mv zeldinha.srm /Users/requeijaum/Documents/RetroArch/saves/zeldaoot.srm