mirror of
https://github.com/s0liton/ps5-romm.git
synced 2026-10-28 22:01:45 +01:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05719d8d64 |
No files matched your search
@@ -20,7 +20,7 @@
|
||||
CHDR_SRCS := $(filter-out %microflac.cpp,$(wildcard third_party/libchdr/src/*.c)) \
|
||||
third_party/libchdr/deps/lzma/src/LzmaDec.c third_party/libchdr/deps/zstd/zstddeclib.c
|
||||
SRCS := src/main.c src/util.c src/http.c src/romm.c src/config.c src/state.c \
|
||||
src/profiles.c src/sync.c src/watch.c src/detect.c src/ra2.c src/playlist.c src/autostart.c src/memcard.c src/zip.c src/bundle.c src/gcmem.c src/statefmt.c src/events.c src/gameid.c src/covers.c src/library.c src/firmware.c src/pair.c src/web.c src/update.c src/jsonlean.c \
|
||||
src/profiles.c src/sync.c src/watch.c src/detect.c src/ra2.c src/playlist.c src/autostart.c src/memcard.c src/zip.c src/bundle.c src/gcmem.c src/statefmt.c src/events.c src/gameid.c src/covers.c src/library.c src/firmware.c src/pair.c src/web.c src/update.c src/presets.c src/jsonlean.c \
|
||||
third_party/cJSON.c third_party/md5.c third_party/monocypher.c third_party/monocypher-ed25519.c \
|
||||
$(CHDR_SRCS)
|
||||
HDRS := $(wildcard src/*.h) third_party/cJSON.h third_party/md5.h third_party/monocypher.h third_party/monocypher-ed25519.h
|
||||
|
||||
@@ -30,42 +30,52 @@ A [RomM](https://romm.app) companion client for jailbroken PS5 consoles (etaHEN
|
||||
|
||||
## Supported Emulators and Games
|
||||
- RommPS has built-in profiles for all known emulators on the PS5, and will set the correct default game, save/save state, and bios directories for them. Since there is a huge influx of new emulators popping up, I will be doing my very best to keep up with them and add profiles accordingly.
|
||||
- You can also manually set the directories for each emulator/platform via the PS5 app or or the Web UI.
|
||||
|
||||
- You can also manually set the directories for each emulator/platform via the PS5 app or or the Web UI. **RommPS can support ANY emulator truly.**
|
||||
### Note
|
||||
- RommPS relies on your emulator **NOT** being sandboxed. Some emulators require a separate payload that must be launched to be able to access outside of the sandbox, so make sure you've followed their instructions to properly install. Sandboxed emulators will not be able to read games or save files.
|
||||
|
||||
| Emulator | Systems | Game Downloads | Saves | Comments |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [RetroArch](https://github.com/mihawk-99/PS5_RetroArch) | NES, Famicom Disk System, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis/Mega Drive, Master System, Game Gear, Sega CD, SG-1000, arcade (FinalBurn Neo), PS2 (LRPS2), PSP (PPSSPP), GameCube and Wii (Dolphin) | Full Support | Full Support | - |
|
||||
| [RetroArch](https://github.com/mihawk-99/PS5_RetroArch) | NES, Famicom Disk System, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis/Mega Drive, Master System, Game Gear, Sega CD, SG-1000, arcade (FinalBurn Neo), PS2 (PS5SX2 on RetroArch 2.0, LRPS2 before it), PSP (PPSSPP), GameCube and Wii (Dolphin). Any other core you install for a system RomM knows (PS1, N64, Dreamcast, Saturn, 3DS and friends) gets picked up too | Full Support | Full Support | - |
|
||||
| [PSXS5](https://github.com/SynoPiia/PSXS5) | PS1 | Full Support | One memory card per game, and states | States work with RomM's web player (PCSX-ReARMed or Beetle, whichever PSXS5 is set to) |
|
||||
| [SwanStationPS5](https://github.com/darkxex/SwanStationPS5) | PS1 | Full Support | One memory card per game, and states | Plays PSXS5's games, keeps its own saves in `/data/SwanStationPS5`. Its states only load in SwanStationPS5 |
|
||||
| PSSwanStation | PS1 | Full Support | One memory card per game, and states | Its states only load in PSSwanStation |
|
||||
| [PS5SX2](https://github.com/Swordpdf/PS5SX2) | PS2 | Full Support | Its two shared memory cards, backed up to RomM (5.3 or newer) | - |
|
||||
| [PS5N64](https://github.com/Globaleliteee/PS5N64) | N64 | Full Support | Full Support | - |
|
||||
| [PS5NES](https://github.com/Globaleliteee/PS5NES) | NES | Untested | Untested | Preset uses `/data/PS5NES/games` and `/data/PS5NES/saves`. GH page gone |
|
||||
| PSFlyCast | Dreamcast | Full Support | Each game's own VMU; the shared VMUs are backed up as RomM memory cards | Save states don't sync yet |
|
||||
| PS64 | Commodore 64 | Full Support | Save disks | Games save into their own disk images. No save states |
|
||||
| PS5CEMU-HAR | Wii U, 3DS, DS | Full Support | Wii U and 3DS saves, 3DS states | 3DS states only load in PS5CEMU-HAR. DS is games and BIOS files only, for now |
|
||||
| [Porpoise](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5) | Wii/GameCube | Full Support | N/A | Couldn't exit sandbox w/o crash. [Issue](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/issues/18) reported. Downloading games should work if the emulator is actually working for you.|
|
||||
| [ProsperoEden](https://github.com/blackbearreloaded/ProsperoEden) | Switch | Manual Support | Researching | - |
|
||||
| XPSemu | Xbox | Full Support | Not synced | Games and BIOS files only: its saves live inside its hard disk image |
|
||||
| Seedee5 | Philips CD-i | Full Support | Not synced | Games and BIOS files only: it keeps saves as folders, and only after a clean exit |
|
||||
| [ProsperoEden](https://github.com/blackbearreloaded/ProsperoEden) | Switch | Full Support | Not synced | Games and keys only: saves are kept per Switch user profile |
|
||||
| [PS5X360](https://github.com/BrinooTk/PS5X360) | Xbox 360 | Manual Support | Researching | - |
|
||||
| Mednafen | PS1, NES, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis/Mega Drive, Master System, Game Gear, PC Engine, Neo Geo Pocket, WonderSwan, Virtual Boy, Lynx | Full Support | Full Support | Built-in profile, off until you turn it on |
|
||||
| PS2 ISOs (for PS2 FPKG tools) | PS2 | Full Support | Not synced | Downloads PS2 ISOs to a USB drive (`/mnt/usb0/ps2`) for PS2 FPKG tools. Off until you turn it on |
|
||||
|
||||
|
||||
To give some meaning to the table above. **Full support** means RommPS can detect and properly set game/save directories for that emulator. **Manual Support** means you can use it to sync games and/or saves, but you'll need to point it to the directories yourself. This typically means that full support is coming shortly. Broken or N/A means there was a problem keeping us from sync games and saves, or the emulator just fall out doesn't work or crashes. **Researching** means I am seeing what is possible when it comes to that sync type for the given emulator or game system.
|
||||
|
||||
### Important: Save States
|
||||
Some users have recently reported issues with being able to pull down save states from their servers. They don't see them on the destination save folder for that emulator, or if they do, they don't work. Save states typically cannot be used across different emulators (cores), and even within the same core, the definition of a save state may different with file version, and version of the app.
|
||||
|
||||
**Simply put**:
|
||||
- Emulator core must be the same.
|
||||
- Different schema versions of save states are sometimes incompatible with each other.
|
||||
|
||||
For the 1.2.0 release I will be including some fixes as well as some conversion logic to try and convert save state formats for max compatibility whenever possible. This is also why we have an upload only or upload and download option for save states. Maybe you don't care about syncing, but want to back them up, in case something happens. That's a good use case. I want to be blunt with you: Save states are kind of useless. Yes, they're great for recovering your progress after you feel asleep and forgot to save, but you truly should be saving progress with the in-game saves, not save status.
|
||||
|
||||
### PS4 (experimental) ###
|
||||
|
||||
| Emulator | Systems |
|
||||
| --- | --- |
|
||||
| RetroArch | The RetroArch systems above, wherever the PS4 build has the core |
|
||||
| pSNES, pNES, pGEN, pGBA, pFBN | SNES; NES; Genesis, Master System, Game Gear, Sega CD, SG-1000; GBA, GBC, GB; arcade |
|
||||
|
||||
Only RetroArch has been tested on the PS4 so far.
|
||||
| Emulator | Systems | Saves | Comments |
|
||||
| --- | --- | --- | --- |
|
||||
| RetroArch | The RetroArch systems above, wherever the PS4 build has the core | Saves and states | - |
|
||||
| pSNES | SNES | Saves and states | - |
|
||||
| pNES | NES, Famicom Disk System | Saves and states | - |
|
||||
| pGEN | Genesis/Mega Drive, Master System, Game Gear, Sega CD, SG-1000 | Saves and states | - |
|
||||
| pGBA | Game Boy Advance, Game Boy Color, Game Boy | Saves and states | Saves and states share its saves folder |
|
||||
| pFBN | Arcade (FinalBurn Neo), Neo Geo Pocket (Color), ColecoVision, MSX, ZX Spectrum, SuperGrafx, Channel F | Saves and states | Arcade saves are the games' own memory (EEPROMs), backed up as they are |
|
||||
| mGBA | Game Boy Advance, Game Boy Color, Game Boy | Saves and states | - |
|
||||
| SNESUI | SNES | Saves and states | - |
|
||||
| EmuSuNi | SNES, NES, N64 | Saves and states | Set its N64 folder to `/data/EmuSuNi/n64` in its settings |
|
||||
| NPS64 | N64 | Saves and states | `.z64` dumps |
|
||||
| DolphinPS4 | GameCube, Wii | Saves and states | - |
|
||||
| Resonance | Dreamcast | Each game's own VMU, and states; the shared VMUs are backed up as RomM memory cards | - |
|
||||
| PS4SX2 | PS2 | Its two shared memory cards, backed up to RomM (5.3 or newer) | No save states yet |
|
||||
| Mednafen, PS2 ISOs | As on the PS5 | As on the PS5 | Built-in profiles, off until you turn them on |
|
||||
|
||||
### Save states
|
||||
Save states only load in the emulator core that made them, sometimes only in the same version of it. RommPS backs every state up to RomM, and with **Save states: Back up and restore** in Settings it brings one down only to an emulator that can load it:
|
||||
@@ -97,9 +107,12 @@ If you are interested in downloading PS4 and PS5 games from your RomM server, Ro
|
||||
|
||||
Install **RommPS** from [ProsperoStore](https://homebrew.page) and open it. It'll set up the background daemon, and take you through a setup wizard. That's it: RommPS carries the RomM Sync payload and starts it for you. From then on it starts with your HEN, so after a jailbreak or waking the console it's ready to go.
|
||||
|
||||
Not using the store? Download `PPSA76677.zip` from the releases page, unzip it and copy the `PPSA76677` folder to `/data/homebrew/` on the console.
|
||||
### Manual Install
|
||||
|
||||
If you'd rather run just the payload, without the app:
|
||||
Not using the store? Download `PPSA76677.zip` from the releases page, unzip it and copy the `PPSA76677` folder to `/data/homebrew/` on the console. Just run the RommPS app and it'll copy the background service for you.
|
||||
|
||||
## Payload Only - No PS5 App
|
||||
If you'd rather run just the payload, without the app and adminster via the Web UI:
|
||||
|
||||
1. Download the latest `romm-sync.elf` from the releases page.
|
||||
2. Send it to the console (or use whichever favorite tool you wish):
|
||||
@@ -108,10 +121,6 @@ If you'd rather run just the payload, without the app:
|
||||
```
|
||||
3. Set it up at `http://<ps5-ip>:8780` from another device.
|
||||
|
||||
On the PS5 the payload doesn't update itself: new versions come with RommPS. Without the app, download the new `romm-sync.elf` yourself.
|
||||
|
||||
RomM Sync starts with your HEN, and setup turns this on for you. Both HENs start `payloads/romm-sync.elf` when `romm-sync.elf.auto_start` sits next to it, and RomM Sync puts both in every HEN's folder on the console (`/data/etaHEN/payloads` and `/data/OnionHEN/payloads`), so switching HENs keeps working. If a payload manager like pldmgr has its own copy, that copy is kept up to date too. If you build it yourself, `make install PS5_HOST=<ps5-ip>` copies it over FTP (add `PS5_FTP=ftp://<ps5-ip>:1337 PS5_HEN_DIR=/data/OnionHEN` for onionHEN's FTP plugin).
|
||||
|
||||
### PS4
|
||||
|
||||
> **Experimental.** The PS4 build has only been tested with RetroArch so far. Standalone emulators may work, but haven't been confirmed. I spent all my focus on the PS5 build, so I didn't get to test as much, so consider it unstable and there may be gremlins for now.
|
||||
@@ -145,13 +154,7 @@ The setup walks you through five steps:
|
||||
|
||||
**Back up your saves before the first sync.** Copy your emulator save folders to a computer or USB drive. RomM Sync keeps a local copy before replacing any file, but your own backup is the safest option.
|
||||
|
||||
## PS2 Memory Cards
|
||||
|
||||
LRPS2 keeps every game on one shared memory card by default. RomM stores saves per game, so pick one of these during setup:
|
||||
- **One card per game.** Works with any RomM version. In RetroArch, open the LRPS2 core options and turn off _Shared Memory Cards_. After that, each game's card syncs like any other save. Saves already on the shared card stay there.
|
||||
- **Back up the shared card.** Needs RomM 5.3.0 or newer. The whole card is uploaded when it changes, and you can restore it from Settings in the web UI. It isn't merged with other devices.
|
||||
|
||||
PS5SX2 used 2 shared memory cards in slots 1 and 2. While it does support per-game memory cards, they need to be specified in a per-game ini file. In 1.2.0, I plan on adding the ability to directly modify those .ini files in PS5SX2 to write the proper per-game memory card path, effectively making RommPS handle per-game memory cards for it gracefully. Until then, I suggest using whichever emulator fits your preferred method (PS5SX2 for shared mem cards or use LRPS2 in retroarch for per-game support). If you have both installed, RommPS will sync game and saves, but they will be separate and different saves (PS5SX2 will download your shared memory cards, LRPS2 will grab the memory card per game). I know...it's confusing, but this is just a side-effect of the boom in emulators being released.
|
||||
|
||||
## Other "Memory Card" Consoles
|
||||
PSP, GameCube and Wii saves are synced as one zip per game, the same format other RomM clients use. Depending on the client, they may or may not support unzipping the saves (I don't truly know), but this client does!
|
||||
|
||||
+2
-2
@@ -98,13 +98,13 @@ Games and BIOS files only: saves live inside its hard disk image.
|
||||
|
||||
### PS5CEMU-HAR
|
||||
|
||||
Wii U (Cemu), 3DS (Azahar) and DS (melonDS). Wii U and 3DS saves sync; 3DS save states load only in PS5CEMU-HAR. DS: games and BIOS files only for now.
|
||||
Wii U (Cemu), 3DS (Azahar) and DS (melonDS). Saves sync for all three; 3DS and DS save states load only in PS5CEMU-HAR.
|
||||
|
||||
| System | Games | Saves | Save files | Saved as | States | State files | Memory cards |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| wiiu | `{root}/games` | `{rom_dir}` | a zip per game (wiiu), from `{root}/mlc01/usr/save` | | `{rom_dir}` | `.state*` | |
|
||||
| 3ds | `{root}/azahar/games` | `{rom_dir}` | a zip per game (3ds), from `{root}/azahar/sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/title` | | `{root}/azahar/states` | `.cst` | |
|
||||
| nds | `{root}/melonds/games` | not synced | | | not synced | | |
|
||||
| nds | `{root}/melonds/games` | `{root}/melonds/saves` | `.sav` | `{rom_stem}{ext}` | `{root}/melonds/states` | `.ml*` | |
|
||||
|
||||
### ProsperoEden
|
||||
|
||||
|
||||
+164
-159
@@ -1,160 +1,165 @@
|
||||
[
|
||||
{
|
||||
"id": "psnes", "name": "pSNES", "title_ids": ["PSNE00001"], "detect": ["/data/psnes"],
|
||||
"profile": {
|
||||
"root": "/data/psnes", "rom_dir": "{root}/roms", "save_dir": "{root}/sram", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".srm",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"firmware": {"from": {"satellaview": ["BS-X.bin"], "sufami-turbo": ["STBIOS.bin"]}},
|
||||
"platforms": [{"romm": ["snes", "sfam"], "dir": "snes"}]
|
||||
{
|
||||
"format": 1,
|
||||
"version": "2026.10.23.1",
|
||||
"console": "ps4",
|
||||
"emulators": [
|
||||
{
|
||||
"id": "psnes", "name": "pSNES", "title_ids": ["PSNE00001"], "detect": ["/data/psnes"],
|
||||
"profile": {
|
||||
"root": "/data/psnes", "rom_dir": "{root}/roms", "save_dir": "{root}/sram", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".srm",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"firmware": {"from": {"satellaview": ["BS-X.bin"], "sufami-turbo": ["STBIOS.bin"]}},
|
||||
"platforms": [{"romm": ["snes", "sfam"], "dir": "snes"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pnes", "name": "pNES", "title_ids": ["PNES00001"], "detect": ["/data/pnes"],
|
||||
"profile": {
|
||||
"root": "/data/pnes", "rom_dir": "{root}/roms", "save_dir": "{root}/save", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".sav",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"firmware": {"dir": "{root}"},
|
||||
"platforms": [{"romm": ["nes", "famicom"], "dir": "nes"}, {"romm": ["fds"], "dir": "fds"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pgen", "name": "pGEN", "title_ids": ["PGEN00001"], "detect": ["/data/pgen"],
|
||||
"profile": {
|
||||
"root": "/data/pgen", "rom_dir": "{root}/{platform}", "save_dir": "{root}/rams", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".srm",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"platforms": [
|
||||
{"romm": ["genesis", "genesis-slash-megadrive", "megadrive"], "dir": "megadrive"},
|
||||
{"romm": ["sms", "mastersystem"], "dir": "mastersystem"},
|
||||
{"romm": ["gamegear", "gg"], "dir": "gamegear"},
|
||||
{"romm": ["segacd", "sega-cd"], "dir": "megacd", "save_exts": ".brm"},
|
||||
{"romm": ["sg1000"], "dir": "sg1000"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pgba", "name": "pGBA", "title_ids": ["PGBA00001"], "detect": ["/data/pgba"],
|
||||
"note": "Saves and states share the saves folder.",
|
||||
"profile": {
|
||||
"root": "/data/pgba", "rom_dir": "{root}/{platform}", "save_dir": "{root}/saves", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".sav",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"platforms": [
|
||||
{"romm": ["gba"], "dir": "gba"},
|
||||
{"romm": ["gbc"], "dir": "gbc"},
|
||||
{"romm": ["gb"], "dir": "gb"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pfbn", "name": "pFBN", "title_ids": ["PFBN00001"], "detect": ["/data/pfba"],
|
||||
"note": "Arcade saves are the games' own memory (eeproms), backed up as they are.",
|
||||
"profile": {
|
||||
"root": "/data/pfba", "rom_dir": "{root}/{platform}", "save_dir": "{root}/eeproms", "bios_dir": "{root}/arcade",
|
||||
"save_exts": ".fs",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"firmware": {"dir": "{rom_dir}"},
|
||||
"platforms": [
|
||||
{"romm": ["arcade", "fbneo", "neogeoaes", "neogeomvs", "cps1", "cps2", "cps3"], "dir": "arcade"},
|
||||
{"romm": ["neo-geo-pocket-color", "neo-geo-pocket", "ngpc", "ngp"], "dir": "ngp"},
|
||||
{"romm": ["colecovision"], "dir": "coleco"},
|
||||
{"romm": ["msx"], "dir": "msx"},
|
||||
{"romm": ["zxs", "zx-spectrum"], "dir": "spectrum"},
|
||||
{"romm": ["supergrafx"], "dir": "sgx"},
|
||||
{"romm": ["channel-f", "fairchild-channel-f"], "dir": "channelf"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "emusuni", "name": "EmuSuNi", "title_ids": ["EMSN00001"], "detect": ["/data/EmuSuNi"],
|
||||
"note": "Set EmuSuNi's N64 folder to /data/EmuSuNi/n64 in its settings.",
|
||||
"profile": {
|
||||
"root": "/data/EmuSuNi", "bios_dir": "{root}/system",
|
||||
"save_exts": ".srm",
|
||||
"state_exts": ".state*", "state_format": "raw", "state_slot0": ".state1",
|
||||
"platforms": [
|
||||
{"romm": ["snes", "sfam"], "dir": "snes", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/states"},
|
||||
{"romm": ["nes", "famicom"], "dir": "nes", "rom_dir": "{root}/nes", "save_dir": "{root}/saves/nes", "state_dir": "{root}/states/nes"},
|
||||
{"romm": ["n64"], "dir": "n64", "rom_dir": "{root}/n64", "save_dir": "{root}/saves/n64", "state_dir": "{root}/states/n64", "save_format": "n64-srm", "cards": {"default": "per_game", "modes": "per_game"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dolphinps4", "name": "DolphinPS4", "title_ids": ["DLPH00010"], "detect": ["/data/DolphinPS4"],
|
||||
"note": "Dolphin, with its user folder in {root}/User. States are named after the disc's id.",
|
||||
"profile": {
|
||||
"root": "/data/DolphinPS4", "rom_dir": "{root}/games", "save_dir": "{root}/User",
|
||||
"state_dir": "{root}/User/StateSaves", "state_exts": ".s0*", "state_format": "raw",
|
||||
"state_stems": "{game_id}", "state_name": "{game_id}{ext}",
|
||||
"firmware": {"dir": "{root}/User/GC", "only_named": true, "names": {"b17148254a5799684c7d783206504926": "USA/IPL.bin", "fc924a7c879b661abc37cec4f018fdf3": "USA/IPL.bin", "019e39822a9ca3029124f74dd4d55ac4": "USA/IPL.bin", "db92574caab77a7ec99d4605fd6f2450": "EUR/IPL.bin", "339848a0b7c2124cf155276c1e79cbd0": "EUR/IPL.bin", "0cdda509e2da83c85bfe423dd87346cc": "EUR/IPL.bin"}},
|
||||
"platforms": [
|
||||
{"romm": ["ngc", "gamecube"], "dir": "gamecube", "emulator": "dolphin", "save_layout": "gci", "bundle_dirs": "{root}/User/GC", "cards": {"default": "per_game", "modes": "per_game"}},
|
||||
{"romm": ["wii"], "dir": "wii", "emulator": "dolphin", "save_layout": "wii", "bundle_dirs": "{root}/User/Wii"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resonance", "name": "Resonance", "title_ids": ["DCST00001"], "detect": ["/data/Resonance"],
|
||||
"note": "Each game's own memory card syncs as its save; the shared ones are backed up as RomM memory cards.",
|
||||
"profile": {
|
||||
"root": "/data/Resonance", "rom_dir": "{root}/games", "save_dir": "{root}/data", "bios_dir": "{root}/data",
|
||||
"save_exts": ".bin", "disc_id": "dreamcast",
|
||||
"save_stems": "{game_id}_vmu_save_A1", "save_name": "{game_id}_vmu_save_A1{ext}",
|
||||
"memcards": "{root}/data/vmu_save_A1.bin,{root}/data/vmu_save_B1.bin,{root}/data/vmu_save_C1.bin,{root}/data/vmu_save_D1.bin",
|
||||
"state_dir": "{root}/data", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}_1,{rom_stem}_2,{rom_stem}_3", "state_name": "{rom_stem}{ext}",
|
||||
"firmware": {"names": {"e10c53c2f8b90bab96ead2d368858623": "dc_boot.bin"}, "from": {"arcade": ["naomi.zip", "naomi2.zip", "awbios.zip"]}},
|
||||
"platforms": [{"romm": ["dc", "dreamcast"], "dir": "dreamcast", "save_format": "dc-vmu", "cards": {"default": "per_game", "modes": "per_game,shared", "change": "Turn on Own memory card for each game in its settings (players 2 to 4 always share theirs)", "setting": {"file": "{root}/emu.cfg", "key": "PerGameVmu", "per_game": "yes,true", "shared": "no,false"}}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps4sx2", "name": "PS4SX2", "title_ids": ["PCSX20007"], "detect": ["/data/PS4SX2"],
|
||||
"note": "Two memory cards for every game, backed up to RomM (5.3 or newer). No save states yet.",
|
||||
"profile": {
|
||||
"root": "/data/PS4SX2", "rom_dir": "{root}/roms", "bios_dir": "{root}/bios",
|
||||
"sync_saves": false, "memcards": "{root}/memcards/Mcd001.ps2,{root}/memcards/Mcd002.ps2",
|
||||
"firmware": {"pick_one": ["21038400dc633070a78ad53090c53017", "dc69f0643a3030aaa4797501b483d6c4", "80ac46fa7e77b8ab4366e86948e54f83", "93ea3bcee4252627919175ff1b16a1d9", "d3e81e95db25f5a86a7b7474550a2155"]},
|
||||
"platforms": [{"romm": ["ps2"], "dir": "ps2", "cards": {"default": "shared", "modes": "shared"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nps64", "name": "NPS64", "title_ids": ["NPSX00064"], "detect": ["/data/NPS64"],
|
||||
"note": "Saves and states are named after a hash of the game file (.z64 dumps).",
|
||||
"profile": {
|
||||
"root": "/data/NPS64", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/saves",
|
||||
"bios_dir": "{root}/system", "fixed": ["rom_dir", "save_dir", "state_dir"],
|
||||
"save_exts": ".srm", "save_name": "rom-{rom_fnv64}{ext}", "save_stems": "rom-{rom_fnv64}",
|
||||
"state_exts": ".state", "state_stems": "rom-{rom_fnv64}", "state_name": "rom-{rom_fnv64}.slot1{ext}", "state_format": "raw",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["n64"], "dir": "n64", "save_format": "n64-srm", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mgbaps4", "name": "mGBA", "title_ids": ["MGBA00001"], "detect": ["/data/mgba"],
|
||||
"profile": {
|
||||
"root": "/data/mgba", "rom_dir": "{root}/roms", "save_dir": "{root}/saves", "state_dir": "{root}/states",
|
||||
"save_exts": ".sav", "state_exts": ".ss*", "state_slot0": ".ss1", "state_format": "raw",
|
||||
"platforms": [
|
||||
{"romm": ["gba"], "dir": "gba"},
|
||||
{"romm": ["gbc"], "dir": "gbc"},
|
||||
{"romm": ["gb"], "dir": "gb"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "snesui", "name": "SNESUI", "title_ids": ["SNES00001", "SNES00009"], "detect": ["/data/snes-ps4"],
|
||||
"profile": {
|
||||
"root": "/data/snes-ps4", "rom_dir": "{root}/juegos", "save_dir": "{root}/guardados", "state_dir": "{root}/guardados",
|
||||
"save_exts": ".srm", "state_exts": ".st0,.st1,.st2,.st3", "state_slot0": ".st0", "state_format": "raw",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["snes", "sfam"], "dir": "snes"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pnes", "name": "pNES", "title_ids": ["PNES00001"], "detect": ["/data/pnes"],
|
||||
"profile": {
|
||||
"root": "/data/pnes", "rom_dir": "{root}/roms", "save_dir": "{root}/save", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".sav",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"firmware": {"dir": "{root}"},
|
||||
"platforms": [{"romm": ["nes", "famicom"], "dir": "nes"}, {"romm": ["fds"], "dir": "fds"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pgen", "name": "pGEN", "title_ids": ["PGEN00001"], "detect": ["/data/pgen"],
|
||||
"profile": {
|
||||
"root": "/data/pgen", "rom_dir": "{root}/{platform}", "save_dir": "{root}/rams", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".srm",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"platforms": [
|
||||
{"romm": ["genesis", "genesis-slash-megadrive", "megadrive"], "dir": "megadrive"},
|
||||
{"romm": ["sms", "mastersystem"], "dir": "mastersystem"},
|
||||
{"romm": ["gamegear", "gg"], "dir": "gamegear"},
|
||||
{"romm": ["segacd", "sega-cd"], "dir": "megacd", "save_exts": ".brm"},
|
||||
{"romm": ["sg1000"], "dir": "sg1000"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pgba", "name": "pGBA", "title_ids": ["PGBA00001"], "detect": ["/data/pgba"],
|
||||
"note": "Saves and states share the saves folder.",
|
||||
"profile": {
|
||||
"root": "/data/pgba", "rom_dir": "{root}/{platform}", "save_dir": "{root}/saves", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".sav",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"platforms": [
|
||||
{"romm": ["gba"], "dir": "gba"},
|
||||
{"romm": ["gbc"], "dir": "gbc"},
|
||||
{"romm": ["gb"], "dir": "gb"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pfbn", "name": "pFBN", "title_ids": ["PFBN00001"], "detect": ["/data/pfba"],
|
||||
"note": "Arcade saves are the games' own memory (eeproms), backed up as they are.",
|
||||
"profile": {
|
||||
"root": "/data/pfba", "rom_dir": "{root}/{platform}", "save_dir": "{root}/eeproms", "bios_dir": "{root}/arcade",
|
||||
"save_exts": ".fs",
|
||||
"state_dir": "{root}/saves", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}-0,{rom_stem}-1,{rom_stem}-2,{rom_stem}-3", "state_name": "{rom_stem}-0{ext}",
|
||||
"firmware": {"dir": "{rom_dir}"},
|
||||
"platforms": [
|
||||
{"romm": ["arcade", "fbneo", "neogeoaes", "neogeomvs", "cps1", "cps2", "cps3"], "dir": "arcade"},
|
||||
{"romm": ["neo-geo-pocket-color", "neo-geo-pocket", "ngpc", "ngp"], "dir": "ngp"},
|
||||
{"romm": ["colecovision"], "dir": "coleco"},
|
||||
{"romm": ["msx"], "dir": "msx"},
|
||||
{"romm": ["zxs", "zx-spectrum"], "dir": "spectrum"},
|
||||
{"romm": ["supergrafx"], "dir": "sgx"},
|
||||
{"romm": ["channel-f", "fairchild-channel-f"], "dir": "channelf"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "emusuni", "name": "EmuSuNi", "title_ids": ["EMSN00001"], "detect": ["/data/EmuSuNi"],
|
||||
"note": "Set EmuSuNi's N64 folder to /data/EmuSuNi/n64 in its settings.",
|
||||
"profile": {
|
||||
"root": "/data/EmuSuNi", "bios_dir": "{root}/system",
|
||||
"save_exts": ".srm",
|
||||
"state_exts": ".state*", "state_format": "raw", "state_slot0": ".state1",
|
||||
"platforms": [
|
||||
{"romm": ["snes", "sfam"], "dir": "snes", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/states"},
|
||||
{"romm": ["nes", "famicom"], "dir": "nes", "rom_dir": "{root}/nes", "save_dir": "{root}/saves/nes", "state_dir": "{root}/states/nes"},
|
||||
{"romm": ["n64"], "dir": "n64", "rom_dir": "{root}/n64", "save_dir": "{root}/saves/n64", "state_dir": "{root}/states/n64", "save_format": "n64-srm", "cards": {"default": "per_game", "modes": "per_game"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dolphinps4", "name": "DolphinPS4", "title_ids": ["DLPH00010"], "detect": ["/data/DolphinPS4"],
|
||||
"note": "Dolphin, with its user folder in {root}/User. States are named after the disc's id.",
|
||||
"profile": {
|
||||
"root": "/data/DolphinPS4", "rom_dir": "{root}/games", "save_dir": "{root}/User",
|
||||
"state_dir": "{root}/User/StateSaves", "state_exts": ".s0*", "state_format": "raw",
|
||||
"state_stems": "{game_id}", "state_name": "{game_id}{ext}",
|
||||
"firmware": {"dir": "{root}/User/GC", "only_named": true, "names": {"b17148254a5799684c7d783206504926": "USA/IPL.bin", "fc924a7c879b661abc37cec4f018fdf3": "USA/IPL.bin", "019e39822a9ca3029124f74dd4d55ac4": "USA/IPL.bin", "db92574caab77a7ec99d4605fd6f2450": "EUR/IPL.bin", "339848a0b7c2124cf155276c1e79cbd0": "EUR/IPL.bin", "0cdda509e2da83c85bfe423dd87346cc": "EUR/IPL.bin"}},
|
||||
"platforms": [
|
||||
{"romm": ["ngc", "gamecube"], "dir": "gamecube", "emulator": "dolphin", "save_layout": "gci", "bundle_dirs": "{root}/User/GC", "cards": {"default": "per_game", "modes": "per_game"}},
|
||||
{"romm": ["wii"], "dir": "wii", "emulator": "dolphin", "save_layout": "wii", "bundle_dirs": "{root}/User/Wii"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resonance", "name": "Resonance", "title_ids": ["DCST00001"], "detect": ["/data/Resonance"],
|
||||
"note": "Each game's own memory card syncs as its save; the shared ones are backed up as RomM memory cards.",
|
||||
"profile": {
|
||||
"root": "/data/Resonance", "rom_dir": "{root}/games", "save_dir": "{root}/data", "bios_dir": "{root}/data",
|
||||
"save_exts": ".bin", "disc_id": "dreamcast",
|
||||
"save_stems": "{game_id}_vmu_save_A1", "save_name": "{game_id}_vmu_save_A1{ext}",
|
||||
"memcards": "{root}/data/vmu_save_A1.bin,{root}/data/vmu_save_B1.bin,{root}/data/vmu_save_C1.bin,{root}/data/vmu_save_D1.bin",
|
||||
"state_dir": "{root}/data", "state_exts": ".state", "state_format": "raw",
|
||||
"state_stems": "{rom_stem}_1,{rom_stem}_2,{rom_stem}_3", "state_name": "{rom_stem}{ext}",
|
||||
"firmware": {"names": {"e10c53c2f8b90bab96ead2d368858623": "dc_boot.bin"}, "from": {"arcade": ["naomi.zip", "naomi2.zip", "awbios.zip"]}},
|
||||
"platforms": [{"romm": ["dc", "dreamcast"], "dir": "dreamcast", "save_format": "dc-vmu", "cards": {"default": "per_game", "modes": "per_game,shared", "change": "Turn on Own memory card for each game in its settings (players 2 to 4 always share theirs)", "setting": {"file": "{root}/emu.cfg", "key": "PerGameVmu", "per_game": "yes,true", "shared": "no,false"}}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps4sx2", "name": "PS4SX2", "title_ids": ["PCSX20007"], "detect": ["/data/PS4SX2"],
|
||||
"note": "Two memory cards for every game, backed up to RomM (5.3 or newer). No save states yet.",
|
||||
"profile": {
|
||||
"root": "/data/PS4SX2", "rom_dir": "{root}/roms", "bios_dir": "{root}/bios",
|
||||
"sync_saves": false, "memcards": "{root}/memcards/Mcd001.ps2,{root}/memcards/Mcd002.ps2",
|
||||
"firmware": {"pick_one": ["21038400dc633070a78ad53090c53017", "dc69f0643a3030aaa4797501b483d6c4", "80ac46fa7e77b8ab4366e86948e54f83", "93ea3bcee4252627919175ff1b16a1d9", "d3e81e95db25f5a86a7b7474550a2155"]},
|
||||
"platforms": [{"romm": ["ps2"], "dir": "ps2", "cards": {"default": "shared", "modes": "shared"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nps64", "name": "NPS64", "title_ids": ["NPSX00064"], "detect": ["/data/NPS64"],
|
||||
"note": "Saves and states are named after a hash of the game file (.z64 dumps).",
|
||||
"profile": {
|
||||
"root": "/data/NPS64", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/saves",
|
||||
"bios_dir": "{root}/system", "fixed": ["rom_dir", "save_dir", "state_dir"],
|
||||
"save_exts": ".srm", "save_name": "rom-{rom_fnv64}{ext}", "save_stems": "rom-{rom_fnv64}",
|
||||
"state_exts": ".state", "state_stems": "rom-{rom_fnv64}", "state_name": "rom-{rom_fnv64}.slot1{ext}", "state_format": "raw",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["n64"], "dir": "n64", "save_format": "n64-srm", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mgbaps4", "name": "mGBA", "title_ids": ["MGBA00001"], "detect": ["/data/mgba"],
|
||||
"profile": {
|
||||
"root": "/data/mgba", "rom_dir": "{root}/roms", "save_dir": "{root}/saves", "state_dir": "{root}/states",
|
||||
"save_exts": ".sav", "state_exts": ".ss*", "state_slot0": ".ss1", "state_format": "raw",
|
||||
"platforms": [
|
||||
{"romm": ["gba"], "dir": "gba"},
|
||||
{"romm": ["gbc"], "dir": "gbc"},
|
||||
{"romm": ["gb"], "dir": "gb"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "snesui", "name": "SNESUI", "title_ids": ["SNES00001", "SNES00009"], "detect": ["/data/snes-ps4"],
|
||||
"profile": {
|
||||
"root": "/data/snes-ps4", "rom_dir": "{root}/juegos", "save_dir": "{root}/guardados", "state_dir": "{root}/guardados",
|
||||
"save_exts": ".srm", "state_exts": ".st0,.st1,.st2,.st3", "state_slot0": ".st0", "state_format": "raw",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["snes", "sfam"], "dir": "snes"}]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -735,6 +735,11 @@ void App::refresh_status()
|
||||
repaired_ = true;
|
||||
repair_autostart();
|
||||
}
|
||||
|
||||
if (!presets_checked_ && !payload_too_old()) {
|
||||
presets_checked_ = true;
|
||||
api_.post("/api/presets/check", "{}", [](const Response &) {});
|
||||
}
|
||||
status_.ip = str(j.get(), "ip");
|
||||
status_.port = static_cast<int>(num(j.get(), "web_port", 8780));
|
||||
status_.paired = flag(j.get(), "paired");
|
||||
|
||||
@@ -405,7 +405,7 @@ class App
|
||||
bool bundled_read_ = false;
|
||||
std::atomic<int> start_state_{0}; // 0 idle, 1 sending, 2 sent, 3 failed
|
||||
std::string start_error_; // written before start_state_ becomes 3
|
||||
bool tried_start_ = false, updated_payload_ = false, repaired_ = false;
|
||||
bool tried_start_ = false, updated_payload_ = false, repaired_ = false, presets_checked_ = false;
|
||||
bool payload_waiting_ = false;
|
||||
int unanswered_ = 0; // status requests in a row RomM Sync didn't answer
|
||||
int active_queue_ = 0;
|
||||
|
||||
+199
-193
@@ -1,195 +1,201 @@
|
||||
[
|
||||
{
|
||||
"format": 1,
|
||||
"version": "2026.10.23.1",
|
||||
"console": "ps5",
|
||||
"emulators": [
|
||||
{
|
||||
"id": "psxs5", "name": "PSXS5",
|
||||
"title_ids": ["PPSA97510"],
|
||||
"detect": ["/data/PSXS5", "/data/homebrew/PPSA97510"],
|
||||
"note": "One memory card per game, named after the game file (after the disc with PCSX-ReARMed).",
|
||||
"profile": {
|
||||
"root": "/data/PSXS5", "mount_games": "data", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/states",
|
||||
"bios_dir": "{root}/bios",
|
||||
"save_exts": ".mcd", "save_name": "{serial}_1{ext}", "save_stems": "{serial}_1",
|
||||
"state_exts": ".state*", "state_stems": "{rom_stem_safe}", "state_name": "{rom_stem_safe}{ext}",
|
||||
"save_name_by_core": {"mednafen_psx_hw": "{rom_stem}_1{ext}"},
|
||||
"state_format": "raw", "state_slot0": ".state0", "state_cores": "pcsx_rearmed,mednafen_psx_hw",
|
||||
"state_core_setting": {"file": "{root}/psxs5.ini", "key": "emulator",
|
||||
"values": {"0": "mednafen_psx_hw", "1": "pcsx_rearmed", "2": "mednafen_psx_hw"},
|
||||
"auto": "0", "log": "{root}/logs/psxs5.log", "log_line": "host: emulator ",
|
||||
"log_names": {"rearmed": "pcsx_rearmed", "beetle": "mednafen_psx_hw", "mednafen": "mednafen_psx_hw"}},
|
||||
"firmware": {"names": {"8dd7d5296a650fac7319bce665a6a53c": "scph5500.bin", "490f666e1afb15b7362b406ed1cea246": "scph5501.bin",
|
||||
"32736f17079d0b2b7024407c39bd3050": "scph5502.bin"}},
|
||||
"platforms": [{"romm": ["psx", "ps1", "playstation"], "dir": "psx", "save_format": "psx-card", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
"id": "psxs5", "name": "PSXS5",
|
||||
"title_ids": ["PPSA97510"],
|
||||
"detect": ["/data/PSXS5", "/data/homebrew/PPSA97510"],
|
||||
"note": "One memory card per game, named after the game file (after the disc with PCSX-ReARMed).",
|
||||
"profile": {
|
||||
"root": "/data/PSXS5", "mount_games": "data", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/states",
|
||||
"bios_dir": "{root}/bios",
|
||||
"save_exts": ".mcd", "save_name": "{serial}_1{ext}", "save_stems": "{serial}_1",
|
||||
"state_exts": ".state*", "state_stems": "{rom_stem_safe}", "state_name": "{rom_stem_safe}{ext}",
|
||||
"save_name_by_core": {"mednafen_psx_hw": "{rom_stem}_1{ext}"},
|
||||
"state_format": "raw", "state_slot0": ".state0", "state_cores": "pcsx_rearmed,mednafen_psx_hw",
|
||||
"state_core_setting": {"file": "{root}/psxs5.ini", "key": "emulator",
|
||||
"values": {"0": "mednafen_psx_hw", "1": "pcsx_rearmed", "2": "mednafen_psx_hw"},
|
||||
"auto": "0", "log": "{root}/logs/psxs5.log", "log_line": "host: emulator ",
|
||||
"log_names": {"rearmed": "pcsx_rearmed", "beetle": "mednafen_psx_hw", "mednafen": "mednafen_psx_hw"}},
|
||||
"firmware": {"names": {"8dd7d5296a650fac7319bce665a6a53c": "scph5500.bin", "490f666e1afb15b7362b406ed1cea246": "scph5501.bin",
|
||||
"32736f17079d0b2b7024407c39bd3050": "scph5502.bin"}},
|
||||
"platforms": [{"romm": ["psx", "ps1", "playstation"], "dir": "psx", "save_format": "psx-card", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "swanstationps5", "name": "SwanStationPS5",
|
||||
"title_ids": ["PPSA98510"],
|
||||
"detect": ["/data/SwanStationPS5", "/data/homebrew/PPSA98510"],
|
||||
"note": "Plays the games in PSXS5's folder. One memory card per game, named after the game file. Its save states load only in SwanStationPS5.",
|
||||
"profile": {
|
||||
"root": "/data/SwanStationPS5", "mount_games": "data", "rom_dir": "/data/PSXS5/games", "save_dir": "{root}/saves", "state_dir": "{root}/states",
|
||||
"bios_dir": "{root}/bios",
|
||||
"save_exts": ".mcd", "save_name": "{rom_stem}_1{ext}", "save_stems": "{rom_stem}_1",
|
||||
"state_exts": ".state*", "state_stems": "{rom_stem_safe}", "state_name": "{rom_stem_safe}{ext}",
|
||||
"state_format": "raw", "state_slot0": ".state0", "state_cores": "swanstation",
|
||||
"platforms": [{"romm": ["psx", "ps1", "playstation"], "dir": "psx", "save_format": "psx-card", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "psswanstation", "name": "PSSwanStation",
|
||||
"title_ids": ["PPSA99248"],
|
||||
"detect": ["/data/homebrew/PPSA99248"],
|
||||
"note": "One memory card per game, named after the game file. Its save states load only in PSSwanStation.",
|
||||
"profile": {
|
||||
"root": "/data/homebrew/PPSA99248", "rom_dir": "{root}/games", "save_dir": "{root}/data/saves",
|
||||
"state_dir": "{root}/data/states", "bios_dir": "{root}/bios",
|
||||
"mount_games": true,
|
||||
"save_exts": ".mcd", "save_name": "{rom_stem}_1{ext}", "save_stems": "{rom_stem}_1",
|
||||
"state_exts": ".state", "state_name": "{rom_stem}{ext}", "state_slot0": ".0.state",
|
||||
"state_format": "raw", "state_cores": "psswanstation",
|
||||
"platforms": [{"romm": ["psx", "ps1", "playstation"], "dir": "psx", "save_format": "psx-card", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps64", "name": "PS64",
|
||||
"title_ids": ["PPSA06464"],
|
||||
"detect": ["/data/ps64", "/data/homebrew/PPSA06464"],
|
||||
"note": "Games save into their own disk images. Save Disks sync; there are no save states.",
|
||||
"profile": {
|
||||
"root": "/data/ps64", "rom_dir": "{root}/games", "save_dir": "{root}/emulator/saves",
|
||||
"state_dir": "{root}/emulator/saves", "bios_dir": "{root}/emulator/system",
|
||||
"save_exts": ".d64", "save_name": "{rom_stem}.save0{ext}", "state_exts": ".vsf", "state_cores": "ps64",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["c64"], "dir": "c64", "emulator": "vice"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5sx2", "name": "PS5SX2",
|
||||
"title_ids": ["PPSA99203"],
|
||||
"detect": ["/data/PCSX2", "/data/homebrew/PPSA99203"],
|
||||
"note": "Two memory cards for every game, backed up to RomM (5.3 or newer).",
|
||||
"profile": {
|
||||
"root": "/data/PCSX2", "rom_dir": "{root}/games", "bios_dir": "{root}",
|
||||
"sync_saves": false, "memcards": "{root}/Mcd001.ps2,{root}/Mcd002.ps2",
|
||||
"firmware": {"pick_one": ["21038400dc633070a78ad53090c53017", "dc69f0643a3030aaa4797501b483d6c4", "80ac46fa7e77b8ab4366e86948e54f83",
|
||||
"93ea3bcee4252627919175ff1b16a1d9", "d3e81e95db25f5a86a7b7474550a2155"]},
|
||||
"platforms": [{"romm": ["ps2"], "dir": "ps2", "cards": {"default": "shared", "modes": "shared"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5n64", "name": "PS5N64",
|
||||
"title_ids": ["PPSA99206"],
|
||||
"detect": ["/data/PS5N64", "/data/homebrew/PPSA99206"],
|
||||
"note": "Saves and states are named after a hash of the game file.",
|
||||
"profile": {
|
||||
"root": "/data/PS5N64", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/saves",
|
||||
"fixed": ["rom_dir", "save_dir", "state_dir"],
|
||||
"save_exts": ".sav", "save_name": "{rom_fnv64}{ext}", "save_stems": "{rom_fnv64}",
|
||||
"state_exts": ".state", "state_stems": "{rom_fnv64}", "state_name": "{rom_fnv64}{ext}", "state_format": "raw",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["n64"], "dir": "n64", "save_format": "n64-srm", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5nes", "name": "PS5NES",
|
||||
"title_ids": ["PPSA99204"],
|
||||
"detect": ["/data/PS5NES", "/data/homebrew/PPSA99204"],
|
||||
"note": "Untested: v7 may look for games in PS5SX2's folder.",
|
||||
"profile": {
|
||||
"root": "/data/PS5NES", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/saves",
|
||||
"save_exts": ".sav", "save_name": "{rom_fnv64}{ext}", "save_stems": "{rom_fnv64}",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["nes", "famicom"], "dir": "nes"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "porpoise", "name": "Porpoise",
|
||||
"title_ids": ["PPSA99764"],
|
||||
"detect": ["/data/porpoise", "/data/homebrew/PPSA99764"],
|
||||
"note": "Dolphin, with its user folder in {root}/saves/User.",
|
||||
"profile": {
|
||||
"root": "/data/porpoise", "rom_dir": "{root}/games", "save_dir": "{root}/saves/User",
|
||||
"state_dir": "{root}/states/{game_id}", "state_exts": ".state", "state_format": "raw",
|
||||
"bios_dir": "{root}/bios",
|
||||
"firmware": {"only_named": true, "names": {
|
||||
"b17148254a5799684c7d783206504926": "gc-ntsc-ipl.bin", "fc924a7c879b661abc37cec4f018fdf3": "gc-ntsc-ipl.bin",
|
||||
"019e39822a9ca3029124f74dd4d55ac4": "gc-ntsc-ipl.bin", "db92574caab77a7ec99d4605fd6f2450": "gc-pal-ipl.bin",
|
||||
"339848a0b7c2124cf155276c1e79cbd0": "gc-pal-ipl.bin", "0cdda509e2da83c85bfe423dd87346cc": "gc-pal-ipl.bin"}},
|
||||
"platforms": [
|
||||
{"romm": ["ngc", "gamecube"], "dir": "gamecube", "emulator": "dolphin", "save_layout": "gci", "bundle_dirs": "{root}/saves/User/GC", "cards": {"default": "per_game", "modes": "per_game"}},
|
||||
{"romm": ["wii"], "dir": "wii", "emulator": "dolphin", "save_layout": "wii", "bundle_dirs": "{root}/saves/User/Wii"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "psflycast", "name": "PSFlyCast",
|
||||
"title_ids": ["PPSA99247"],
|
||||
"detect": ["/data/homebrew/PPSA99247"],
|
||||
"note": "Each game's own memory card syncs as its save; the shared ones (Own memory card for each game off) are backed up as RomM memory cards. Save states don't sync yet.",
|
||||
"profile": {
|
||||
"root": "/data/homebrew/PPSA99247", "rom_dir": "{root}/games", "save_dir": "{root}/data",
|
||||
"state_dir": "{root}/data/savestates", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".bin", "state_exts": "", "game_ids": "{root}/data/game-ids.txt", "disc_id": "dreamcast",
|
||||
"save_stems": "{game_id}_vmu_save_A1", "save_name": "{game_id}_vmu_save_A1{ext}",
|
||||
"memcards": "{root}/data/vmu_save_A1.bin,{root}/data/vmu_save_B1.bin,{root}/data/vmu_save_C1.bin,{root}/data/vmu_save_D1.bin",
|
||||
"firmware": {"names": {"e10c53c2f8b90bab96ead2d368858623": "dc_boot.bin", "0a93f7940c455905bea6e392dfde92a4": "dc_flash.bin"},
|
||||
"from": {"arcade": ["naomi.zip", "naomi2.zip", "awbios.zip"]}},
|
||||
"platforms": [{"romm": ["dc", "dreamcast"], "dir": "dc", "emulator": "flycast", "save_format": "dc-vmu", "cards": {"default": "per_game", "modes": "per_game,shared", "change": "Turn on Own memory card for each game in its settings (players 2 to 4 always share theirs)", "setting": {"file": "{root}/emu.cfg", "key": "PerGameVmu", "per_game": "yes,true", "shared": "no,false"}}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "seedee5", "name": "Seedee5",
|
||||
"title_ids": ["PPSA99142"],
|
||||
"detect": ["/data/homebrew/PPSA99142"],
|
||||
"note": "Games and BIOS files only: saved games are kept as folders, and only after a clean exit.",
|
||||
"profile": {
|
||||
"root": "/data/homebrew/PPSA99142/data", "rom_dir": "{root}/roms", "save_dir": "{root}/saves",
|
||||
"bios_dir": "{root}/system/same_cdi/bios", "sync_saves": false,
|
||||
"platforms": [{"romm": ["philips-cd-i"], "dir": "cdi", "emulator": "same_cdi"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "xpsemu", "name": "XPSemu",
|
||||
"title_ids": ["PPSA97358"],
|
||||
"detect": ["/data/xemu", "/data/homebrew/PPSA97358"],
|
||||
"note": "Games and BIOS files only: saves live inside its hard disk image.",
|
||||
"profile": {
|
||||
"root": "/data/xemu", "rom_dir": "{root}/games", "bios_dir": "{root}", "sync_saves": false,
|
||||
"firmware": {"only_files": ["mcpx_1.0.bin", "Complex_4627.bin"]},
|
||||
"platforms": [{"romm": ["xbox"], "dir": "xbox", "emulator": "xemu"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5cemu", "name": "PS5CEMU-HAR",
|
||||
"title_ids": ["PPSA99360"],
|
||||
"detect": ["/data/ps5cemu", "/data/homebrew/PPSA99360"],
|
||||
"note": "Wii U (Cemu), 3DS (Azahar) and DS (melonDS). Saves sync for all three; 3DS and DS save states load only in PS5CEMU-HAR.",
|
||||
"profile": {
|
||||
"root": "/data/ps5cemu",
|
||||
"platforms": [
|
||||
{"romm": ["wiiu"], "dir": "wiiu", "emulator": "cemu", "rom_dir": "{root}/games", "bios_dir": "{root}",
|
||||
"save_layout": "wiiu", "bundle_dirs": "{root}/mlc01/usr/save",
|
||||
"firmware": {"only_files": ["keys.txt"]}},
|
||||
{"romm": ["3ds"], "dir": "3ds", "emulator": "azahar", "rom_dir": "{root}/azahar/games", "bios_dir": "{root}/azahar/sysdata",
|
||||
"save_layout": "3ds", "bundle_dirs": "{root}/azahar/sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/title",
|
||||
"state_dir": "{root}/azahar/states", "state_exts": ".cst", "state_stems": "{game_id}", "state_name": "{game_id}.01{ext}",
|
||||
"state_format": "raw", "state_cores": "ps5cemu-azahar",
|
||||
"firmware": {"only_files": ["aes_keys.txt", "seeddb.bin"]}},
|
||||
{"romm": ["nds"], "dir": "nds", "emulator": "melonds", "rom_dir": "{root}/melonds/games",
|
||||
"save_dir": "{root}/melonds/saves", "save_exts": ".sav", "bios_dir": "{root}/melonds/bios", "sync_saves": true,
|
||||
"state_dir": "{root}/melonds/states", "state_exts": ".ml*", "state_format": "raw", "state_cores": "ps5cemu-melonds",
|
||||
"firmware": {"only_files": ["bios7.bin", "bios9.bin", "firmware.bin"]}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "prosperoeden", "name": "ProsperoEden",
|
||||
"title_ids": ["PPSA99008", "PPSA99186"],
|
||||
"detect": ["/data/prosperoeden", "/data/homebrew/PPSA99008", "/data/homebrew/PPSA99186"],
|
||||
"note": "Games (with their updates and DLC) and keys only: saves are kept per Switch user profile. Its Switch UI app shares the same folders.",
|
||||
"profile": {
|
||||
"root": "/data/prosperoeden", "rom_dir": "{root}/roms", "update_dir": "{root}/updates", "dlc_dir": "{root}/updates",
|
||||
"bios_dir": "{root}", "sync_saves": false,
|
||||
"firmware": {"by_ext": {".keys": "keys", ".nca": "firmware"}},
|
||||
"platforms": [{"romm": ["switch"], "dir": "switch", "emulator": "prosperoeden"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "swanstationps5", "name": "SwanStationPS5",
|
||||
"title_ids": ["PPSA98510"],
|
||||
"detect": ["/data/SwanStationPS5", "/data/homebrew/PPSA98510"],
|
||||
"note": "Plays the games in PSXS5's folder. One memory card per game, named after the game file. Its save states load only in SwanStationPS5.",
|
||||
"profile": {
|
||||
"root": "/data/SwanStationPS5", "mount_games": "data", "rom_dir": "/data/PSXS5/games", "save_dir": "{root}/saves", "state_dir": "{root}/states",
|
||||
"bios_dir": "{root}/bios",
|
||||
"save_exts": ".mcd", "save_name": "{rom_stem}_1{ext}", "save_stems": "{rom_stem}_1",
|
||||
"state_exts": ".state*", "state_stems": "{rom_stem_safe}", "state_name": "{rom_stem_safe}{ext}",
|
||||
"state_format": "raw", "state_slot0": ".state0", "state_cores": "swanstation",
|
||||
"platforms": [{"romm": ["psx", "ps1", "playstation"], "dir": "psx", "save_format": "psx-card", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "psswanstation", "name": "PSSwanStation",
|
||||
"title_ids": ["PPSA99248"],
|
||||
"detect": ["/data/homebrew/PPSA99248"],
|
||||
"note": "One memory card per game, named after the game file. Its save states load only in PSSwanStation.",
|
||||
"profile": {
|
||||
"root": "/data/homebrew/PPSA99248", "rom_dir": "{root}/games", "save_dir": "{root}/data/saves",
|
||||
"state_dir": "{root}/data/states", "bios_dir": "{root}/bios",
|
||||
"mount_games": true,
|
||||
"save_exts": ".mcd", "save_name": "{rom_stem}_1{ext}", "save_stems": "{rom_stem}_1",
|
||||
"state_exts": ".state", "state_name": "{rom_stem}{ext}", "state_slot0": ".0.state",
|
||||
"state_format": "raw", "state_cores": "psswanstation",
|
||||
"platforms": [{"romm": ["psx", "ps1", "playstation"], "dir": "psx", "save_format": "psx-card", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps64", "name": "PS64",
|
||||
"title_ids": ["PPSA06464"],
|
||||
"detect": ["/data/ps64", "/data/homebrew/PPSA06464"],
|
||||
"note": "Games save into their own disk images. Save Disks sync; there are no save states.",
|
||||
"profile": {
|
||||
"root": "/data/ps64", "rom_dir": "{root}/games", "save_dir": "{root}/emulator/saves",
|
||||
"state_dir": "{root}/emulator/saves", "bios_dir": "{root}/emulator/system",
|
||||
"save_exts": ".d64", "save_name": "{rom_stem}.save0{ext}", "state_exts": ".vsf", "state_cores": "ps64",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["c64"], "dir": "c64", "emulator": "vice"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5sx2", "name": "PS5SX2",
|
||||
"title_ids": ["PPSA99203"],
|
||||
"detect": ["/data/PCSX2", "/data/homebrew/PPSA99203"],
|
||||
"note": "Two memory cards for every game, backed up to RomM (5.3 or newer).",
|
||||
"profile": {
|
||||
"root": "/data/PCSX2", "rom_dir": "{root}/games", "bios_dir": "{root}",
|
||||
"sync_saves": false, "memcards": "{root}/Mcd001.ps2,{root}/Mcd002.ps2",
|
||||
"firmware": {"pick_one": ["21038400dc633070a78ad53090c53017", "dc69f0643a3030aaa4797501b483d6c4", "80ac46fa7e77b8ab4366e86948e54f83",
|
||||
"93ea3bcee4252627919175ff1b16a1d9", "d3e81e95db25f5a86a7b7474550a2155"]},
|
||||
"platforms": [{"romm": ["ps2"], "dir": "ps2", "cards": {"default": "shared", "modes": "shared"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5n64", "name": "PS5N64",
|
||||
"title_ids": ["PPSA99206"],
|
||||
"detect": ["/data/PS5N64", "/data/homebrew/PPSA99206"],
|
||||
"note": "Saves and states are named after a hash of the game file.",
|
||||
"profile": {
|
||||
"root": "/data/PS5N64", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/saves",
|
||||
"fixed": ["rom_dir", "save_dir", "state_dir"],
|
||||
"save_exts": ".sav", "save_name": "{rom_fnv64}{ext}", "save_stems": "{rom_fnv64}",
|
||||
"state_exts": ".state", "state_stems": "{rom_fnv64}", "state_name": "{rom_fnv64}{ext}", "state_format": "raw",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["n64"], "dir": "n64", "save_format": "n64-srm", "cards": {"default": "per_game", "modes": "per_game"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5nes", "name": "PS5NES",
|
||||
"title_ids": ["PPSA99204"],
|
||||
"detect": ["/data/PS5NES", "/data/homebrew/PPSA99204"],
|
||||
"note": "Untested: v7 may look for games in PS5SX2's folder.",
|
||||
"profile": {
|
||||
"root": "/data/PS5NES", "rom_dir": "{root}/games", "save_dir": "{root}/saves", "state_dir": "{root}/saves",
|
||||
"save_exts": ".sav", "save_name": "{rom_fnv64}{ext}", "save_stems": "{rom_fnv64}",
|
||||
"firmware": {"skip": true},
|
||||
"platforms": [{"romm": ["nes", "famicom"], "dir": "nes"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "porpoise", "name": "Porpoise",
|
||||
"title_ids": ["PPSA99764"],
|
||||
"detect": ["/data/porpoise", "/data/homebrew/PPSA99764"],
|
||||
"note": "Dolphin, with its user folder in {root}/saves/User.",
|
||||
"profile": {
|
||||
"root": "/data/porpoise", "rom_dir": "{root}/games", "save_dir": "{root}/saves/User",
|
||||
"state_dir": "{root}/states/{game_id}", "state_exts": ".state", "state_format": "raw",
|
||||
"bios_dir": "{root}/bios",
|
||||
"firmware": {"only_named": true, "names": {
|
||||
"b17148254a5799684c7d783206504926": "gc-ntsc-ipl.bin", "fc924a7c879b661abc37cec4f018fdf3": "gc-ntsc-ipl.bin",
|
||||
"019e39822a9ca3029124f74dd4d55ac4": "gc-ntsc-ipl.bin", "db92574caab77a7ec99d4605fd6f2450": "gc-pal-ipl.bin",
|
||||
"339848a0b7c2124cf155276c1e79cbd0": "gc-pal-ipl.bin", "0cdda509e2da83c85bfe423dd87346cc": "gc-pal-ipl.bin"}},
|
||||
"platforms": [
|
||||
{"romm": ["ngc", "gamecube"], "dir": "gamecube", "emulator": "dolphin", "save_layout": "gci", "bundle_dirs": "{root}/saves/User/GC", "cards": {"default": "per_game", "modes": "per_game"}},
|
||||
{"romm": ["wii"], "dir": "wii", "emulator": "dolphin", "save_layout": "wii", "bundle_dirs": "{root}/saves/User/Wii"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "psflycast", "name": "PSFlyCast",
|
||||
"title_ids": ["PPSA99247"],
|
||||
"detect": ["/data/homebrew/PPSA99247"],
|
||||
"note": "Each game's own memory card syncs as its save; the shared ones (Own memory card for each game off) are backed up as RomM memory cards. Save states don't sync yet.",
|
||||
"profile": {
|
||||
"root": "/data/homebrew/PPSA99247", "rom_dir": "{root}/games", "save_dir": "{root}/data",
|
||||
"state_dir": "{root}/data/savestates", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".bin", "state_exts": "", "game_ids": "{root}/data/game-ids.txt", "disc_id": "dreamcast",
|
||||
"save_stems": "{game_id}_vmu_save_A1", "save_name": "{game_id}_vmu_save_A1{ext}",
|
||||
"memcards": "{root}/data/vmu_save_A1.bin,{root}/data/vmu_save_B1.bin,{root}/data/vmu_save_C1.bin,{root}/data/vmu_save_D1.bin",
|
||||
"firmware": {"names": {"e10c53c2f8b90bab96ead2d368858623": "dc_boot.bin", "0a93f7940c455905bea6e392dfde92a4": "dc_flash.bin"},
|
||||
"from": {"arcade": ["naomi.zip", "naomi2.zip", "awbios.zip"]}},
|
||||
"platforms": [{"romm": ["dc", "dreamcast"], "dir": "dc", "emulator": "flycast", "save_format": "dc-vmu", "cards": {"default": "per_game", "modes": "per_game,shared", "change": "Turn on Own memory card for each game in its settings (players 2 to 4 always share theirs)", "setting": {"file": "{root}/emu.cfg", "key": "PerGameVmu", "per_game": "yes,true", "shared": "no,false"}}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "seedee5", "name": "Seedee5",
|
||||
"title_ids": ["PPSA99142"],
|
||||
"detect": ["/data/homebrew/PPSA99142"],
|
||||
"note": "Games and BIOS files only: saved games are kept as folders, and only after a clean exit.",
|
||||
"profile": {
|
||||
"root": "/data/homebrew/PPSA99142/data", "rom_dir": "{root}/roms", "save_dir": "{root}/saves",
|
||||
"bios_dir": "{root}/system/same_cdi/bios", "sync_saves": false,
|
||||
"platforms": [{"romm": ["philips-cd-i"], "dir": "cdi", "emulator": "same_cdi"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "xpsemu", "name": "XPSemu",
|
||||
"title_ids": ["PPSA97358"],
|
||||
"detect": ["/data/xemu", "/data/homebrew/PPSA97358"],
|
||||
"note": "Games and BIOS files only: saves live inside its hard disk image.",
|
||||
"profile": {
|
||||
"root": "/data/xemu", "rom_dir": "{root}/games", "bios_dir": "{root}", "sync_saves": false,
|
||||
"firmware": {"only_files": ["mcpx_1.0.bin", "Complex_4627.bin"]},
|
||||
"platforms": [{"romm": ["xbox"], "dir": "xbox", "emulator": "xemu"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ps5cemu", "name": "PS5CEMU-HAR",
|
||||
"title_ids": ["PPSA99360"],
|
||||
"detect": ["/data/ps5cemu", "/data/homebrew/PPSA99360"],
|
||||
"note": "Wii U (Cemu), 3DS (Azahar) and DS (melonDS). Wii U and 3DS saves sync; 3DS save states load only in PS5CEMU-HAR. DS: games and BIOS files only for now.",
|
||||
"profile": {
|
||||
"root": "/data/ps5cemu",
|
||||
"platforms": [
|
||||
{"romm": ["wiiu"], "dir": "wiiu", "emulator": "cemu", "rom_dir": "{root}/games", "bios_dir": "{root}",
|
||||
"save_layout": "wiiu", "bundle_dirs": "{root}/mlc01/usr/save",
|
||||
"firmware": {"only_files": ["keys.txt"]}},
|
||||
{"romm": ["3ds"], "dir": "3ds", "emulator": "azahar", "rom_dir": "{root}/azahar/games", "bios_dir": "{root}/azahar/sysdata",
|
||||
"save_layout": "3ds", "bundle_dirs": "{root}/azahar/sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/title",
|
||||
"state_dir": "{root}/azahar/states", "state_exts": ".cst", "state_stems": "{game_id}", "state_name": "{game_id}.01{ext}",
|
||||
"state_format": "raw", "state_cores": "ps5cemu-azahar",
|
||||
"firmware": {"only_files": ["aes_keys.txt", "seeddb.bin"]}},
|
||||
{"romm": ["nds"], "dir": "nds", "emulator": "melonds", "rom_dir": "{root}/melonds/games",
|
||||
"save_dir": "{root}/melonds/saves", "bios_dir": "{root}/melonds/bios", "sync_saves": false,
|
||||
"firmware": {"only_files": ["bios7.bin", "bios9.bin", "firmware.bin"]}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "prosperoeden", "name": "ProsperoEden",
|
||||
"title_ids": ["PPSA99008", "PPSA99186"],
|
||||
"detect": ["/data/prosperoeden", "/data/homebrew/PPSA99008", "/data/homebrew/PPSA99186"],
|
||||
"note": "Games (with their updates and DLC) and keys only: saves are kept per Switch user profile. Its Switch UI app shares the same folders.",
|
||||
"profile": {
|
||||
"root": "/data/prosperoeden", "rom_dir": "{root}/roms", "update_dir": "{root}/updates", "dlc_dir": "{root}/updates",
|
||||
"bios_dir": "{root}", "sync_saves": false,
|
||||
"firmware": {"by_ext": {".keys": "keys", ".nca": "firmware"}},
|
||||
"platforms": [{"romm": ["switch"], "dir": "switch", "emulator": "prosperoeden"}]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
+73
-8
@@ -23,6 +23,7 @@
|
||||
#include "ra2.h"
|
||||
#include "state.h"
|
||||
#include "util.h"
|
||||
#include "presets.h"
|
||||
|
||||
/* Bump this when generated profiles gain new keys, or existing installs keep
|
||||
* their old profile forever (ask me how I know). */
|
||||
@@ -382,6 +383,14 @@ static int app_installed(const char *title_id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Presets typically are {"format", "version", "console" "emulators": [....]}*/
|
||||
static cJSON *catalog_from(cJSON *file) {
|
||||
if (cJSON_IsArray(file)) return file;
|
||||
cJSON *list = cJSON_DetachItemFromObjectCaseSensitive(file, "emulators");
|
||||
cJSON_Delete(file);
|
||||
return list;
|
||||
}
|
||||
|
||||
/* The catalog: ROMM_SYNC_CATALOG names a file (for tests), else the console's. */
|
||||
static cJSON *emulator_catalog(void) {
|
||||
const char *env = getenv("ROMM_SYNC_CATALOG");
|
||||
@@ -389,9 +398,9 @@ static cJSON *emulator_catalog(void) {
|
||||
char *txt = read_file(env, NULL);
|
||||
cJSON *j = txt ? cJSON_Parse(txt) : NULL;
|
||||
free(txt);
|
||||
return j;
|
||||
return catalog_from(j);
|
||||
}
|
||||
return plat_info()->emulator_catalog ? cJSON_Parse(plat_info()->emulator_catalog) : NULL;
|
||||
return cJSON_Duplicate(presets_catalog(), 1);
|
||||
}
|
||||
|
||||
static cJSON *g_catalog;
|
||||
@@ -399,9 +408,13 @@ static void load_catalog(void) { g_catalog = emulator_catalog(); }
|
||||
|
||||
const cJSON *detect_catalog_entry(const char *id) {
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
pthread_once(&once, load_catalog);
|
||||
const cJSON *e;
|
||||
cJSON_ArrayForEach(e, g_catalog)
|
||||
const char *env = getenv("ROMM_SYNC_CATALOG");
|
||||
const cJSON *catalog = presets_catalog(), *e;
|
||||
if (env && *env) {
|
||||
pthread_once(&once, load_catalog);
|
||||
catalog = g_catalog;
|
||||
}
|
||||
cJSON_ArrayForEach(e, catalog)
|
||||
if (!strcmp(jget_str(e, "id", ""), id)) return e;
|
||||
return NULL;
|
||||
}
|
||||
@@ -524,6 +537,57 @@ cJSON *detect_emulators(void) {
|
||||
* preset, which follow it (PSXS5's cores, as its versions change them). */
|
||||
static const char *const PRESET_ONLY[] = {"state_core_setting"};
|
||||
|
||||
/* "sync_saves" says whether an emulator's saves can sync, and only the preset
|
||||
* gets to say it now (profiles.c). Saved profiles still carry copies from
|
||||
* before: a "false" the preset never shipped was the user switching sync off,
|
||||
* which "sync": "off" says now; one it did ship (and has since dropped, the
|
||||
* saves turning out fine) is simply gone. Every "false" ever shipped, by
|
||||
* emulator and system ("*": all of them): */
|
||||
static const struct {
|
||||
const char *profile, *system;
|
||||
} SHIPPED_NO_SYNC[] = {
|
||||
{"ps5cemu", "nds"}, {"prosperoeden", "*"}, {"ps4sx2", "*"}, {"ps5sx2", "*"},
|
||||
{"resonance", "*"}, {"seedee5", "*"}, {"xpsemu", "*"},
|
||||
};
|
||||
|
||||
static int shipped_no_sync(const char *profile, const char *system) {
|
||||
for (size_t i = 0; i < sizeof SHIPPED_NO_SYNC / sizeof SHIPPED_NO_SYNC[0]; i++)
|
||||
if (!strcmp(SHIPPED_NO_SYNC[i].profile, profile) &&
|
||||
(!strcmp(SHIPPED_NO_SYNC[i].system, "*") || !strcmp(SHIPPED_NO_SYNC[i].system, system)))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* One level's saved copy (the profile's, system "*", or a platform's) gone;
|
||||
* the user's own "off" kept as "sync". `preset` is what the preset says for
|
||||
* that level now: a copy of it isn't anyone's choice either. */
|
||||
static int drop_saved_sync_saves(cJSON *o, const cJSON *preset, const char *profile, const char *system, const char *name) {
|
||||
const cJSON *v = cJSON_GetObjectItemCaseSensitive(o, "sync_saves");
|
||||
if (!v) return 0;
|
||||
if (cJSON_IsFalse(v) && !cJSON_IsFalse(preset) && !shipped_no_sync(profile, system)) {
|
||||
cJSON_DeleteItemFromObjectCaseSensitive(o, "sync");
|
||||
cJSON_AddStringToObject(o, "sync", "off");
|
||||
LOGI("the %s profile: sync turned off for %s, as its saved profile had it", name, !strcmp(system, "*") ? "all its systems" : system);
|
||||
}
|
||||
cJSON_DeleteItemFromObjectCaseSensitive(o, "sync_saves");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int drop_saved_sync(cJSON *p, const cJSON *preset) {
|
||||
const char *id = jget_str(p, "id", ""), *name = jget_str(p, "name", "");
|
||||
const cJSON *top = cJSON_GetObjectItemCaseSensitive(preset, "sync_saves");
|
||||
int n = drop_saved_sync_saves(p, top, id, "*", name);
|
||||
cJSON *pe;
|
||||
cJSON_ArrayForEach(pe, cJSON_GetObjectItemCaseSensitive(p, "platforms")) {
|
||||
const cJSON *ce, *says = top;
|
||||
cJSON_ArrayForEach(ce, cJSON_GetObjectItemCaseSensitive(preset, "platforms"))
|
||||
if (!strcmp(profiles_system_key(ce), profiles_system_key(pe)) && cJSON_GetObjectItemCaseSensitive(ce, "sync_saves"))
|
||||
says = cJSON_GetObjectItemCaseSensitive(ce, "sync_saves");
|
||||
n += drop_saved_sync_saves(pe, says, id, profiles_system_key(pe), name);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static int fill_from_catalog(cJSON *p) {
|
||||
const cJSON *entry = detect_catalog_entry(jget_str(p, "id", ""));
|
||||
const cJSON *preset = entry ? cJSON_GetObjectItemCaseSensitive(entry, "profile") : NULL, *k;
|
||||
@@ -541,7 +605,7 @@ static int fill_from_catalog(cJSON *p) {
|
||||
return added;
|
||||
}
|
||||
cJSON_ArrayForEach(k, preset) {
|
||||
if (!strcmp(k->string, "platforms")) continue;
|
||||
if (!strcmp(k->string, "platforms") || !strcmp(k->string, "sync_saves")) continue; /* (the preset's alone) */
|
||||
const cJSON *have = cJSON_GetObjectItemCaseSensitive(p, k->string);
|
||||
int preset_only = 0;
|
||||
for (size_t i = 0; i < sizeof PRESET_ONLY / sizeof PRESET_ONLY[0]; i++) preset_only |= !strcmp(k->string, PRESET_ONLY[i]);
|
||||
@@ -553,6 +617,7 @@ static int fill_from_catalog(cJSON *p) {
|
||||
added++;
|
||||
}
|
||||
}
|
||||
const int dropped = drop_saved_sync(p, preset);
|
||||
cJSON_ArrayForEach(pe, cJSON_GetObjectItemCaseSensitive(p, "platforms")) {
|
||||
const cJSON *first = cJSON_GetArrayItem(cJSON_GetObjectItemCaseSensitive(pe, "romm"), 0), *ce;
|
||||
if (!cJSON_IsString(first)) continue;
|
||||
@@ -560,14 +625,14 @@ static int fill_from_catalog(cJSON *p) {
|
||||
const cJSON *cf = cJSON_GetArrayItem(cJSON_GetObjectItemCaseSensitive(ce, "romm"), 0);
|
||||
if (!cJSON_IsString(cf) || strcmp(cf->valuestring, first->valuestring)) continue;
|
||||
cJSON_ArrayForEach(k, ce)
|
||||
if (!cJSON_GetObjectItemCaseSensitive(pe, k->string)) {
|
||||
if (strcmp(k->string, "sync_saves") != 0 && !cJSON_GetObjectItemCaseSensitive(pe, k->string)) {
|
||||
cJSON_AddItemToObject(pe, k->string, cJSON_Duplicate(k, 1));
|
||||
added++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added) LOGI("the %s profile got %d setting%s from its newer preset", jget_str(p, "name", ""), added, added == 1 ? "" : "s");
|
||||
return added;
|
||||
return added + dropped;
|
||||
}
|
||||
|
||||
int detect_refresh_profiles(cJSON *profiles) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "util.h"
|
||||
#include "watch.h"
|
||||
#include "web.h"
|
||||
#include "presets.h"
|
||||
|
||||
#define MONITOR_INTERVAL_SEC 3
|
||||
/* Failed foreground reads in a row before game detection is given up. */
|
||||
@@ -144,6 +145,7 @@ static void *app_main(void *arg) {
|
||||
plat_notify("RommPS: the console's date and time aren't set. Set them in the console's Settings for syncing to work.");
|
||||
}
|
||||
|
||||
presets_init();
|
||||
config_load();
|
||||
if (g_cfg.profiles_custom && detect_refresh_profiles(g_cfg.profiles) > 0) config_save();
|
||||
state_load();
|
||||
@@ -194,6 +196,8 @@ static void *app_main(void *arg) {
|
||||
library_link_extras();
|
||||
|
||||
sleep(5); /* wait for the network after boot */
|
||||
presets_check();
|
||||
|
||||
if (config_is_paired()) {
|
||||
char base[512], version[32] = "";
|
||||
config_lock();
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
#include "presets.h"
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
#include "detect.h"
|
||||
#include "http.h"
|
||||
#include "monocypher-ed25519.h"
|
||||
#include "update_keys.h"
|
||||
#include "state.h"
|
||||
|
||||
#ifndef PRESETS_URL
|
||||
#define PRESETS_URL "https://raw.githubusercontent.com/s0liton/RommPS/presets"
|
||||
#endif
|
||||
|
||||
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static cJSON *g_file;
|
||||
|
||||
static char g_source[16] = "built-in";
|
||||
|
||||
static int g_checking;
|
||||
static char g_error[256];
|
||||
static time_t g_checked_at;
|
||||
|
||||
|
||||
|
||||
int presets_version_cmp(const char *a, const char *b) {
|
||||
int x[4] = {0}, y[4] = {0};
|
||||
sscanf(a, "%d.%d.%d.%d", &x[0], &x[1], &x[2], &x[3]);
|
||||
sscanf(b, "%d.%d.%d.%d", &y[0], &y[1], &y[2], &y[3]);
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (x[i] != y[i]) return x[i] < y[i] ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void download_path(const char *name, char *out, size_t n) {
|
||||
char dir[PATH_MAX_LEN];
|
||||
path_join(dir, sizeof dir, plat_data_dir(), "presets");
|
||||
path_join(out, n, dir, name);
|
||||
}
|
||||
|
||||
static cJSON *usable(cJSON *f, const char *from) {
|
||||
const char *why = !cJSON_IsObject(f) ? "Not valid JSON"
|
||||
: jget_num(f, "format", 0) != PRESETS_FORMAT ? "In a format this version cannot read"
|
||||
: strcmp(jget_str(f, "console", ""), plat_info()->name) != 0 ? "Not for this console"
|
||||
: presets_version_cmp(jget_str(f, "version", ""), "0") <= 0 ? "Missing its version value"
|
||||
: !cJSON_IsArray(cJSON_GetObjectItemCaseSensitive(f, "emulators")) ? "Missing emulators section."
|
||||
: NULL;
|
||||
if (!why) return f;
|
||||
LOGW("Presets: %s is %s; Not using it", from, why);
|
||||
cJSON_Delete(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static cJSON *read_download(const char *name) {
|
||||
char path[PATH_MAX_LEN];
|
||||
download_path(name, path, sizeof path);
|
||||
char *txt = read_file(path, NULL);
|
||||
if (!txt) return NULL; /* A presets file was never downloaded. */
|
||||
cJSON *f = cJSON_Parse(txt);
|
||||
free(txt);
|
||||
return usable(f, name);
|
||||
}
|
||||
|
||||
static void remove_downloads(void) {
|
||||
char path[PATH_MAX_LEN];
|
||||
download_path("current.json", path, sizeof path);
|
||||
unlink(path);
|
||||
download_path("previous.json", path, sizeof path);
|
||||
unlink(path);
|
||||
}
|
||||
|
||||
void presets_init(void) {
|
||||
const char *embedded = plat_info()->emulator_catalog;
|
||||
cJSON *best = embedded ? usable (cJSON_Parse(embedded), "The built-in presets file") : NULL;
|
||||
cJSON *dl = read_download("current.json");
|
||||
if (!dl) dl = read_download("previous.json");
|
||||
if (dl && (!best || presets_version_cmp(jget_str(dl, "version", ""), jget_str(best, "version", "")) > 0)) {
|
||||
cJSON_Delete(best);
|
||||
best = dl;
|
||||
str_copy(g_source, sizeof g_source, "download");
|
||||
} else if (dl) {
|
||||
/* A newer RommPS brought newer presets than the last download */
|
||||
LOGI("presets: built-in %s is newer than the download (%s), let's remove it", jget_str(best, "version", ""), jget_str(dl, "version", ""));
|
||||
cJSON_Delete(dl);
|
||||
remove_downloads();
|
||||
}
|
||||
pthread_mutex_lock(&g_lock);
|
||||
g_file = best;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
LOGI("presets: %s (%s)", best ? jget_str(best, "version", "") : "none", g_source);
|
||||
}
|
||||
|
||||
const cJSON *presets_catalog(void) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
const cJSON *c = cJSON_GetObjectItemCaseSensitive(g_file, "emulators");
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Where presets-<console-name>.json and .sig file are located. */
|
||||
static const char *presets_url(void) {
|
||||
const char *u = getenv("ROMM_SYNC_PRESETS_URL"); /* Used for tests. */
|
||||
return u && *u ? u: PRESETS_URL;
|
||||
}
|
||||
|
||||
static int fetch(const char *url, http_resp *r, char *err, size_t en) {
|
||||
if (http_get_verified(url, r) != 0) snprintf( err, en, "Can't reach %s: %s", url, r->error);
|
||||
else if (r->status != 200) snprintf(err, en, "%s answered with HTTP %ld", url, r->status);
|
||||
else return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check to see if the presets file is signed by our release key */
|
||||
static int signed_by_us(const http_resp *file, const http_resp *sig) {
|
||||
if (sig->len != 64) return 0;
|
||||
for (size_t i = 0; i < sizeof UPDATE_KEYS / sizeof UPDATE_KEYS[0]; i++)
|
||||
if (crypto_ed25519_check((const uint8_t *)sig->body, UPDATE_KEYS[i], (const uint8_t *)file->body, file->len) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int save_download(const http_resp *file, char *err, size_t en) {
|
||||
char cur[PATH_MAX_LEN], prev[PATH_MAX_LEN];
|
||||
download_path("current.json", cur, sizeof cur);
|
||||
download_path("previous.json", prev, sizeof prev);
|
||||
mkdir_parent(cur);
|
||||
if (file_exists(cur) && move_file(cur, prev) !=0) LOGW("presets: can't keep the previous download");
|
||||
if (write_file_atomic(cur, file->body, file->len) == 0) return 0;
|
||||
snprintf(err, en, "can't save the presets to %s", cur);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void apply(cJSON *f) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
g_file = f;
|
||||
str_copy(g_source, sizeof g_source, "download");
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
config_lock();
|
||||
int changed = g_cfg.profiles_custom && detect_refresh_profiles(g_cfg.profiles) > 0;
|
||||
if (changed) config_save();
|
||||
config_unlock();
|
||||
LOGI("presets: now %s%s", jget_str(f, "version", ""), changed ? ", saved profiles updated" : "");
|
||||
}
|
||||
|
||||
static void *check_thread(void *arg) {
|
||||
(void)arg;
|
||||
char url[600], sig_url[600], have[32], err[256] = "";
|
||||
http_resp file = {0}, sig = {0};
|
||||
cJSON *f = NULL;
|
||||
|
||||
snprintf(url, sizeof url, "%s/presets-%s.json", presets_url(), plat_info()->name);
|
||||
snprintf(sig_url, sizeof sig_url, "%s/presets-%s.sig", presets_url(), plat_info()->name);
|
||||
|
||||
if(fetch(url, &file, err, sizeof err) == 0 && fetch(sig_url, &sig, err, sizeof err) == 0) {
|
||||
if (!signed_by_us(&file, &sig)) snprintf(err, sizeof err, "the presets signature doesn't match, so not using them");
|
||||
else if (!(f = usable(cJSON_ParseWithLength(file.body, file.len), "the download")))
|
||||
snprintf(err, sizeof err, "the published presets can't be used here");
|
||||
}
|
||||
if (f) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
str_copy(have, sizeof have, g_file ? jget_str(g_file, "version", "0") : "0");
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
if (presets_version_cmp(jget_str(f, "version", ""), have) <= 0) {
|
||||
LOGI("presets: %s is current", have);
|
||||
cJSON_Delete(f);
|
||||
} else if (save_download(&file, err, sizeof err) == 0) {
|
||||
apply(f);
|
||||
} else {
|
||||
cJSON_Delete(f);
|
||||
}
|
||||
|
||||
}
|
||||
if (err[0]) LOGW("presets: %s", err);
|
||||
http_resp_free(&file);
|
||||
http_resp_free(&sig);
|
||||
|
||||
pthread_mutex_lock(&g_lock);
|
||||
g_checking = 0;
|
||||
g_checked_at = time(NULL);
|
||||
str_copy(g_error, sizeof g_error, err);
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int presets_check(void) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
int busy = g_checking;
|
||||
g_checking = 1;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
if (busy) return -1;
|
||||
if (thread_start(check_thread, NULL) != 0) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
g_checking = 0;
|
||||
str_copy(g_error, sizeof g_error, "cannot start the presets check");
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *presets_status(void) {
|
||||
cJSON *j = cJSON_CreateObject();
|
||||
pthread_mutex_lock(&g_lock);
|
||||
cJSON_AddStringToObject(j, "version", g_file ? jget_str(g_file, "version", "") : "");
|
||||
cJSON_AddStringToObject(j, "source", g_source);
|
||||
cJSON_AddStringToObject(j, "state", g_checking ? "checking" : g_error[0] ? "error" : "idle");
|
||||
cJSON_AddStringToObject(j, "error", g_error);
|
||||
cJSON_AddNumberToObject(j, "checked_at", (double)g_checked_at);
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return j;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef ROMM_PRESETS_H
|
||||
#define ROMM_PRESETS_H
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#define PRESETS_FORMAT 1
|
||||
|
||||
/* Pick up the latest valid presets. Called at startup. */
|
||||
void presets_init(void);
|
||||
|
||||
/* The catalog currently in use. Newer file takes its place and the old one stays valid in case it's still being used. */
|
||||
const cJSON *presets_catalog(void);
|
||||
|
||||
/* Looks for newer presets in the background, and downloads, verifies, and applies them.*/
|
||||
int presets_check(void);
|
||||
|
||||
/* Tells preset version, source (built-in) or (downloaded), info about the state, errors, and last checked time */
|
||||
cJSON *presets_status(void);
|
||||
|
||||
/* Compare the version code within date to see if this contains newer presets. */
|
||||
int presets_version_cmp(const char *a, const char *b);
|
||||
|
||||
#endif
|
||||
+20
-8
@@ -94,13 +94,12 @@ cJSON *profiles_default(void) {
|
||||
return defs;
|
||||
}
|
||||
|
||||
/* A save state setting: the profile's own, else the built-in catalog's for
|
||||
* the same emulator and system (profiles saved before these settings existed). */
|
||||
static const cJSON *state_setting(const cJSON *p, const cJSON *e, const char *key) {
|
||||
const cJSON *v = cJSON_GetObjectItemCaseSensitive(e, key);
|
||||
if (!v) v = cJSON_GetObjectItemCaseSensitive(p, key);
|
||||
if (v) return v;
|
||||
const cJSON *cat = cJSON_GetObjectItemCaseSensitive(detect_catalog_entry(jstr(p, NULL, "id", "")), "profile"), *ce;
|
||||
/* The built-in catalog's setting for the same emulator and system: its
|
||||
* platform's, else its profile's. *known says whether the catalog has the
|
||||
* emulator at all (RetroArch and hand-made profiles aren't in it). */
|
||||
static const cJSON *catalog_setting(const cJSON *p, const cJSON *e, const char *key, int *known) {
|
||||
const cJSON *cat = cJSON_GetObjectItemCaseSensitive(detect_catalog_entry(jstr(p, NULL, "id", "")), "profile"), *ce, *v;
|
||||
if (known) *known = cat != NULL;
|
||||
if (!cat) return NULL;
|
||||
cJSON_ArrayForEach(ce, cJSON_GetObjectItemCaseSensitive(cat, "platforms")) {
|
||||
if (strcmp(profiles_system_key(ce), profiles_system_key(e)) != 0) continue;
|
||||
@@ -109,6 +108,14 @@ static const cJSON *state_setting(const cJSON *p, const cJSON *e, const char *ke
|
||||
return cJSON_GetObjectItemCaseSensitive(cat, key);
|
||||
}
|
||||
|
||||
/* A save state setting: the profile's own, else the built-in catalog's for
|
||||
* the same emulator and system (profiles saved before these settings existed). */
|
||||
static const cJSON *state_setting(const cJSON *p, const cJSON *e, const char *key) {
|
||||
const cJSON *v = cJSON_GetObjectItemCaseSensitive(e, key);
|
||||
if (!v) v = cJSON_GetObjectItemCaseSensitive(p, key);
|
||||
return v ? v : catalog_setting(p, e, key, NULL);
|
||||
}
|
||||
|
||||
static const char *state_str(const cJSON *p, const cJSON *e, const char *key, const char *def) {
|
||||
const cJSON *v = state_setting(p, e, key);
|
||||
return cJSON_IsString(v) ? v->valuestring : def;
|
||||
@@ -624,7 +631,12 @@ int profiles_expand(const cJSON *profiles, profile_map **out) {
|
||||
m->extra = profile_lists(p, "extra", profiles_system_key(e));
|
||||
const cJSON *mg = state_setting(p, e, "mount_games");
|
||||
m->mount_games = cJSON_IsTrue(mg) ? 1 : cJSON_IsString(mg) && !strcmp(mg->valuestring, "data") ? 2 : 0;
|
||||
m->sync_saves = m->base_sync_saves = jbool(p, e, "sync_saves", 1);
|
||||
/* Whether its saves can sync at all is the preset's call, never a
|
||||
* saved copy's: a preset turns them on once they're tested, or off
|
||||
* when an emulator update breaks them. The user's say is "sync". */
|
||||
int in_catalog = 0;
|
||||
const cJSON *can = catalog_setting(p, e, "sync_saves", &in_catalog);
|
||||
m->sync_saves = m->base_sync_saves = in_catalog ? !cJSON_IsFalse(can) : jbool(p, e, "sync_saves", 1);
|
||||
const char *mode = jstr(p, e, "sync", "all"); /* per platform, or the emulator's (before 1.2) */
|
||||
m->sync_off = !strcmp(mode, "off");
|
||||
m->sync_states = !m->sync_off && strcmp(mode, "saves") != 0;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "memcard.h"
|
||||
#include "pair.h"
|
||||
#include "platform.h"
|
||||
#include "presets.h"
|
||||
#include "profiles.h"
|
||||
#include "ra2.h"
|
||||
#include "romm.h"
|
||||
@@ -974,6 +975,15 @@ static void route(request *r) {
|
||||
else send_json(fd, 200, update_status());
|
||||
return;
|
||||
}
|
||||
if (is_get && !strcmp(r->path, "/api/presets")) {
|
||||
send_json(fd, 200, presets_status());
|
||||
return;
|
||||
}
|
||||
if (is_post && !strcmp(r->path, "/api/presets/check")) {
|
||||
if (presets_check() != 0) send_error(fd, 409, "a presets check is already running");
|
||||
else send_json(fd, 200, presets_status());
|
||||
return;
|
||||
}
|
||||
/* Autostart works before pairing too: RommPS sets it up and keeps it
|
||||
* current whether or not RomM Sync is paired yet. */
|
||||
if (is_get && !strcmp(r->path, "/api/autostart")) {
|
||||
|
||||
@@ -46,7 +46,7 @@ def cards_cell(c):
|
||||
|
||||
|
||||
def section(console, path):
|
||||
cat = json.load(open(os.path.join(ROOT, path)))
|
||||
cat = json.load(open(os.path.join(ROOT, path)))["emulators"]
|
||||
lines = ["## %s" % console, ""]
|
||||
for e in cat:
|
||||
profile = e.get("profile", {})
|
||||
|
||||
Reference in new issue
Block a user