feat: place files cores read below the save dir

This commit is contained in:
Abdessamad Derraz committed 2026-10-22 20:26:12 +02:00
commit 2aaf0e50d1
50 files changed
+8588 -2031

No files matched your search

+14 -14
View File
@@ -105,24 +105,24 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
| Platform | On its BIOS list | Files its emulators load | Checked by |
|----------|-----------------:|-------------------------:|------------|
| AmberELEC | 414/434 | 4,551/4,655 | MD5 hash |
| Batocera | 353/353 | 13,943/15,038 | MD5 hash |
| AmberELEC | 414/434 | 4,551/4,663 | MD5 hash |
| Batocera | 353/353 | 13,943/15,053 | MD5 hash |
| BizHawk | 118/118 | - | SHA1 hash |
| EmuDeck | 168/168 | 8,174/8,251 | MD5 hash |
| EmuELEC | 544/572 | 4,597/9,948 | MD5 hash |
| KNULLI | 290/290 | 8,896/15,095 | MD5 hash |
| Lakka | 532/532 | 7,601/7,661 | file presence |
| EmuDeck | 168/168 | 8,158/8,272 | MD5 hash |
| EmuELEC | 544/572 | 4,597/9,956 | MD5 hash |
| KNULLI | 290/290 | 8,892/15,114 | MD5 hash |
| Lakka | 532/532 | 7,682/7,742 | file presence |
| MiSTer FPGA | 81/81 | - | MD5 hash |
| MinUI | 0/0 | 78/79 | file presence |
| MyMinUI | 0/0 | 347/356 | file presence |
| NextUI | 0/0 | 343/349 | file presence |
| ROCKNIX | 38/38 | 7,096/8,371 | MD5 hash |
| Recalbox | 351/351 | 2,748/2,919 | MD5 hash |
| RetroArch | 526/526 | 7,610/7,652 | file presence |
| RetroBat | 348/348 | 4,736/9,202 | MD5 hash |
| RetroDECK | 2,007/2,008 | 6,274/6,526 | MD5 hash |
| RetroPie * | 532/532 | 7,883/12,408 | file presence |
| RomM | 383/383 | 83/847 | MD5 hash |
| ROCKNIX | 38/38 | 7,078/8,397 | MD5 hash |
| Recalbox | 351/351 | 2,748/2,933 | MD5 hash |
| RetroArch | 526/526 | 7,691/7,733 | file presence |
| RetroBat | 348/348 | 4,729/9,218 | MD5 hash |
| RetroDECK | 2,007/2,008 | 6,258/6,547 | MD5 hash |
| RetroPie * | 532/532 | 7,858/12,489 | file presence |
| RomM | 383/383 | 83/860 | MD5 hash |
Each fraction is what the pack has over what is needed, counting required and optional files alike since both ship. The first column is the BIOS list the platform publishes. The second counts files its emulators load that this list never mentions, found by reading their source code, and it is routinely several times larger. A short fraction means files are still missing, and they are named in the [gap analysis](https://abdess.github.io/retrobios/gaps/).
That second number is a floor, not a ceiling: an emulator that accepts any file handed to it names none in its code, so nothing there can be counted.
@@ -209,4 +209,4 @@ The scripts and tooling are released under the [MIT License](LICENSE).
The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed.
The reasoning, and where it is weakest, is in [the project's legal position](https://abdess.github.io/retrobios/wiki/legal/).
*Auto-generated on 2026-10-22T16:04:04Z*
*Auto-generated on 2026-10-22T17:48:21Z*
+14 -2
View File
@@ -7,14 +7,16 @@ profiled_date: "2026-10-22"
source_commit: "219d743a1e698101fe1ab8bef43e6563126c3c27"
core_version: "2126.2"
display_name: "Nintendo - 3DS (Azahar)"
library_name: "Azahar"
cores:
- azahar
- azahar-sa
systems:
- nintendo-3ds
# path: is where the libretro core reads a file, standalone_path: where the
# standalone app reads it under its user directory.
# path: is where the libretro core reads a file below the save directory the
# frontend hands it (load_from), standalone_path: where the standalone app
# reads it under its user directory.
notes: |
Successor to Citra (via Lime3DS). The libretro core lives in the same
repository (src/citra_libretro) and is built with ENABLE_LIBRETRO, which
@@ -27,6 +29,9 @@ notes: |
ref: src/citra_libretro/core_settings.cpp:592-603, src/citra_libretro/core_settings.cpp:1048-1080,
src/citra_libretro/environment.cpp:191-208
The core reports library_name "Azahar".
ref: src/citra_libretro/environment.cpp:269
Standalone data dir: azahar-emu under XDG_DATA_HOME (Azahar on Windows and
macOS), with citra-emu and lime3ds-emu kept as legacy locations. A user/
directory beside the executable (Windows) or in the working directory takes
@@ -53,6 +58,7 @@ files:
- name: keys.txt
path: Azahar/sysdata/keys.txt
standalone_path: sysdata/keys.txt
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
hle_fallback: true
@@ -64,6 +70,7 @@ files:
- name: shared_font.bin
path: Azahar/sysdata/shared_font.bin
standalone_path: sysdata/shared_font.bin
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
hle_fallback: true
@@ -73,6 +80,7 @@ files:
- name: seeddb.bin
path: Azahar/sysdata/seeddb.bin
standalone_path: sysdata/seeddb.bin
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
note: "game seed database for 9.6+ titles, an empty one is written if absent"
@@ -81,6 +89,7 @@ files:
- name: otp.bin
path: Azahar/sysdata/otp.bin
standalone_path: sysdata/otp.bin
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
size: 256
@@ -91,6 +100,7 @@ files:
- name: movable.sed
path: Azahar/nand/private/movable.sed
standalone_path: nand/private/movable.sed
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
size: [288, 320]
@@ -102,6 +112,7 @@ files:
- name: SecureInfo_A
path: Azahar/nand/rw/sys/SecureInfo_A
standalone_path: nand/rw/sys/SecureInfo_A
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
size: 273
@@ -113,6 +124,7 @@ files:
- name: LocalFriendCodeSeed_B
path: Azahar/nand/rw/sys/LocalFriendCodeSeed_B
standalone_path: nand/rw/sys/LocalFriendCodeSeed_B
load_from: {libretro: save_dir}
system: nintendo-3ds
required: false
size: 272
+62 -57
View File
@@ -10,6 +10,8 @@ source_commit: "4802094a2c15559541b3da3a6b11c2e12ba5d7e8"
upstream_commit: "32e6628a6a9e5255fd0f7e734386a364ff9c106e"
core_version: "2.6"
display_name: "Nintendo - Wii U (Cemu)"
library_name: "Cemu"
save_folder_once: true
cores:
- cemu
systems: [nintendo-wiiu]
@@ -51,8 +53,11 @@ notes: |
portable directory next to the executable when it exists, otherwise
%APPDATA%/Cemu on Windows, XDG_DATA_HOME/Cemu and XDG_CONFIG_HOME/Cemu on
Linux, and the MLC defaults to <user data>/mlc01. path: entries below are
the libretro destination under the system directory, and standalone_path: is
relative to the Cemu user data directory.
the libretro destination under the system directory, or under the save
directory for the MLC (load_from), and standalone_path: is relative to the
Cemu user data directory, which holds the MLC by default (load_from
data_dir). The core reports library_name "Cemu"
(src/libretro/CemuLibretro.cpp:3354).
ref: src/libretro/CemuLibretro.cpp:1647-1707,
src/libretro/CemuLibretro.cpp:3264-3272,
src/gui/wxgui/CemuApp.cpp:90-123, src/gui/wxgui/CemuApp.cpp:127-171,
@@ -204,7 +209,7 @@ files:
- name: CafeCn.ttf
path: Cemu/mlc01/sys/title/0005001b/10042400/content/CafeCn.ttf
standalone_path: mlc01/sys/title/0005001b/10042400/content/CafeCn.ttf
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
hle_fallback: true
description: "Shared font, Simplified Chinese, console dump"
@@ -217,7 +222,7 @@ files:
- name: CafeKr.ttf
path: Cemu/mlc01/sys/title/0005001b/10042400/content/CafeKr.ttf
standalone_path: mlc01/sys/title/0005001b/10042400/content/CafeKr.ttf
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
hle_fallback: true
description: "Shared font, Korean, console dump"
@@ -226,7 +231,7 @@ files:
- name: CafeStd.ttf
path: Cemu/mlc01/sys/title/0005001b/10042400/content/CafeStd.ttf
standalone_path: mlc01/sys/title/0005001b/10042400/content/CafeStd.ttf
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
hle_fallback: true
description: "Shared font, standard Latin and Japanese, console dump"
@@ -235,7 +240,7 @@ files:
- name: CafeTw.ttf
path: Cemu/mlc01/sys/title/0005001b/10042400/content/CafeTw.ttf
standalone_path: mlc01/sys/title/0005001b/10042400/content/CafeTw.ttf
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
hle_fallback: true
description: "Shared font, Traditional Chinese, console dump"
@@ -426,7 +431,7 @@ files:
- name: swkbd.pack
path: Cemu/mlc01/sys/title/0005001b/1004f000/content/00/swkbd/swkbd.pack
standalone_path: mlc01/sys/title/0005001b/1004f000/content/00/swkbd/swkbd.pack
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Software keyboard applet data, title 0005001b-1004f000"
note: >-
@@ -437,7 +442,7 @@ files:
- name: erreula.pack
path: Cemu/mlc01/sys/title/0005001b/10051000/content/00/erreula/erreula.pack
standalone_path: mlc01/sys/title/0005001b/10051000/content/00/erreula/erreula.pack
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Error viewer applet data, title 0005001b-10051000"
note: >-
@@ -464,7 +469,7 @@ files:
- name: WIIU_COMMON_1_CERT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_COMMON_1_CERT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_COMMON_1_CERT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client certificate, common"
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:471, source src/Cafe/IOSU/legacy/iosu_crypto.cpp:303-330"
@@ -472,7 +477,7 @@ files:
- name: WIIU_COMMON_1_RSA_KEY.aes
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_COMMON_1_RSA_KEY.aes
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_COMMON_1_RSA_KEY.aes
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client key, common, AES-CBC encrypted"
note: "Must be a non-empty multiple of 16 bytes."
@@ -481,7 +486,7 @@ files:
- name: WIIU_ACCOUNT_1_CERT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_ACCOUNT_1_CERT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_ACCOUNT_1_CERT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client certificate, account"
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:472, source src/Cafe/IOSU/legacy/iosu_crypto.cpp:303-330"
@@ -489,7 +494,7 @@ files:
- name: WIIU_ACCOUNT_1_RSA_KEY.aes
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_ACCOUNT_1_RSA_KEY.aes
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_ACCOUNT_1_RSA_KEY.aes
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client key, account, AES-CBC encrypted"
note: "Must be a non-empty multiple of 16 bytes."
@@ -498,7 +503,7 @@ files:
- name: WIIU_OLIVE_1_CERT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_OLIVE_1_CERT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_OLIVE_1_CERT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client certificate, olive (Miiverse)"
note: "Registered twice, under ids 4 and 7."
@@ -507,7 +512,7 @@ files:
- name: WIIU_OLIVE_1_RSA_KEY.aes
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_OLIVE_1_RSA_KEY.aes
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_OLIVE_1_RSA_KEY.aes
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client key, olive, AES-CBC encrypted"
note: "Must be a non-empty multiple of 16 bytes."
@@ -516,7 +521,7 @@ files:
- name: WIIU_VINO_1_CERT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_VINO_1_CERT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_VINO_1_CERT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client certificate, vino (Nintendo TVii)"
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:474, source src/Cafe/IOSU/legacy/iosu_crypto.cpp:303-330"
@@ -524,7 +529,7 @@ files:
- name: WIIU_VINO_1_RSA_KEY.aes
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_VINO_1_RSA_KEY.aes
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_VINO_1_RSA_KEY.aes
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client key, vino, AES-CBC encrypted"
note: "Must be a non-empty multiple of 16 bytes."
@@ -533,7 +538,7 @@ files:
- name: WIIU_WOOD_1_CERT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_WOOD_1_CERT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_WOOD_1_CERT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client certificate, wood (eShop)"
note: "Registered twice, under ids 6 and 8."
@@ -542,7 +547,7 @@ files:
- name: WIIU_WOOD_1_RSA_KEY.aes
path: Cemu/mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_WOOD_1_RSA_KEY.aes
standalone_path: mlc01/sys/title/0005001b/10054000/content/ccerts/WIIU_WOOD_1_RSA_KEY.aes
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
description: "Client key, wood, AES-CBC encrypted"
note: "Must be a non-empty multiple of 16 bytes."
@@ -553,273 +558,273 @@ files:
- name: CACERT_NINTENDO_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:480"
- name: CACERT_NINTENDO_CA_G2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA_G2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA_G2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:481"
- name: CACERT_NINTENDO_CA_G3.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA_G3.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA_G3.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:482"
- name: CACERT_NINTENDO_CLASS2_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CLASS2_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CLASS2_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:483"
- name: CACERT_NINTENDO_CLASS2_CA_G2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CLASS2_CA_G2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CLASS2_CA_G2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:484"
- name: CACERT_NINTENDO_CLASS2_CA_G3.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CLASS2_CA_G3.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CLASS2_CA_G3.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:485"
- name: BALTIMORE_CYBERTRUST_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/BALTIMORE_CYBERTRUST_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/BALTIMORE_CYBERTRUST_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:488"
- name: CYBERTRUST_GLOBAL_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/CYBERTRUST_GLOBAL_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/CYBERTRUST_GLOBAL_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:489"
- name: VERIZON_GLOBAL_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/VERIZON_GLOBAL_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/VERIZON_GLOBAL_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:490"
- name: GLOBALSIGN_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GLOBALSIGN_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GLOBALSIGN_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:491"
- name: GLOBALSIGN_ROOT_CA_R2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GLOBALSIGN_ROOT_CA_R2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GLOBALSIGN_ROOT_CA_R2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:492"
- name: GLOBALSIGN_ROOT_CA_R3.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GLOBALSIGN_ROOT_CA_R3.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GLOBALSIGN_ROOT_CA_R3.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:493"
- name: VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G3.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G3.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G3.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:494"
- name: VERISIGN_UNIVERSAL_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_UNIVERSAL_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_UNIVERSAL_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:495"
- name: VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G5.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G5.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G5.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:496"
- name: THAWTE_PRIMARY_ROOT_CA_G3.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/THAWTE_PRIMARY_ROOT_CA_G3.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/THAWTE_PRIMARY_ROOT_CA_G3.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:497"
- name: THAWTE_PRIMARY_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/THAWTE_PRIMARY_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/THAWTE_PRIMARY_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:498"
- name: GEOTRUST_GLOBAL_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_GLOBAL_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_GLOBAL_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:499"
- name: GEOTRUST_GLOBAL_CA2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_GLOBAL_CA2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_GLOBAL_CA2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:500"
- name: GEOTRUST_PRIMARY_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_PRIMARY_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_PRIMARY_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:501"
- name: GEOTRUST_PRIMARY_CA_G3.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_PRIMARY_CA_G3.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GEOTRUST_PRIMARY_CA_G3.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:502"
- name: ADDTRUST_EXT_CA_ROOT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/ADDTRUST_EXT_CA_ROOT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/ADDTRUST_EXT_CA_ROOT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:503"
- name: COMODO_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/COMODO_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/COMODO_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:504"
- name: UTN_DATACORP_SGC_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/UTN_DATACORP_SGC_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/UTN_DATACORP_SGC_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:505"
- name: UTN_USERFIRST_HARDWARE_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/UTN_USERFIRST_HARDWARE_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/UTN_USERFIRST_HARDWARE_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:506"
- name: DIGICERT_HIGH_ASSURANCE_EV_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_HIGH_ASSURANCE_EV_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_HIGH_ASSURANCE_EV_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:507"
- name: DIGICERT_ASSURED_ID_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_ASSURED_ID_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_ASSURED_ID_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:508"
- name: DIGICERT_GLOBAL_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_GLOBAL_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_GLOBAL_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:509"
- name: GTE_CYBERTRUST_GLOBAL_ROOT.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/GTE_CYBERTRUST_GLOBAL_ROOT.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/GTE_CYBERTRUST_GLOBAL_ROOT.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:510"
- name: VERISIGN_CLASS3_PUBLIC_PRIMARY_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:511"
- name: THAWTE_PREMIUM_SERVER_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/THAWTE_PREMIUM_SERVER_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/THAWTE_PREMIUM_SERVER_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:512"
- name: EQUIFAX_SECURE_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/EQUIFAX_SECURE_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/EQUIFAX_SECURE_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:513"
- name: ENTRUST_SECURE_SERVER_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_SECURE_SERVER_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_SECURE_SERVER_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:514"
- name: VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:515"
- name: ENTRUST_CA_2048.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_CA_2048.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_CA_2048.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:516"
- name: ENTRUST_ROOT_CA.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_ROOT_CA.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_ROOT_CA.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:517"
- name: ENTRUST_ROOT_CA_G2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_ROOT_CA_G2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/ENTRUST_ROOT_CA_G2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:518"
- name: DIGICERT_ASSURED_ID_ROOT_CA_G2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_ASSURED_ID_ROOT_CA_G2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_ASSURED_ID_ROOT_CA_G2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:519"
- name: DIGICERT_GLOBAL_ROOT_CA_G2.der
path: Cemu/mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_GLOBAL_ROOT_CA_G2.der
standalone_path: mlc01/sys/title/0005001b/10054000/content/scerts/DIGICERT_GLOBAL_ROOT_CA_G2.der
load_from: save_dir
load_from: {libretro: save_dir, standalone: data_dir}
required: false
source_ref: "upstream src/Cafe/IOSU/legacy/iosu_crypto.cpp:520"
+13 -2
View File
@@ -7,14 +7,21 @@ profiled_date: "2026-03-23"
source_commit: "a0483e9abe8134ae6b290cdca7d49cb77689f05b"
core_version: "Git"
display_name: "Nintendo - 3DS (Citra)"
library_name: "Citra"
cores: [citra]
systems: [nintendo-3ds]
notes: |
Citra 3DS emulator. Upstream taken down March 2024 (Nintendo lawsuit).
Mirror: https://github.com/citra-mirror/citra
Data dir: {save_dir}/Citra/ or {system_dir}/Citra/
ref: src/citra_libretro/citra_libretro.cpp:460-466
Data dir: {save_dir}/Citra/ under the default "LibRetro Default" option
citra_use_libretro_save_path, {system_dir}/Citra/ only when the frontend
gives no save directory. sysdata/ sits under it.
ref: src/citra_libretro/citra_libretro.cpp:157, src/citra_libretro/citra_libretro.cpp:445-466,
src/citra_libretro/environment.cpp:139, src/common/file_util.cpp:855
The core reports library_name "Citra".
ref: src/citra_libretro/environment.cpp:180
HLE for most services. Embedded open-source replacements for shared font,
Mii data, country list, and bad word list compiled into the binary.
@@ -25,6 +32,7 @@ notes: |
files:
- name: aes_keys.txt
path: "Citra/sysdata/aes_keys.txt"
load_from: {libretro: save_dir}
system: nintendo-3ds
description: "AES encryption keys"
required: false
@@ -33,6 +41,7 @@ files:
- name: boot9.bin
path: "Citra/sysdata/boot9.bin"
load_from: {libretro: save_dir}
system: nintendo-3ds
description: "ARM9 bootrom"
required: false
@@ -43,6 +52,7 @@ files:
- name: shared_font.bin
path: "Citra/sysdata/shared_font.bin"
load_from: {libretro: save_dir}
system: nintendo-3ds
description: "system shared font (legacy format)"
required: false
@@ -52,6 +62,7 @@ files:
- name: seeddb.bin
path: "Citra/sysdata/seeddb.bin"
load_from: {libretro: save_dir}
system: nintendo-3ds
description: "game seed database"
required: false
+3
View File
@@ -7,6 +7,7 @@ profiled_date: "2026-03-23"
source_commit: "95e2f06c179c8daf44fa344c206f549cf332d21b"
core_version: "Git"
display_name: "Nintendo - 3DS (Citra 2018)"
library_name: "Citra2018"
cores: [citra2018]
systems:
- nintendo-3ds
@@ -24,6 +25,7 @@ notes: |
files:
- name: aes_keys.txt
path: "Citra/sysdata/aes_keys.txt"
load_from: save_dir
system: nintendo-3ds
required: false
note: "AES encryption keys for decrypting 3DS content"
@@ -31,6 +33,7 @@ files:
- name: shared_font.bin
path: "Citra/sysdata/shared_font.bin"
load_from: save_dir
system: nintendo-3ds
required: false
hle_fallback: true
+24 -5
View File
@@ -4,9 +4,11 @@ core_classification: community_fork
source: "https://github.com/libretro/citra"
upstream: "https://github.com/citra-emu/citra"
profiled_date: "2026-03-23"
source_commit: "a0483e9abe8134ae6b290cdca7d49cb77689f05b"
source_commit: "5401990a9be46e4497abc92db3d5f2042674303d"
source_branch: canary
core_version: "Git"
display_name: "Nintendo - 3DS (Citra Canary/Experimental)"
library_name: "Citra Canary"
cores: [citra_canary]
systems:
- nintendo-3ds
@@ -18,8 +20,23 @@ notes: |
Upstream citra-emu/citra taken down March 2024 (Nintendo lawsuit).
Mirror: https://github.com/citra-mirror/citra
Data dir: {system_dir}/Citra/sysdata/
ref: src/citra_libretro/citra_libretro.cpp:305-314
The buildbot builds citra_canary from the canary branch of libretro/citra
(libretro-super recipes/windows/cores-windows-x64_seh-noccache).
Under the default option citra_use_libretro_save_path the core points the
root and user directories at {save_dir}/Citra ({system_dir}/Citra without a
save directory), but GetUserPath fills sysdata/ on its first call, before
that, and the UserDir case never moves it. aes_keys.txt and
shared_font.bin are read from the host's Citra data directory: user/sysdata/
beside the working directory when ./user exists, else
$XDG_DATA_HOME/citra-emu/sysdata/ (Linux) or user/sysdata/ beside the
executable, else %APPDATA%/Citra/sysdata/ (Windows). Neither the system nor
the save directory of the frontend.
ref: src/citra_libretro/citra_libretro.cpp:295-316, src/common/file_util.cpp:674-736,
src/common/common_paths.h:16-38
The core reports library_name "Citra Canary".
ref: src/citra_libretro/environment.cpp:131
Embedded open-source replacements for shared font, country list, bad word list.
ref: src/core/file_sys/archive_ncch.cpp:142-147
@@ -28,14 +45,16 @@ notes: |
files:
- name: aes_keys.txt
path: "Citra/sysdata/aes_keys.txt"
path: "sysdata/aes_keys.txt"
load_from: data_dir
system: nintendo-3ds
required: false
note: "AES encryption keys for decrypting 3DS content"
source_ref: "src/core/hw/aes/key.cpp:71-131"
- name: shared_font.bin
path: "Citra/sysdata/shared_font.bin"
path: "sysdata/shared_font.bin"
load_from: data_dir
system: nintendo-3ds
required: false
hle_fallback: true
+15 -7
View File
@@ -9,6 +9,7 @@ source_commit: "4d23cf151640eb810cb1b8e9d9fc922cf59c0b87"
upstream_commit: "f5dfb7e3d36ce24551ed0bb886b79f3f192dac9d"
core_version: "Git"
display_name: "Nintendo - GameCube / Wii (Dolphin)"
library_name: "dolphin-emu"
cores:
- dolphin
- "dolphin-gui"
@@ -139,7 +140,8 @@ files:
# Used by integrated mGBA core for GameCube-GBA connectivity.
# Loaded from User/GBA/gba_bios.bin (no Sys fallback).
- name: "gba_bios.bin"
path: "GBA/gba_bios.bin"
path: "User/GBA/gba_bios.bin"
standalone_path: "GBA/gba_bios.bin"
load_from:
libretro: save_dir
required: false
@@ -152,7 +154,8 @@ files:
# Not typically user-supplied, but needed for full Wii functionality.
- name: "SYSCONF"
path: "Wii/shared2/sys/SYSCONF"
path: "User/Wii/shared2/sys/SYSCONF"
standalone_path: "Wii/shared2/sys/SYSCONF"
load_from:
libretro: save_dir
required: false
@@ -161,7 +164,8 @@ files:
source_ref: "upstream Source/Core/Common/CommonPaths.h:118, source Source/Core/Common/FileUtil.cpp:905"
- name: "setting.txt"
path: "Wii/title/00000001/00000002/data/setting.txt"
path: "User/Wii/title/00000001/00000002/data/setting.txt"
standalone_path: "Wii/title/00000001/00000002/data/setting.txt"
load_from:
libretro: save_dir
size: 256
@@ -188,7 +192,8 @@ files:
# -- Wii SD Card Image --
- name: "WiiSD.raw"
path: "Load/WiiSD.raw"
path: "User/Load/WiiSD.raw"
standalone_path: "Load/WiiSD.raw"
load_from:
libretro: save_dir
required: false
@@ -229,7 +234,8 @@ files:
# -- NAND Certificates (auto-extracted) --
- name: "clientca.pem"
path: "Wii/clientca.pem"
path: "User/Wii/clientca.pem"
standalone_path: "Wii/clientca.pem"
load_from:
libretro: save_dir
required: false
@@ -238,7 +244,8 @@ files:
source_ref: "source Source/Core/DiscIO/NANDImporter.cpp:220-304"
- name: "clientcakey.pem"
path: "Wii/clientcakey.pem"
path: "User/Wii/clientcakey.pem"
standalone_path: "Wii/clientcakey.pem"
load_from:
libretro: save_dir
required: false
@@ -247,7 +254,8 @@ files:
source_ref: "source Source/Core/DiscIO/NANDImporter.cpp:256"
- name: "rootca.pem"
path: "Wii/rootca.pem"
path: "User/Wii/rootca.pem"
standalone_path: "Wii/rootca.pem"
load_from:
libretro: save_dir
required: false
+2
View File
@@ -7,6 +7,7 @@ profiled_date: "2026-03-23"
source_commit: "0ce31536bef3162fe7e69ff5f555334ec4913cef"
core_version: "v0.2.97.29"
display_name: "Arcade (FB Alpha 2012)"
library_name: "FB Alpha 2012"
cores:
- fbalpha2012
- fbalpha
@@ -55,6 +56,7 @@ notes: |
files:
- name: "hiscore.dat"
path: "hiscore.dat"
description: "High score persistence database"
load_from: save_dir
required: false
+2
View File
@@ -7,6 +7,7 @@ profiled_date: "2026-03-23"
source_commit: "57f8015c2e0cdebcf42d3add2c314bb7448f9421"
core_version: "v0.2.97.29"
display_name: "Arcade (FB Alpha 2012 CPS-3)"
library_name: "FB Alpha 2012 CPS-3"
cores:
- fbalpha2012_cps3
systems:
@@ -53,6 +54,7 @@ notes: |
files:
- name: "hiscore.dat"
path: "hiscore.dat"
description: "High score persistence database"
load_from: save_dir
required: false
+46 -1
View File
@@ -8,6 +8,7 @@ source_commit: "089bb69207fe8fa5eec0006d1d759fb6426f4027"
upstream_commit: "f362278ccd4c60991caac3b4d240d4a3f751bea2"
core_version: "v5.0.7"
display_name: "Commodore - Amiga (FS-UAE)"
library_name: ""
cores:
- fsuae
found_by: content
@@ -19,7 +20,15 @@ notes: |
Standalone: scans --kickstarts_dir for .rom/.bin files (default: Documents/FS-UAE/Kickstarts/).
Batocera overrides to bios/amiga/ via --kickstarts_dir (batocera fsuaePaths.py:8, batocera fsuaeGenerator.py:68).
Libretro port: scans save_dir/fsuae/Kickstarts/ (frozen at v2.7.15).
Libretro port: scans save_dir/fsuae/Kickstarts/ (frozen at v2.7.15), and
reads rom.key there. The base directory is <save_dir>/fsuae, the build
leaving USE_FSUAEDIRS off. The core reports an empty library_name, for
which RetroArch adds no folder of its own.
ref: source Makefile.libretro:125-126, source sources/src/fs-uae/paths.c:133-138,
source sources/src/fs-uae/paths.c:196-198, source sources/src/fs-uae/paths.c:258-265,
source sources/src/fs-uae/config.c:128, source libretro/libretro.c:194-206,
source libretro/libretro.c:575
Identification by SHA-1 checksum, not filename (rommgr.cpp:92-151, od-fs/python/fsuae/roms/romservice.py:78-91).
AROS kickstart (1 MB) compiled into binary as fallback for all models (rommgr.cpp:112).
rom.key searched in kickstart dir and multiple fallback paths (rommgr.cpp:1397-1498).
@@ -34,6 +43,9 @@ notes: |
files:
- name: "amiga-os-130.rom"
path: "fsuae/Kickstarts/amiga-os-130.rom"
standalone_path: "amiga-os-130.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -45,6 +57,9 @@ files:
source_ref: "upstream rommgr.cpp:150-151 (id=6, ROMTYPE_KICK)"
- name: "amiga-os-204.rom"
path: "fsuae/Kickstarts/amiga-os-204.rom"
standalone_path: "amiga-os-204.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -56,6 +71,9 @@ files:
source_ref: "upstream rommgr.cpp:158-159 (id=7, ROMTYPE_KICK)"
- name: "amiga-os-205.rom"
path: "fsuae/Kickstarts/amiga-os-205.rom"
standalone_path: "amiga-os-205.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -67,6 +85,9 @@ files:
source_ref: "upstream rommgr.cpp:164-165 (id=10, ROMTYPE_KICK)"
- name: "amiga-os-120.rom"
path: "fsuae/Kickstarts/amiga-os-120.rom"
standalone_path: "amiga-os-120.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -78,6 +99,9 @@ files:
source_ref: "upstream rommgr.cpp:148-149 (id=5, ROMTYPE_KICK)"
- name: "amiga-os-310-a1200.rom"
path: "fsuae/Kickstarts/amiga-os-310-a1200.rom"
standalone_path: "amiga-os-310-a1200.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -89,6 +113,9 @@ files:
source_ref: "upstream rommgr.cpp:185-186 (id=15, ROMTYPE_KICK)"
- name: "amiga-os-310-a3000.rom"
path: "fsuae/Kickstarts/amiga-os-310-a3000.rom"
standalone_path: "amiga-os-310-a3000.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -100,6 +127,9 @@ files:
source_ref: "upstream rommgr.cpp:189-190 (id=61, ROMTYPE_KICK)"
- name: "amiga-os-310-a4000.rom"
path: "fsuae/Kickstarts/amiga-os-310-a4000.rom"
standalone_path: "amiga-os-310-a4000.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -111,6 +141,9 @@ files:
source_ref: "upstream rommgr.cpp:195-196 (id=16, ROMTYPE_KICK)"
- name: "amiga-os-310-cd32.rom"
path: "fsuae/Kickstarts/amiga-os-310-cd32.rom"
standalone_path: "amiga-os-310-cd32.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
hle_fallback: true
@@ -122,6 +155,9 @@ files:
source_ref: "upstream rommgr.cpp:312-313 (id=18, ROMTYPE_KICKCD32)"
- name: "amiga-ext-310-cd32.rom"
path: "fsuae/Kickstarts/amiga-ext-310-cd32.rom"
standalone_path: "amiga-ext-310-cd32.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
size: 524288
@@ -132,6 +168,9 @@ files:
source_ref: "upstream rommgr.cpp:314-315 (id=19, ROMTYPE_EXTCD32)"
- name: "cd32fmv.rom"
path: "fsuae/Kickstarts/cd32fmv.rom"
standalone_path: "cd32fmv.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
size: 262144
@@ -142,6 +181,9 @@ files:
source_ref: "upstream rommgr.cpp:323-326 (id=23/74, ROMTYPE_CD32CART)"
- name: "amiga-ext-130-cdtv.rom"
path: "fsuae/Kickstarts/amiga-ext-130-cdtv.rom"
standalone_path: "amiga-ext-130-cdtv.rom"
load_from: {libretro: save_dir}
system: amiga
required: false
size: 262144
@@ -152,6 +194,9 @@ files:
source_ref: "upstream rommgr.cpp:328-329 (id=20, ROMTYPE_EXTCDTV)"
- name: "rom.key"
path: "fsuae/Kickstarts/rom.key"
standalone_path: "rom.key"
load_from: {libretro: save_dir}
system: amiga
found_by: name
required: false
+22 -12
View File
@@ -8,6 +8,7 @@ source_commit: "f228bde5fbf4861ba749a229803addcd6238663f"
upstream_commit: "cba6d7279b44894297d8c1dde1effdbba3605c32"
core_version: "Git"
display_name: "Nintendo - GameCube / Wii (Ishiiruka)"
library_name: "Ishiiruka"
cores:
- ishiiruka
systems: [nintendo-gamecube, nintendo-wii]
@@ -131,7 +132,8 @@ files:
# -- Wii System Files --
# Auto-generated/managed by Dolphin's Wii emulation.
- name: "SYSCONF"
path: "Wii/shared2/sys/SYSCONF"
path: "User/Wii/shared2/sys/SYSCONF"
standalone_path: "Wii/shared2/sys/SYSCONF"
load_from:
libretro: save_dir
required: false
@@ -140,7 +142,8 @@ files:
source_ref: "source Source/Core/Common/CommonPaths.h:59, 95, source Source/Core/Common/SysConf.cpp:40"
- name: "setting.txt"
path: "Wii/title/00000001/00000002/data/setting.txt"
path: "User/Wii/title/00000001/00000002/data/setting.txt"
standalone_path: "Wii/title/00000001/00000002/data/setting.txt"
load_from:
libretro: save_dir
size: 256
@@ -151,7 +154,8 @@ files:
# -- Wii SD Card Image --
- name: "sd.raw"
path: "Wii/sd.raw"
path: "User/Wii/sd.raw"
standalone_path: "Wii/sd.raw"
load_from:
libretro: save_dir
required: false
@@ -179,28 +183,34 @@ files:
# -- NAND Certificates (auto-extracted) --
- name: "clientca.pem"
path: "Wii/clientca.pem"
path: "User/Wii/clientca.pem"
standalone_path: "Wii/clientca.pem"
load_from:
libretro: save_dir
required: false
hle_fallback: true
mode: standalone
note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "source Source/Core/DiscIO/NANDImporter.cpp:199-264"
source_ref: "source Source/Core/DiscIO/NANDImporter.cpp:199-264, source Source/Core/Core/IOS/Network/SSL.cpp:339-341, source Source/Core/Core/WiiRoot.cpp:73-76"
- name: "clientcakey.pem"
path: "Wii/clientcakey.pem"
path: "User/Wii/clientcakey.pem"
standalone_path: "Wii/clientcakey.pem"
load_from:
libretro: save_dir
required: false
hle_fallback: true
mode: standalone
note: "SSL client private key. Auto-extracted from IOS13 content during NAND import"
source_ref: "upstream Source/Core/DiscIO/NANDImporter.cpp:235"
source_ref: "upstream Source/Core/DiscIO/NANDImporter.cpp:235, source Source/Core/Core/IOS/Network/SSL.cpp:339-343, source Source/Core/Core/WiiRoot.cpp:73-76"
- name: "rootca.pem"
path: "Wii/rootca.pem"
path: "User/Wii/rootca.pem"
standalone_path: "Wii/rootca.pem"
load_from:
libretro: save_dir
required: false
hle_fallback: true
mode: standalone
note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "upstream Source/Core/DiscIO/NANDImporter.cpp:236"
source_ref: "upstream Source/Core/DiscIO/NANDImporter.cpp:236, source Source/Core/Core/IOS/Network/SSL.cpp:404-406, source Source/Core/Core/WiiRoot.cpp:73-76"
notes:
hle_available: true
+11 -3
View File
@@ -8,6 +8,7 @@ profiled_date: "2026-03-24"
source_commit: "3787358bdae41d5532ccd00c8998a527ae949949"
core_version: "Git"
display_name: "Nintendo - 3DS (Panda3DS)"
library_name: "Panda3DS"
cores: [panda3ds]
systems: [nintendo-3ds]
@@ -19,13 +20,19 @@ notes: |
option panda3ds_dsp_emulation). System archives (shared font, bad word
list, country list, mii data) are compiled into the binary via cmrc.
The libretro core uses the save directory, not RetroArch's system
directory. Files go under <save_dir>/Emulator Files/sysdata/.
directory. Files go under <save_dir>/Emulator Files/sysdata/, under the
working directory when the frontend gives no save directory. The core
reports library_name "Panda3DS". Standalone reads them from Emulator
Files/sysdata/ below its SDL pref path.
ref: src/libretro_core.cpp:29-31, src/libretro_core.cpp:233, src/libretro_core.cpp:277-281,
src/emulator.cpp:196-208
The .info declares firmware_count=0. is_experimental = true.
files:
- name: "aes_keys.txt"
path: "Emulator Files/sysdata/aes_keys.txt"
load_from: save_dir
standalone_path: "Emulator Files/sysdata/aes_keys.txt"
load_from: {libretro: save_dir, standalone: data_dir}
description: "AES encryption keys for decrypting encrypted ROMs"
required: false
source_ref: "src/emulator.cpp:229,237-238, src/core/crypto/aes_engine.cpp:13-92"
@@ -38,7 +45,8 @@ files:
- name: "seeddb.bin"
path: "Emulator Files/sysdata/seeddb.bin"
load_from: save_dir
standalone_path: "Emulator Files/sysdata/seeddb.bin"
load_from: {libretro: save_dir, standalone: data_dir}
description: "Seed database for seed-encrypted games"
required: false
source_ref: "src/emulator.cpp:230,241-242, src/core/loader/ncch.cpp:78-93"
+15
View File
@@ -6,6 +6,7 @@ profiled_date: "2026-10-21"
source_commit: "2c3d7023e9136f75c789803b44f119d8749dc104"
core_version: "8.0_pre"
display_name: "Atari - 2600 (Stella)"
library_name: "Stella"
cores:
- stella
- "STELLASA"
@@ -45,42 +46,56 @@ notes: |
files:
- name: KVS1.WAV
path: stella/KVS1.WAV
standalone_path: KVS1.WAV
description: "KidVid Voice Module audio tape 1 (Smurfs Save the Day)"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
category: game_data
source_ref: "src/emucore/KidVid.cxx:246-248, src/emucore/KidVid.cxx:255-268"
- name: KVS2.WAV
path: stella/KVS2.WAV
standalone_path: KVS2.WAV
description: "KidVid Voice Module audio tape 2 (Smurfs Save the Day)"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
category: game_data
source_ref: "src/emucore/KidVid.cxx:246-248, src/emucore/KidVid.cxx:255-268"
- name: KVS3.WAV
path: stella/KVS3.WAV
standalone_path: KVS3.WAV
description: "KidVid Voice Module audio tape 3 (Smurfs Save the Day)"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
category: game_data
source_ref: "src/emucore/KidVid.cxx:246-248, src/emucore/KidVid.cxx:255-268"
- name: KVB1.WAV
path: stella/KVB1.WAV
standalone_path: KVB1.WAV
description: "KidVid Voice Module audio tape 1 (Berenstain Bears)"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
category: game_data
source_ref: "src/emucore/KidVid.cxx:246-248, src/emucore/KidVid.cxx:255-268"
- name: KVB2.WAV
path: stella/KVB2.WAV
standalone_path: KVB2.WAV
description: "KidVid Voice Module audio tape 2 (Berenstain Bears)"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
category: game_data
source_ref: "src/emucore/KidVid.cxx:246-248, src/emucore/KidVid.cxx:255-268"
- name: KVB3.WAV
path: stella/KVB3.WAV
standalone_path: KVB3.WAV
description: "KidVid Voice Module audio tape 3 (Berenstain Bears)"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
category: game_data
source_ref: "src/emucore/KidVid.cxx:246-248, src/emucore/KidVid.cxx:255-268"
- name: KVSHARED.WAV
path: stella/KVSHARED.WAV
standalone_path: KVSHARED.WAV
description: "KidVid Voice Module shared audio samples"
required: false
load_from: {libretro: save_dir, standalone: config_dir}
+9 -1
View File
@@ -7,6 +7,7 @@ upstream: https://github.com/wheremyfoodat/Panda3DS
profiled_date: "2026-03-25"
source_commit: "b97b0befa326105e181838bc6fa11c546de8294d"
core_version: git
library_name: "Trident"
cores:
- trident
@@ -21,10 +22,15 @@ notes: >-
the game ROM at runtime, not from an external file.
DSP modes: HLE (default), LLE (Teakra), Null.
Files are resolved from the RetroArch save directory
(<save_dir>/Emulator Files/sysdata/), not the system directory.
(<save_dir>/Emulator Files/sysdata/), not the system directory, or from
the working directory when the frontend gives none. The core reports
library_name "Trident". The wrapper is vendor/Panda3DS at 69095ee6
(DanAlexMorton/Panda3DS src/libretro_core.cpp:30, 243, 280-285).
files:
- name: aes_keys.txt
path: "Emulator Files/sysdata/aes_keys.txt"
load_from: save_dir
description: AES encryption keys for decrypting encrypted 3DS ROMs
required: false
source_ref: >-
@@ -33,6 +39,8 @@ files:
src/core/loader/ncch.cpp:151-164 (panic if encrypted ROM loaded without keys)
- name: seeddb.bin
path: "Emulator Files/sysdata/seeddb.bin"
load_from: save_dir
description: seed database for seed-encrypted 3DS games
required: false
source_ref: >-
+1 -1
View File
@@ -11,7 +11,7 @@
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "abc5b672e1d4b2f507b6d8c3d5c23a120a7dffea6e583559bdcc92c9cbbc7692"
$defaultInstallSha256 = "b574c5beb84e9988a53a742f1abf0ad98fcb23dae961facef98c955856380704"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+215 -13
View File
@@ -105,6 +105,8 @@ OMISSION_REASONS = {
"user_provided": "user provided",
"no_platform_slug": "no platform folder",
"below_platform_folder": "below the platform folder",
"no_save_directory": "no save directory",
"no_core_folder": "no core folder",
}
# What a file is when it is not firmware, as the manifest's category field
# says, and as the installer words it. Firmware carries no category.
@@ -2398,6 +2400,101 @@ def _destination_path(root: Path, value: object) -> Path:
return root.resolve() / Path(*relative.parts)
class SavePlace(NamedTuple):
"""The save directory a frontend's config names, whether it hands each
core a folder of its own below it (RetroArch's sort_savefiles_enable,
runloop.c runloop_path_set_redirect), and whether the file list's dests
hold that folder (its save_directory.by_core)."""
directory: Path
by_core: bool
listed_by_core: bool = True
# BIOS folder -> where the files read below its frontend's save directory go,
# set once the save directory is known (save_directory_for).
_SAVE_DIRS: dict[Path, SavePlace] = {}
def _entry_place(root: Path, entry: dict) -> tuple[Path, object]:
"""The folder an entry's dest is relative to, and the dest.
An entry with root: save is relative to the save directory the frontend
hands the core, below the BIOS folder's frontend save directory and the
core's own folder where the frontend sorts saves by core. Any other is
relative to the BIOS folder.
"""
if entry.get("root") != "save":
return root, entry.get("dest")
place = _SAVE_DIRS.get(root)
if place is None:
raise ValueError(f"no save directory known for {entry.get('dest')}")
dest = entry.get("dest")
folder = entry.get("core_folder")
# No core_folder: the core reads the same place whether the frontend
# sorts saves by core or not (an empty library_name, or Cemu adding its
# own folder only where the directory is not named after it).
if folder and isinstance(dest, str) and place.by_core != place.listed_by_core:
if place.listed_by_core:
dest = dest.split("/", 1)[1] if "/" in dest else dest
else:
dest = f"{folder}/{dest}"
return place.directory, dest
def _entry_path(root: Path, entry: dict) -> Path:
"""Where an entry is written, as _entry_place places it."""
return _destination_path(*_entry_place(root, entry))
def _check_root(entry: dict, dest: str) -> None:
"""root is absent or save, and core_folder one folder name of a save entry."""
root = entry.get("root")
if root is not None and root != "save":
raise ValueError(f"invalid root for {dest}")
folder = entry.get("core_folder")
if folder is None:
return
if root != "save":
raise ValueError(f"core_folder outside root: save for {dest}")
if len(_safe_relative_path(folder, f"core_folder of {dest}").parts) != 1:
raise ValueError(f"core_folder must be one folder name for {dest}")
def _check_save_directory(data: dict) -> None:
"""The manifest's save_directory, which every root: save entry needs."""
layout = data.get("save_directory")
entries = [
entry for field in ("files", "case_variants", "omitted_files")
for entry in data.get(field) or [] if isinstance(entry, dict)
]
if layout is None:
# An omission may name one: the list says the file exists and why
# this platform cannot place it.
installable = [
entry for field in ("files", "case_variants")
for entry in data.get(field) or [] if isinstance(entry, dict)
]
if any(entry.get("root") == "save" for entry in installable):
raise ValueError("entries below the save directory and no save_directory")
return
if (
not isinstance(layout, dict)
or set(layout) != {"folder", "by_core"}
or not isinstance(layout["by_core"], bool)
or len(_safe_relative_path(layout["folder"], "save_directory.folder").parts) != 1
):
raise ValueError("invalid save_directory")
if layout["by_core"]:
# The core's folder heads each dest it names: dropped, never guessed,
# where the frontend does not sort saves by core.
for entry in entries:
folder = entry.get("core_folder")
dest = entry.get("dest")
if folder and isinstance(dest, str) and dest.split("/", 1)[0] != folder:
raise ValueError(f"{dest} does not start with its core_folder {folder}")
def _check_tags(entry: dict, field: str, where: str) -> None:
"""A selection field is absent or a short list of short strings."""
value = entry.get(field)
@@ -2531,6 +2628,7 @@ def _check_entry(
if dest in seen_destinations:
raise ValueError(f"duplicate manifest destination: {dest}")
seen_destinations.add(dest)
_check_root(entry, dest)
size = _check_download(entry, where, dest)
largest = size
@@ -2643,6 +2741,7 @@ def _validate_manifest(data: object, plat: str) -> dict:
raise ValueError("invalid data_directories list")
for index, entry in enumerate(data_dirs):
_check_data_directory(entry, f"data_directories[{index}]")
_check_save_directory(data)
declared_total_files = data.get("total_files")
if declared_total_files is not None and declared_total_files != len(files):
@@ -2667,6 +2766,7 @@ def _validate_manifest(data: object, plat: str) -> dict:
raise ValueError(f"duplicate or conflicting omitted destination: {dest}")
seen_omitted.add(dest)
_check_omission(entry, dest)
_check_root(entry, dest)
claims = entry.get("alternatives", [])
if not isinstance(claims, list) or len(claims) > MAX_SELECTION_TAGS:
raise ValueError(f"invalid omitted alternatives for {dest}")
@@ -3371,6 +3471,89 @@ def _sha256_file(path: Path) -> str:
return _digest_file(path, ("sha256",))["sha256"]
def _cfg_true(value: str | None) -> bool:
return (value or "").strip().lower() == "true"
def save_directory_for(
bios_path: Path, manifest: dict, override: Path | None = None,
) -> SavePlace | str:
"""The save directory the platform's frontend hands its cores, or why
this run cannot tell.
A RetroArch found by its retroarch.cfg answers from it: savefile_directory
as RetroArch expands it, a value left at default or naming no folder
being saves/ beside the config (configuration.c config_load_file keeps
the default unless path_is_directory), and sort_savefiles_enable, true by
default (config.def.h:1099). savefiles_in_content_dir and
sort_savefiles_by_content_enable put the directory beside each game: no
single place then. Without a config, the BIOS folder standing at its
default name puts the save directory beside it, as the pack lays them out.
``override`` (--save-dir) names the directory itself.
"""
layout = manifest.get("save_directory")
if not layout:
return "the file list names no save directory for this platform"
folder = layout["folder"]
listed = bool(layout["by_core"])
by_core = listed
found = _RETROARCH_CONFIGS.get(bios_path)
if found is not None:
cfg_path, app_dir = found
for key in ("savefiles_in_content_dir", "sort_savefiles_by_content_enable"):
if _cfg_true(_android_cfg_value(cfg_path, key)):
return (f"RetroArch keeps saves beside each game ({key} in {cfg_path}): "
"name the folder with --save-dir")
sort = _android_cfg_value(cfg_path, "sort_savefiles_enable")
if sort is not None:
by_core = _cfg_true(sort)
if override is not None:
return SavePlace(override, by_core, listed)
directory = _retroarch_cfg_dir(cfg_path, "savefile_directory", app_dir, folder)
if directory is None or not directory.is_dir():
directory = app_dir / folder
return SavePlace(directory, by_core, listed)
if override is not None:
return SavePlace(override, by_core, listed)
base = manifest.get("base_destination") or ""
if base and bios_path.name == base:
return SavePlace(bios_path.parent / folder, by_core, listed)
return (f"{bios_path} is not the frontend's default BIOS folder: "
"name its save directory with --save-dir")
def place_save_entries(
files: list[dict], bios_path: Path, manifest: dict, override: Path | None,
display: str,
) -> tuple[list[dict], list[dict]]:
"""Find the save directory for the entries read below it.
Returns the files to install and, when the save directory is unknown,
the entries left out as omissions, said once with the reason.
"""
saved = [entry for entry in files if entry.get("root") == "save"]
if not saved:
return files, []
place = save_directory_for(bios_path, manifest, override)
if isinstance(place, SavePlace):
_SAVE_DIRS[bios_path] = place
where = f"{place.directory}{os.sep}<core>" if place.by_core else str(place.directory)
print(f" {_plural(len(saved), 'file')} read from {display}'s save directory "
f"go to {where}")
return files, []
print(f" {_plural(len(saved), 'file')} read from {display}'s save directory "
f"left out: {place}")
kept = [entry for entry in files if entry.get("root") != "save"]
unplaced = [
{"dest": entry["dest"], "name": PurePosixPath(entry["dest"]).name,
"system": "", "required": False, "reason": "no_save_directory",
"cores": entry.get("cores"), "root": "save",
**({"core_folder": entry["core_folder"]} if entry.get("core_folder") else {})}
for entry in saved
]
return kept, unplaced
def _classify_local(entry: dict, dest: Path) -> str:
"""Return 'up_to_date' or 'mismatched' for a file already on disk."""
expected = {
@@ -3400,7 +3583,7 @@ def check_local(
present: list[tuple[dict, Path]] = []
for f in files:
dest = _destination_path(bios_path, f["dest"])
dest = _entry_path(bios_path, f)
if dest.exists():
present.append((f, dest))
else:
@@ -3667,12 +3850,12 @@ def plan_kept(entries: list[dict], root: Path) -> dict[Path, list[int]]:
planned: dict[Path, list[int]] = {}
for entry in entries:
try:
dest = _destination_path(root, entry["dest"])
dest = _entry_path(root, entry)
info = os.lstat(dest)
except (OSError, ValueError):
continue
if stat.S_ISREG(info.st_mode):
run = KEPT.plan(_kept_slot(root, entry["dest"])[0], info.st_size)
run = KEPT.plan(_kept_slot(*_entry_place(root, entry))[0], info.st_size)
tally = planned.setdefault(run, [0, 0])
tally[0] += 1
tally[1] += info.st_size
@@ -3759,7 +3942,7 @@ def _download_one(
if _stop.is_set():
return str(f.get("dest", "?")), False, "stopped"
try:
dest = _destination_path(bios_path, f["dest"])
dest = _entry_path(bios_path, f)
except ValueError:
return str(f.get("dest", "?")), False, "unsafe destination"
try:
@@ -3882,7 +4065,7 @@ def _download_one(
_clear_read_only(dest)
# The file in place, a personal dump or another revision, is
# moved aside first: replaced, it was gone for good.
KEPT.keep(*_kept_slot(bios_path, f["dest"]), dest, expected, tmp_path)
KEPT.keep(*_kept_slot(*_entry_place(bios_path, f)), dest, expected, tmp_path)
if partial is not None and _NO_RENAME_WHILE_OPEN:
# Windows renames no open file. Elsewhere the lock is held
# until the copy has its final name.
@@ -4168,7 +4351,9 @@ def _folder_writable(path: Path) -> bool:
# Options that take a value, as a hint gives them back.
_VALUED_OPTIONS = ("--system", "--core", "--emulator", "--region", "--target", "--cores-dir")
_VALUED_OPTIONS = (
"--system", "--core", "--emulator", "--region", "--target", "--cores-dir", "--save-dir",
)
class Invocation:
@@ -4466,9 +4651,11 @@ def _absent_required(omitted: list[dict], bios_path: Path) -> list[dict]:
if not entry.get("required", True) or not dest:
continue
try:
placed = _destination_path(bios_path, dest).is_file()
placed = _entry_path(bios_path, {**entry, "dest": dest}).is_file()
except ValueError:
placed = False # a path climbing out of the folder is never one we read
# A path climbing out of the folder is never one we read, and one
# below a save directory this run does not know is not looked at.
placed = False
if not placed:
absent.append(entry)
return absent
@@ -5720,6 +5907,14 @@ def main() -> None:
help="install only what the cores already installed read "
"(RetroArch, Lakka, RetroPie)",
)
parser.add_argument(
"--save-dir",
type=Path,
metavar="PATH",
help="the frontend's save directory (RetroArch: savefile_directory), "
"for the files some cores read there (Azahar's keys and system "
"data), when it is not found",
)
parser.add_argument(
"--cores-dir",
type=Path,
@@ -5840,7 +6035,8 @@ def main() -> None:
# The folder says where each file goes: nothing else applies.
alone = ("platform", "dest", "target", "every_board", "check", "list_platforms",
"list_targets", "standalone_copies", "emulator", "installed_cores",
"cores_dir", "system", "core", "region", "no_game_data", "list_systems",
"cores_dir", "save_dir", "system", "core", "region", "no_game_data",
"list_systems",
"list_cores", "romm_firmware", "list_emulators", "events", "quiet",
"yes", "verbose")
given = [name for name in alone if getattr(args, name)]
@@ -6434,6 +6630,13 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
f"under {library}")
bios_path = library
files, unplaced = place_save_entries(
files, bios_path, manifest, args.save_dir, display_name(plat_name, manifest)
)
if unplaced:
total_omitted += len(unplaced)
omitted_seen.extend(unplaced)
print("\nChecking existing files...")
to_download, up_to_date, mismatched = check_local(
files, bios_path, jobs=args.jobs, term=term
@@ -6448,7 +6651,7 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
# is in place: it goes.
for entry in up_to_date:
try:
stale = _destination_path(bios_path, entry["dest"])
stale = _entry_path(bios_path, entry)
except ValueError:
continue
stale = stale.parent / f".{stale.name}.part"
@@ -6460,9 +6663,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
narrowed = bool(
target or args.system or args.core or args.region or args.installed_cores
or len(files) + game_data_cut != len(manifest.get("files", [])) + len(
variants if case_kept else []
)
or len(files) + game_data_cut + len(unplaced)
!= len(manifest.get("files", [])) + len(variants if case_kept else [])
)
data_dirs = select_data_directories(manifest, files, narrowed)
data_states = [
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="abc5b672e1d4b2f507b6d8c3d5c23a120a7dffea6e583559bdcc92c9cbbc7692"
DEFAULT_INSTALL_SHA256="b574c5beb84e9988a53a742f1abf0ad98fcb23dae961facef98c955856380704"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+107 -1
View File
@@ -33,8 +33,111 @@
"total_size": 5342026087,
"pack_files": 4955,
"pack_size": 5342026087,
"total_omitted": 59,
"total_omitted": 67,
"omitted_files": [
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
]
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "bios/BLOOD.GRP",
"name": "BLOOD.GRP",
@@ -1019,6 +1122,9 @@
"com.fms.speccy",
"com.fms.speccy.deluxe"
],
"stella": [
"STELLASA"
],
"tgbdual": [
"TGB Dual"
],
+196 -1
View File
@@ -278,7 +278,7 @@
"total_size": 9590599187,
"pack_files": 14510,
"pack_size": 9590599187,
"total_omitted": 400,
"total_omitted": 415,
"omitted_files": [
{
"dest": "AUDIO.APC",
@@ -500,6 +500,19 @@
],
"category": "game_data"
},
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
],
"held": true
},
{
"dest": "GAMEMAPS.APC",
"name": "GAMEMAPS.APC",
@@ -1072,6 +1085,97 @@
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "System BIOS image",
"name": "System BIOS image",
@@ -1500,6 +1604,94 @@
],
"category": "game_data"
},
{
"dest": "dolphin-emu/User/GBA/gba_bios.bin",
"root": "save",
"core_folder": "dolphin-emu",
"name": "gba_bios.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
],
"systems": [
"nintendo-gba"
],
"held": true
},
{
"dest": "dolphin-emu/User/Load/WiiSD.raw",
"root": "save",
"core_folder": "dolphin-emu",
"name": "WiiSD.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/rootca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "dolphin-emu",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "dolphin-emu",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "doom2bfg.wad",
"name": "doom2bfg.wad",
@@ -5080,6 +5272,9 @@
"sonic3-air": [
"sonic3air"
],
"stella": [
"STELLASA"
],
"theforceengine": [
"tfe"
],
+466 -197
View File
@@ -109,7 +109,8 @@
"darwin": [
"$HOME/Library/Application Support/Azahar/sysdata"
]
}
},
"source": "aes_keys.txt"
},
{
"file": "Citra/sysdata/boot9.bin",
@@ -304,12 +305,335 @@
}
}
],
"total_files": 8223,
"total_size": 7668611857,
"pack_files": 8225,
"pack_size": 7668611857,
"total_omitted": 12,
"total_files": 8207,
"total_size": 7660568116,
"pack_files": 8209,
"pack_size": 7660568116,
"total_omitted": 49,
"omitted_files": [
{
"dest": "Citra/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
],
"systems": [
"nintendo-3ds"
],
"held": true
},
{
"dest": "Citra/Citra/sysdata/boot9.bin",
"root": "save",
"core_folder": "Citra",
"name": "boot9.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Citra",
"name": "seeddb.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra2018/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra2018",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra2018"
],
"systems": [
"nintendo-3ds"
],
"held": true
},
{
"dest": "Citra2018/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra2018",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra2018"
]
},
{
"dest": "FB Alpha 2012 CPS-3/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012 CPS-3",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012_cps3"
]
},
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
]
},
{
"dest": "Ishiiruka/User/Wii/clientca.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/rootca.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/sd.raw",
"root": "save",
"core_folder": "Ishiiruka",
"name": "sd.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "Ishiiruka",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "Ishiiruka",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Panda3DS/Emulator Files/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Panda3DS",
"name": "aes_keys.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"panda3ds"
],
"systems": [
"nintendo-3ds"
],
"held": true
},
{
"dest": "Panda3DS/Emulator Files/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Panda3DS",
"name": "seeddb.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"panda3ds"
]
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Trident/Emulator Files/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Trident",
"name": "aes_keys.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"trident"
],
"systems": [
"nintendo-3ds"
],
"held": true
},
{
"dest": "Trident/Emulator Files/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Trident",
"name": "seeddb.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"trident"
]
},
{
"dest": "doomu.wad",
"name": "doomu.wad",
@@ -332,6 +656,139 @@
],
"category": "game_data"
},
{
"dest": "fsuae/Kickstarts/amiga-ext-130-cdtv.rom",
"root": "save",
"name": "amiga-ext-130-cdtv.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-ext-310-cd32.rom",
"root": "save",
"name": "amiga-ext-310-cd32.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-120.rom",
"root": "save",
"name": "amiga-os-120.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-130.rom",
"root": "save",
"name": "amiga-os-130.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-204.rom",
"root": "save",
"name": "amiga-os-204.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-205.rom",
"root": "save",
"name": "amiga-os-205.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a1200.rom",
"root": "save",
"name": "amiga-os-310-a1200.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a3000.rom",
"root": "save",
"name": "amiga-os-310-a3000.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a4000.rom",
"root": "save",
"name": "amiga-os-310-a4000.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-cd32.rom",
"root": "save",
"name": "amiga-os-310-cd32.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/cd32fmv.rom",
"root": "save",
"name": "cd32fmv.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/rom.key",
"root": "save",
"name": "rom.key",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
],
"held": true
},
{
"dest": "jazz2/Source/Anims.j2a",
"name": "Anims.j2a",
@@ -780,6 +1237,9 @@
"pocketsnes",
"snes9x_next"
],
"stella": [
"STELLASA"
],
"triforce": [
"dolphin-triforce",
"dolphin_triforce"
@@ -1083,17 +1543,6 @@
"cores": null,
"systems": [
"nintendo-3ds"
],
"alternatives": [
{
"sha1": "29556640b022cbfaf427874695838b43a5e8aff4",
"sha256": "0d866e062cf62275fbb291ba44af253e67fdc5bd76c7640b6c4a9dfe742510d9",
"size": 7357,
"repo_path": "bios/Nintendo/3DS/aes_keys.txt",
"cores": [
"trident"
]
}
]
},
{
@@ -18224,116 +18673,6 @@
"fmsx"
]
},
{
"dest": "amiga-os-130.rom",
"sha1": "891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
"sha256": "ee05862d8102a08436ac4056da7d549db31625c7d47b24dfb7b3c9a5c113ca53",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/Kickstart-v1.3-rev34.5-1987-Commodore-A500-A1000-A2000-CDTV.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-204.rom",
"sha1": "c5839f5cb98a7a8947065c3ed2f14f5f42e334a1",
"sha256": "d0b70e8a1772614b897f92c33cb299bed3fc8e3de488fc12f67f97fc2486eb79",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-204.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-205.rom",
"sha1": "02843c4253bbd29aba535b0aa3bd9a85034ecde4",
"sha256": "8d57d6e9d976df42d91ff9d10a5eedaede1a00e3b4b34cad750ca5ff7229f28a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-205-a600.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-120.rom",
"sha1": "11f9e62cf299f72184835b7b2a70a16333fc0d88",
"sha256": "87cddb1f499e32758de20145e73031a84bab299e3f6e5c8487e76d02b2ee9d16",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-os-120.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-a1200.rom",
"sha1": "e21545723fe8374e91342617604f1b3d703094f1",
"sha256": "6d43840d4099a74170ea0f0425b6257c3891ebcaa39c4d1840075a9ab22b5707",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a1200.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-a3000.rom",
"sha1": "f8e210d72b4c4853e0c9b85d223ba20e3d1b36ee",
"sha256": "f2c9307aacb92b203a3135a35185deffad6c66cfa8d79920b235d7cf9a5222c4",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a3000.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-a4000.rom",
"sha1": "5fe04842d04a489720f0f4bb0e46948199406f49",
"sha256": "71236ed62c85394a3ad7d0d8d65405bac7a10083e06ba6b82120dacc7a00fe9a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a4000.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-ext-310-cd32.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
"sha256": "d964826432559994ffe329f643fd4e42dbbff313362d9434945725d7119cae87",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-ext-310-cd32.rom",
"cores": [
"fsuae"
]
},
{
"dest": "cd32fmv.rom",
"sha1": "03ca81c7a7b259cf64bc9582863eca0f6529f435",
"sha256": "680e654880c891a3b03e6107ed30b10903efca16c1dac1b4c8e7835f4e37acb6",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/cd32fmv.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-ext-130-cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"fsuae"
]
},
{
"dest": "fuse/48.rom",
"sha1": "5ea7c2b824672e914525d1d5c419d71b84a426a2",
@@ -66883,7 +67222,6 @@
"repo_path": "bios/Commodore/Amiga/rom.key",
"cores": [
"amiberry",
"fsuae",
"puae",
"puae2021",
"uae4arm"
@@ -68783,47 +69121,6 @@
],
"executable": true
},
{
"dest": "Citra/sysdata/boot9.bin",
"sha1": "5a3d3d6df4743e6b50afe0fc717fa8a12bc888e6",
"sha256": "2f88744feed717856386400a44bba4b9ca62e76a32c715d4f309c399bf28166f",
"size": 65536,
"repo_path": "bios/Nintendo/3DS/boot9.bin",
"cores": [
"citra"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "Citra/sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067",
"sha256": "a06aaf6b50ebc38ebdccc59b913d4a1c0cc7005289957530acaaf78ffc49dddd",
"size": 3145728,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/shared_font.bin",
"cores": [
"citra",
"citra2018",
"citra_canary"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "Citra/sysdata/seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"citra"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "switch/title.keys",
"sha1": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
@@ -94504,19 +94801,6 @@
],
"category": "game_data"
},
{
"dest": "seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"trident"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "id1/pak0.pak",
"sha1": "36b42dc7b6313fd9cabc0be8b9e9864840929735",
@@ -95897,21 +96181,6 @@
"nintendo-3ds"
]
},
{
"dest": "Citra/sysdata/aes_keys.txt",
"sha1": "29556640b022cbfaf427874695838b43a5e8aff4",
"sha256": "0d866e062cf62275fbb291ba44af253e67fdc5bd76c7640b6c4a9dfe742510d9",
"size": 7357,
"repo_path": "bios/Nintendo/3DS/aes_keys.txt",
"cores": [
"citra",
"citra2018",
"citra_canary"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "switch/prod.keys",
"sha1": "c146b651696d25287241f4bab18226e488bc45b3",
+107 -1
View File
@@ -24,8 +24,111 @@
"total_size": 4137534715,
"pack_files": 5296,
"pack_size": 4137534715,
"total_omitted": 164,
"total_omitted": 172,
"omitted_files": [
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
]
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "bios/AUDIOHED.VSI",
"name": "AUDIOHED.VSI",
@@ -2189,6 +2292,9 @@
"soniccd": [
"sonicretrocd"
],
"stella": [
"STELLASA"
],
"tgbdual": [
"TGB Dual"
],
+244 -1
View File
@@ -33,7 +33,7 @@
"total_size": 9410194263,
"pack_files": 9377,
"pack_size": 9410194263,
"total_omitted": 399,
"total_omitted": 418,
"omitted_files": [
{
"dest": "AUDIO.APC",
@@ -112,6 +112,54 @@
],
"category": "game_data"
},
{
"dest": "Citra/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/boot9.bin",
"root": "save",
"core_folder": "Citra",
"name": "boot9.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Citra",
"name": "seeddb.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "DARK.GOB",
"name": "DARK.GOB",
@@ -255,6 +303,19 @@
],
"category": "game_data"
},
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
],
"held": true
},
{
"dest": "GAMEMAPS.APC",
"name": "GAMEMAPS.APC",
@@ -827,6 +888,97 @@
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "System BIOS image",
"name": "System BIOS image",
@@ -1255,6 +1407,94 @@
],
"category": "game_data"
},
{
"dest": "dolphin-emu/User/GBA/gba_bios.bin",
"root": "save",
"core_folder": "dolphin-emu",
"name": "gba_bios.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
],
"systems": [
"nintendo-gba"
],
"held": true
},
{
"dest": "dolphin-emu/User/Load/WiiSD.raw",
"root": "save",
"core_folder": "dolphin-emu",
"name": "WiiSD.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/rootca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "dolphin-emu",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "dolphin-emu",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "doom2bfg.wad",
"name": "doom2bfg.wad",
@@ -4822,6 +5062,9 @@
"sonic3-air": [
"sonic3air"
],
"stella": [
"STELLASA"
],
"theforceengine": [
"tfe"
],
+1086 -60
View File
File diff suppressed because it is too large. Load diff
+183 -1
View File
@@ -283,8 +283,99 @@
"total_size": 4356797363,
"pack_files": 3372,
"pack_size": 4356797363,
"total_omitted": 14,
"total_omitted": 28,
"omitted_files": [
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "caesar3/The_empire.555",
"name": "The_empire.555",
@@ -384,6 +475,94 @@
],
"category": "game_data"
},
{
"dest": "dolphin-emu/User/GBA/gba_bios.bin",
"root": "save",
"core_folder": "dolphin-emu",
"name": "gba_bios.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
],
"systems": [
"nintendo-gba"
],
"held": true
},
{
"dest": "dolphin-emu/User/Load/WiiSD.raw",
"root": "save",
"core_folder": "dolphin-emu",
"name": "WiiSD.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/rootca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "dolphin-emu",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "dolphin-emu",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "doomu.wad",
"name": "doomu.wad",
@@ -809,6 +988,9 @@
"com.fms.speccy",
"com.fms.speccy.deluxe"
],
"stella": [
"STELLASA"
],
"tgbdual": [
"TGB Dual"
],
+1085 -59
View File
File diff suppressed because it is too large. Load diff
+300 -75
View File
@@ -284,11 +284,11 @@
}
}
],
"total_files": 5153,
"total_size": 8012332667,
"pack_files": 5155,
"pack_size": 8012332667,
"total_omitted": 155,
"total_files": 5146,
"total_size": 8009125130,
"pack_files": 5148,
"pack_size": 8009125130,
"total_omitted": 178,
"omitted_files": [
{
"dest": "AR6002G.ROM",
@@ -332,6 +332,93 @@
],
"category": "game_data"
},
{
"dest": "Azahar/Azahar/nand/private/movable.sed",
"root": "save",
"core_folder": "Azahar",
"name": "movable.sed",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/Azahar/nand/rw/sys/LocalFriendCodeSeed_B",
"root": "save",
"core_folder": "Azahar",
"name": "LocalFriendCodeSeed_B",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/Azahar/nand/rw/sys/SecureInfo_A",
"root": "save",
"core_folder": "Azahar",
"name": "SecureInfo_A",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/Azahar/sysdata/keys.txt",
"root": "save",
"core_folder": "Azahar",
"name": "keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
],
"held": true
},
{
"dest": "Azahar/Azahar/sysdata/otp.bin",
"root": "save",
"core_folder": "Azahar",
"name": "otp.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
],
"held": true
},
{
"dest": "Azahar/Azahar/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Azahar",
"name": "seeddb.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
],
"held": true
},
{
"dest": "Azahar/Azahar/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Azahar",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"azahar"
]
},
{
"dest": "BIOSDSI7.ROM",
"name": "BIOSDSI7.ROM",
@@ -491,6 +578,32 @@
"cxbx-reloaded"
]
},
{
"dest": "FB Alpha 2012 CPS-3/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012 CPS-3",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012_cps3"
],
"held": true
},
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
],
"held": true
},
{
"dest": "GANIM.BS6",
"name": "GANIM.BS6",
@@ -722,6 +835,97 @@
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "TD.GRP",
"name": "TD.GRP",
@@ -1073,6 +1277,94 @@
],
"category": "game_data"
},
{
"dest": "dolphin-emu/User/GBA/gba_bios.bin",
"root": "save",
"core_folder": "dolphin-emu",
"name": "gba_bios.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
],
"systems": [
"nintendo-gba"
],
"held": true
},
{
"dest": "dolphin-emu/User/Load/WiiSD.raw",
"root": "save",
"core_folder": "dolphin-emu",
"name": "WiiSD.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/rootca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "dolphin-emu",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "dolphin-emu",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "doom2bfg.wad",
"name": "doom2bfg.wad",
@@ -2416,6 +2708,9 @@
"sonic3-air": [
"sonic3air"
],
"stella": [
"STELLASA"
],
"tgbdual": [
"TGB Dual"
],
@@ -11358,16 +11653,6 @@
"xegs"
]
},
{
"dest": "Azahar/sysdata/keys.txt",
"sha1": "5288772be438b86978a801e6dcb14228ad3a20af",
"sha256": "ea8b3d7c752d85f2326b9bfa01615414ab34ba052f87481b0ff4003fd887e50a",
"size": 7424,
"repo_path": "bios/Other/azaharplus/sysdata/keys.txt",
"cores": [
"azahar"
]
},
{
"dest": "sysdata/keys.txt",
"sha1": "5288772be438b86978a801e6dcb14228ad3a20af",
@@ -40188,66 +40473,6 @@
"sega-saturn"
]
},
{
"dest": "Azahar/sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067",
"sha256": "a06aaf6b50ebc38ebdccc59b913d4a1c0cc7005289957530acaaf78ffc49dddd",
"size": 3145728,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/shared_font.bin",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/sysdata/seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/sysdata/otp.bin",
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
"size": 256,
"repo_path": "bios/Nintendo/3DS/otp.bin",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/nand/private/movable.sed",
"sha1": "6620b0a0c79dfe80be682fbd5c31c152ab570899",
"sha256": "5c024772c93d5b491398873b05be69a178d8a5208152d60ea2c006dfdbb2ce00",
"size": 320,
"repo_path": "bios/Nintendo/3DS/Citra/nand/private/movable.sed",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/nand/rw/sys/SecureInfo_A",
"sha1": "ff52883a0f61afcbbb3f8e59b9327ac52050e9a7",
"sha256": "5eda7e8951c48fc0913445fc844fa1caafcf226f82c0d06341e7e5fa39ae033e",
"size": 273,
"repo_path": "bios/Nintendo/3DS/Citra/nand/rw/sys/SecureInfo_A",
"cores": [
"azahar"
]
},
{
"dest": "Azahar/nand/rw/sys/LocalFriendCodeSeed_B",
"sha1": "3011ae55c3dc70203fd62aad98bf485e55d018ff",
"sha256": "086e900d21c6ae3f892db284359315cb5ec44bb3939f2fb3187d212f5be3ac61",
"size": 272,
"repo_path": "bios/Nintendo/3DS/Citra/nand/rw/sys/LocalFriendCodeSeed_B",
"cores": [
"azahar"
]
},
{
"dest": "sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067",
+453 -232
View File
@@ -91,7 +91,7 @@
"$HOME/Library/Application Support/Azahar/sysdata"
]
},
"source": "bios/Citra/sysdata/aes_keys.txt"
"source": "bios/aes_keys.txt"
},
{
"file": "Citra/sysdata/boot9.bin",
@@ -106,8 +106,7 @@
"darwin": [
"$HOME/Library/Application Support/Azahar/sysdata"
]
},
"source": "bios/Citra/sysdata/boot9.bin"
}
},
{
"pattern": "scph*.bin",
@@ -293,12 +292,319 @@
"source": "bios/dc/dc_nvmem.bin"
}
],
"total_files": 8591,
"total_size": 8557487724,
"pack_files": 8593,
"pack_size": 8557487724,
"total_omitted": 33,
"total_files": 8575,
"total_size": 8549443983,
"pack_files": 8577,
"pack_size": 8549443983,
"total_omitted": 70,
"omitted_files": [
{
"dest": "Citra/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/boot9.bin",
"root": "save",
"core_folder": "Citra",
"name": "boot9.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Citra",
"name": "seeddb.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra2018/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra2018",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra2018"
]
},
{
"dest": "Citra2018/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra2018",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra2018"
]
},
{
"dest": "FB Alpha 2012 CPS-3/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012 CPS-3",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012_cps3"
]
},
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
]
},
{
"dest": "Ishiiruka/User/Wii/clientca.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/rootca.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/sd.raw",
"root": "save",
"core_folder": "Ishiiruka",
"name": "sd.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "Ishiiruka",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "Ishiiruka",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Panda3DS/Emulator Files/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Panda3DS",
"name": "aes_keys.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"panda3ds"
]
},
{
"dest": "Panda3DS/Emulator Files/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Panda3DS",
"name": "seeddb.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"panda3ds"
]
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Trident/Emulator Files/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Trident",
"name": "aes_keys.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"trident"
]
},
{
"dest": "Trident/Emulator Files/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Trident",
"name": "seeddb.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"trident"
]
},
{
"dest": "bios/doom2bfg.wad",
"name": "doom2bfg.wad",
@@ -659,6 +965,142 @@
"gzdoom"
],
"category": "game_data"
},
{
"dest": "fsuae/Kickstarts/amiga-ext-130-cdtv.rom",
"root": "save",
"name": "amiga-ext-130-cdtv.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-ext-310-cd32.rom",
"root": "save",
"name": "amiga-ext-310-cd32.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-120.rom",
"root": "save",
"name": "amiga-os-120.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-130.rom",
"root": "save",
"name": "amiga-os-130.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-204.rom",
"root": "save",
"name": "amiga-os-204.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-205.rom",
"root": "save",
"name": "amiga-os-205.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a1200.rom",
"root": "save",
"name": "amiga-os-310-a1200.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a3000.rom",
"root": "save",
"name": "amiga-os-310-a3000.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a4000.rom",
"root": "save",
"name": "amiga-os-310-a4000.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-cd32.rom",
"root": "save",
"name": "amiga-os-310-cd32.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/cd32fmv.rom",
"root": "save",
"name": "cd32fmv.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/rom.key",
"root": "save",
"name": "rom.key",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
],
"held": true
}
],
"core_names": {
@@ -1127,6 +1569,9 @@
"com.fms.speccy",
"com.fms.speccy.deluxe"
],
"stella": [
"STELLASA"
],
"superzsnes": [
"SUPERZSNES"
],
@@ -37084,17 +37529,6 @@
"cores": null,
"systems": [
"nintendo-3ds"
],
"alternatives": [
{
"sha1": "29556640b022cbfaf427874695838b43a5e8aff4",
"sha256": "0d866e062cf62275fbb291ba44af253e67fdc5bd76c7640b6c4a9dfe742510d9",
"size": 7357,
"repo_path": "bios/Nintendo/3DS/aes_keys.txt",
"cores": [
"trident"
]
}
]
},
{
@@ -56605,149 +57039,6 @@
"sinclair-zx-spectrum"
]
},
{
"dest": "bios/amiga-os-130.rom",
"sha1": "891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
"sha256": "ee05862d8102a08436ac4056da7d549db31625c7d47b24dfb7b3c9a5c113ca53",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/Kickstart-v1.3-rev34.5-1987-Commodore-A500-A1000-A2000-CDTV.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-204.rom",
"sha1": "c5839f5cb98a7a8947065c3ed2f14f5f42e334a1",
"sha256": "d0b70e8a1772614b897f92c33cb299bed3fc8e3de488fc12f67f97fc2486eb79",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-204.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-205.rom",
"sha1": "02843c4253bbd29aba535b0aa3bd9a85034ecde4",
"sha256": "8d57d6e9d976df42d91ff9d10a5eedaede1a00e3b4b34cad750ca5ff7229f28a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-205-a600.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-120.rom",
"sha1": "11f9e62cf299f72184835b7b2a70a16333fc0d88",
"sha256": "87cddb1f499e32758de20145e73031a84bab299e3f6e5c8487e76d02b2ee9d16",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-os-120.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-310-a1200.rom",
"sha1": "e21545723fe8374e91342617604f1b3d703094f1",
"sha256": "6d43840d4099a74170ea0f0425b6257c3891ebcaa39c4d1840075a9ab22b5707",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a1200.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-310-a3000.rom",
"sha1": "f8e210d72b4c4853e0c9b85d223ba20e3d1b36ee",
"sha256": "f2c9307aacb92b203a3135a35185deffad6c66cfa8d79920b235d7cf9a5222c4",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a3000.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-310-a4000.rom",
"sha1": "5fe04842d04a489720f0f4bb0e46948199406f49",
"sha256": "71236ed62c85394a3ad7d0d8d65405bac7a10083e06ba6b82120dacc7a00fe9a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a4000.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-os-310-cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-ext-310-cd32.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
"sha256": "d964826432559994ffe329f643fd4e42dbbff313362d9434945725d7119cae87",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-ext-310-cd32.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/cd32fmv.rom",
"sha1": "03ca81c7a7b259cf64bc9582863eca0f6529f435",
"sha256": "680e654880c891a3b03e6107ed30b10903efca16c1dac1b4c8e7835f4e37acb6",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/cd32fmv.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/amiga-ext-130-cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"fsuae"
],
"systems": [
"commodore-amiga"
]
},
{
"dest": "bios/fuse/48.rom",
"sha1": "5ea7c2b824672e914525d1d5c419d71b84a426a2",
@@ -104668,7 +104959,6 @@
"repo_path": "bios/Commodore/Amiga/rom.key",
"cores": [
"amiberry",
"fsuae",
"puae",
"puae2021",
"uae4arm"
@@ -105244,47 +105534,6 @@
],
"executable": true
},
{
"dest": "bios/Citra/sysdata/boot9.bin",
"sha1": "5a3d3d6df4743e6b50afe0fc717fa8a12bc888e6",
"sha256": "2f88744feed717856386400a44bba4b9ca62e76a32c715d4f309c399bf28166f",
"size": 65536,
"repo_path": "bios/Nintendo/3DS/boot9.bin",
"cores": [
"citra"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "bios/Citra/sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067",
"sha256": "a06aaf6b50ebc38ebdccc59b913d4a1c0cc7005289957530acaaf78ffc49dddd",
"size": 3145728,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/shared_font.bin",
"cores": [
"citra",
"citra2018",
"citra_canary"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "bios/Citra/sysdata/seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"citra"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "bios/cromwell_1024.bin",
"sha1": "4e1c2c2ee308ca4591542b3ca48653f65fae6e0f",
@@ -116047,19 +116296,6 @@
],
"category": "game_data"
},
{
"dest": "bios/seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"trident"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "bios/id1/pak0.pak",
"sha1": "36b42dc7b6313fd9cabc0be8b9e9864840929735",
@@ -117603,21 +117839,6 @@
"sony-playstation-2"
]
},
{
"dest": "bios/Citra/sysdata/aes_keys.txt",
"sha1": "29556640b022cbfaf427874695838b43a5e8aff4",
"sha256": "0d866e062cf62275fbb291ba44af253e67fdc5bd76c7640b6c4a9dfe742510d9",
"size": 7357,
"repo_path": "bios/Nintendo/3DS/aes_keys.txt",
"cores": [
"citra",
"citra2018",
"citra_canary"
],
"systems": [
"nintendo-3ds"
]
},
{
"dest": "bios/GBA/gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
+1236 -283
View File
File diff suppressed because it is too large. Load diff
+536 -180
View File
@@ -21,12 +21,407 @@
}
],
"standalone_copies": [],
"total_files": 7152,
"total_size": 8224212452,
"pack_files": 7154,
"pack_size": 8224212452,
"total_omitted": 42,
"total_files": 7135,
"total_size": 8216161354,
"pack_files": 7137,
"pack_size": 8216161354,
"total_omitted": 86,
"omitted_files": [
{
"dest": "Citra/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/boot9.bin",
"root": "save",
"core_folder": "Citra",
"name": "boot9.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Citra",
"name": "seeddb.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra"
]
},
{
"dest": "Citra2018/Citra/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Citra2018",
"name": "aes_keys.txt",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra2018"
]
},
{
"dest": "Citra2018/Citra/sysdata/shared_font.bin",
"root": "save",
"core_folder": "Citra2018",
"name": "shared_font.bin",
"system": "nintendo-3ds",
"required": false,
"reason": "no_save_directory",
"cores": [
"citra2018"
]
},
{
"dest": "FB Alpha 2012 CPS-3/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012 CPS-3",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012_cps3"
]
},
{
"dest": "FB Alpha 2012/hiscore.dat",
"root": "save",
"core_folder": "FB Alpha 2012",
"name": "hiscore.dat",
"system": "arcade",
"required": false,
"reason": "no_save_directory",
"cores": [
"fbalpha2012"
]
},
{
"dest": "Ishiiruka/User/Wii/clientca.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/rootca.pem",
"root": "save",
"core_folder": "Ishiiruka",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/sd.raw",
"root": "save",
"core_folder": "Ishiiruka",
"name": "sd.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "Ishiiruka",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Ishiiruka/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "Ishiiruka",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"ishiiruka"
]
},
{
"dest": "Panda3DS/Emulator Files/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Panda3DS",
"name": "aes_keys.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"panda3ds"
]
},
{
"dest": "Panda3DS/Emulator Files/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Panda3DS",
"name": "seeddb.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"panda3ds"
]
},
{
"dest": "Stella/stella/KVB1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVB3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVB3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS1.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS1.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS2.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS2.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVS3.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVS3.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Stella/stella/KVSHARED.WAV",
"root": "save",
"core_folder": "Stella",
"name": "KVSHARED.WAV",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"stella"
],
"category": "game_data"
},
{
"dest": "Trident/Emulator Files/sysdata/aes_keys.txt",
"root": "save",
"core_folder": "Trident",
"name": "aes_keys.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"trident"
]
},
{
"dest": "Trident/Emulator Files/sysdata/seeddb.bin",
"root": "save",
"core_folder": "Trident",
"name": "seeddb.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"trident"
]
},
{
"dest": "dolphin-emu/User/GBA/gba_bios.bin",
"root": "save",
"core_folder": "dolphin-emu",
"name": "gba_bios.bin",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
],
"systems": [
"nintendo-gba"
],
"held": true
},
{
"dest": "dolphin-emu/User/Load/WiiSD.raw",
"root": "save",
"core_folder": "dolphin-emu",
"name": "WiiSD.raw",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/clientcakey.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "clientcakey.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/rootca.pem",
"root": "save",
"core_folder": "dolphin-emu",
"name": "rootca.pem",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/shared2/sys/SYSCONF",
"root": "save",
"core_folder": "dolphin-emu",
"name": "SYSCONF",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "dolphin-emu/User/Wii/title/00000001/00000002/data/setting.txt",
"root": "save",
"core_folder": "dolphin-emu",
"name": "setting.txt",
"system": "",
"required": false,
"reason": "no_save_directory",
"cores": [
"dolphin"
]
},
{
"dest": "doom2bfg.wad",
"name": "doom2bfg.wad",
@@ -159,6 +554,139 @@
],
"category": "game_data"
},
{
"dest": "fsuae/Kickstarts/amiga-ext-130-cdtv.rom",
"root": "save",
"name": "amiga-ext-130-cdtv.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-ext-310-cd32.rom",
"root": "save",
"name": "amiga-ext-310-cd32.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-120.rom",
"root": "save",
"name": "amiga-os-120.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-130.rom",
"root": "save",
"name": "amiga-os-130.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-204.rom",
"root": "save",
"name": "amiga-os-204.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-205.rom",
"root": "save",
"name": "amiga-os-205.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a1200.rom",
"root": "save",
"name": "amiga-os-310-a1200.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a3000.rom",
"root": "save",
"name": "amiga-os-310-a3000.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-a4000.rom",
"root": "save",
"name": "amiga-os-310-a4000.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/amiga-os-310-cd32.rom",
"root": "save",
"name": "amiga-os-310-cd32.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/cd32fmv.rom",
"root": "save",
"name": "cd32fmv.rom",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
]
},
{
"dest": "fsuae/Kickstarts/rom.key",
"root": "save",
"name": "rom.key",
"system": "amiga",
"required": false,
"reason": "no_save_directory",
"cores": [
"fsuae"
],
"held": true
},
{
"dest": "id24res.wad",
"name": "id24res.wad",
@@ -797,6 +1325,9 @@
"pocketsnes",
"snes9x_next"
],
"stella": [
"STELLASA"
],
"tgbdual": [
"TGB Dual"
],
@@ -5777,116 +6308,6 @@
"fmsx"
]
},
{
"dest": "amiga-os-130.rom",
"sha1": "891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
"sha256": "ee05862d8102a08436ac4056da7d549db31625c7d47b24dfb7b3c9a5c113ca53",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/Kickstart-v1.3-rev34.5-1987-Commodore-A500-A1000-A2000-CDTV.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-204.rom",
"sha1": "c5839f5cb98a7a8947065c3ed2f14f5f42e334a1",
"sha256": "d0b70e8a1772614b897f92c33cb299bed3fc8e3de488fc12f67f97fc2486eb79",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-204.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-205.rom",
"sha1": "02843c4253bbd29aba535b0aa3bd9a85034ecde4",
"sha256": "8d57d6e9d976df42d91ff9d10a5eedaede1a00e3b4b34cad750ca5ff7229f28a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-205-a600.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-120.rom",
"sha1": "11f9e62cf299f72184835b7b2a70a16333fc0d88",
"sha256": "87cddb1f499e32758de20145e73031a84bab299e3f6e5c8487e76d02b2ee9d16",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-os-120.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-a1200.rom",
"sha1": "e21545723fe8374e91342617604f1b3d703094f1",
"sha256": "6d43840d4099a74170ea0f0425b6257c3891ebcaa39c4d1840075a9ab22b5707",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a1200.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-a3000.rom",
"sha1": "f8e210d72b4c4853e0c9b85d223ba20e3d1b36ee",
"sha256": "f2c9307aacb92b203a3135a35185deffad6c66cfa8d79920b235d7cf9a5222c4",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a3000.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-a4000.rom",
"sha1": "5fe04842d04a489720f0f4bb0e46948199406f49",
"sha256": "71236ed62c85394a3ad7d0d8d65405bac7a10083e06ba6b82120dacc7a00fe9a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a4000.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-os-310-cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-ext-310-cd32.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
"sha256": "d964826432559994ffe329f643fd4e42dbbff313362d9434945725d7119cae87",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-ext-310-cd32.rom",
"cores": [
"fsuae"
]
},
{
"dest": "cd32fmv.rom",
"sha1": "03ca81c7a7b259cf64bc9582863eca0f6529f435",
"sha256": "680e654880c891a3b03e6107ed30b10903efca16c1dac1b4c8e7835f4e37acb6",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/cd32fmv.rom",
"cores": [
"fsuae"
]
},
{
"dest": "amiga-ext-130-cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"fsuae"
]
},
{
"dest": "fuse/48.rom",
"sha1": "5ea7c2b824672e914525d1d5c419d71b84a426a2",
@@ -52121,7 +52542,6 @@
"repo_path": "bios/Commodore/Amiga/rom.key",
"cores": [
"amiberry",
"fsuae",
"puae",
"puae2021",
"uae4arm"
@@ -53871,50 +54291,6 @@
],
"executable": true
},
{
"dest": "Citra/sysdata/aes_keys.txt",
"sha1": "29556640b022cbfaf427874695838b43a5e8aff4",
"sha256": "0d866e062cf62275fbb291ba44af253e67fdc5bd76c7640b6c4a9dfe742510d9",
"size": 7357,
"repo_path": "bios/Nintendo/3DS/aes_keys.txt",
"cores": [
"citra",
"citra2018",
"citra_canary"
]
},
{
"dest": "Citra/sysdata/boot9.bin",
"sha1": "5a3d3d6df4743e6b50afe0fc717fa8a12bc888e6",
"sha256": "2f88744feed717856386400a44bba4b9ca62e76a32c715d4f309c399bf28166f",
"size": 65536,
"repo_path": "bios/Nintendo/3DS/boot9.bin",
"cores": [
"citra"
]
},
{
"dest": "Citra/sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067",
"sha256": "a06aaf6b50ebc38ebdccc59b913d4a1c0cc7005289957530acaaf78ffc49dddd",
"size": 3145728,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/shared_font.bin",
"cores": [
"citra",
"citra2018",
"citra_canary"
]
},
{
"dest": "Citra/sysdata/seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"citra"
]
},
{
"dest": "dinothawr/dinothawr.game",
"sha1": "566fd769c8026151e27f3956849b7840532797fd",
@@ -77574,26 +77950,6 @@
],
"category": "game_data"
},
{
"dest": "aes_keys.txt",
"sha1": "29556640b022cbfaf427874695838b43a5e8aff4",
"sha256": "0d866e062cf62275fbb291ba44af253e67fdc5bd76c7640b6c4a9dfe742510d9",
"size": 7357,
"repo_path": "bios/Nintendo/3DS/aes_keys.txt",
"cores": [
"trident"
]
},
{
"dest": "seeddb.bin",
"sha1": "9e2c006997761082be87cf316e57c4da5cddbc62",
"sha256": "ff485c60090185aff05354ece01f86b61f6020332bed0f9090c58d4678e5a180",
"size": 53264,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/seeddb.bin",
"cores": [
"trident"
]
},
{
"dest": "id1/pak0.pak",
"sha1": "36b42dc7b6313fd9cabc0be8b9e9864840929735",
+934 -752
View File
File diff suppressed because it is too large. Load diff
+35
View File
@@ -190,6 +190,15 @@ platforms:
hash_type: sha1
verification_mode: existence
base_destination: system
# The directory RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY returns
# (runloop.c:2699-2700, libretro/RetroArch a81e450c): savefile_directory,
# saves/ beside system/ by default (platform_unix.c:3364-3365,
# platform_win32.c:1343, platform_darwin.m:456), with the core's
# library_name appended since sort_savefiles_enable defaults to true
# (config.def.h:1099, runloop.c:11285-11293).
save_directory:
folder: saves
by_core: true
case_insensitive_fs: true
schedule: weekly
cores: all_libretro
@@ -484,6 +493,12 @@ platforms:
source_url: https://raw.githubusercontent.com/batocera-linux/batocera.linux/master/package/batocera/core/batocera-scripts/scripts/batocera-systems
source_format: python_dict
hash_type: md5
# The libretro launcher hands each core /userdata/saves/<system> with
# sort_savefiles_enable off (python-src/batocera-launch-libretro/
# batocera_launch_libretro/emulator.py:754-757 at 8bbc0d6e, saves_dir in
# python-src/batocera-launch/batocera_launch/emulator.py:251-252 at
# d30617e5): one folder per launched system, no single place.
save_directory: null
schedule: weekly
target_scraper: batocera_targets
target_source: https://github.com/batocera-linux/batocera.linux
@@ -537,6 +552,11 @@ platforms:
hash_type: md5
# Windows: names differing only by case land on one file.
case_insensitive_fs: true
# emulatorLauncher hands a libretro core saves\<system> (saves\<system>\
# LR-amiberry for amiberry, saves\ for mame), sorted by core for dolphin
# only (emulatorLauncher/Generators/LibRetro.Generator.cs:1128-1131 and
# 1305-1315 at 7f8dc28a): one folder per launched system, no single place.
save_directory: null
schedule: weekly
target_scraper: null
target_source: null
@@ -617,6 +637,13 @@ platforms:
scraper: libretro
inherits_from: retroarch
cores: all_libretro
# Lakka's retroarch.cfg sets savefile_directory to /storage/savefiles,
# beside /storage/system, and sorts saves by core
# (packages/lakka/retroarch_base/retroarch/config/retroarch.cfg:3164-3165
# and 3206-3207, libretro/Lakka-LibreELEC cc3bf85d).
save_directory:
folder: savefiles
by_core: true
# Lakka is a Linux distribution: two names that differ by case are two
# files there, not the one RetroArch's Windows and macOS builds see.
case_insensitive_fs: false
@@ -863,6 +890,14 @@ platforms:
# scriptmodules are what say which packages it ships.
# RetroPie runs on Linux, where case tells two files apart.
case_insensitive_fs: false
# Not RetroArch's: RetroPie sets no savefile_directory and turns
# sort_savefiles_enable off (scriptmodules/emulators/retroarch.sh:289-290,
# RetroPie-Setup 46128cd2), and its RetroArch (retropie-v1.19.0) has no
# default save directory on Linux (frontend/drivers/platform_unix.c sets
# DEFAULT_DIR_SRAM only at 1624-1626, for Android). The save directory is
# then the folder of the loaded content (runloop.c runloop_path_set_redirect),
# one per game: a file read below it has no single place.
save_directory: null
schedule: null
target_scraper: retropie_targets
target_source: https://retropie.org.uk/stats/pkgflags/
+10 -1
View File
@@ -335,6 +335,15 @@
"type": "integer",
"minimum": 0
},
"library_name": {
"description": "The library_name the libretro core reports in retro_get_system_info. A frontend sorting saves by core (RetroArch's sort_savefiles_enable) hands the core <save directory>/<library_name>, so a profile with a file the libretro build reads below the save directory (load_from: save_dir) declares it. Empty when the core reports an empty name, which RetroArch appends nothing for.",
"type": "string",
"pattern": "^[^/\\\\]*$"
},
"save_folder_once": {
"description": "true when the core adds its own folder to the save directory only where the directory it is handed is not already named so (Cemu's in_named_dir), so it reads the same place whether the frontend sorts saves by core or not. Every load_from: save_dir path then starts with that folder, the library_name.",
"const": true
},
"logo": {
"type": "string"
},
@@ -536,7 +545,7 @@
]
},
"load_from": {
"description": "Where the emulator reads the file when not from the system directory, keyed by build when the libretro and standalone builds read it from different places.",
"description": "Where the emulator reads the file when not from the system directory, keyed by build when the libretro and standalone builds read it from different places. save_dir: path is relative to the directory the frontend returns for RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, and packs and the installer place the libretro build's file there.",
"oneOf": [
{
"type": "string"
+41 -1
View File
@@ -175,7 +175,9 @@
"external",
"user_provided",
"no_platform_slug",
"below_platform_folder"
"below_platform_folder",
"no_save_directory",
"no_core_folder"
]
},
"category": {
@@ -316,6 +318,16 @@
}
}
}
},
"root": {
"description": "save: dest is relative to the save directory the frontend hands the core (RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY), not to the BIOS directory. Absent for a file of the BIOS tree.",
"const": "save"
},
"core_folder": {
"description": "The core's library_name, the folder a frontend sorting saves by core (RetroArch's sort_savefiles_enable) inserts between its save directory and dest. Only with root: save. Absent on a save entry when the core reads the same place sorted or not.",
"type": "string",
"minLength": 1,
"pattern": "^[^/\\\\]+$"
}
}
}
@@ -407,6 +419,24 @@
}
}
}
},
"save_directory": {
"description": "Where the platform keeps the save directory its frontend hands a core, present when an entry carries root: save. folder is the pack folder holding those files and the save directory's default place beside the BIOS directory, and by_core says the frontend appends each core's library_name (core_folder).",
"type": "object",
"additionalProperties": false,
"required": [
"folder",
"by_core"
],
"properties": {
"folder": {
"type": "string",
"pattern": "^[^/\\\\.][^/\\\\]*$"
},
"by_core": {
"type": "boolean"
}
}
}
},
"$defs": {
@@ -861,6 +891,16 @@
}
}
}
},
"root": {
"description": "save: dest is relative to the save directory the frontend hands the core (RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY), not to the BIOS directory. Absent for a file of the BIOS tree.",
"const": "save"
},
"core_folder": {
"description": "The core's library_name, the folder a frontend sorting saves by core (RetroArch's sort_savefiles_enable) inserts between its save directory and dest. Only with root: save. Absent on a save entry when the core reads the same place sorted or not.",
"type": "string",
"minLength": 1,
"pattern": "^[^/\\\\]+$"
}
},
"oneOf": [
+10 -1
View File
@@ -51,6 +51,7 @@ from validation import (
declaration_settles,
installed_with_emulator,
outside_gap_scope,
read_from_save_dir,
unread_on,
)
@@ -657,6 +658,7 @@ def slot_key(f: dict, standalone: bool = False) -> tuple:
f.get("system"),
f.get("variant_group"),
f.get("mode", "both"),
read_from_save_dir(f, standalone),
)
@@ -725,8 +727,11 @@ def _cross_reference_profile(
# takes it: Batocera's standalone Cemu reads keys.txt at
# standalone_path, and the libretro Cemu/keys.txt reported here
# settled the declaration on a slot the platform never fills.
save_root = read_from_save_dir(f, is_standalone)
if is_standalone:
effective_path = f.get("standalone_path") or f.get("path") or fname
elif save_root:
effective_path = f.get("path") or fname
else:
effective_path = system_dir_path(f, f.get("path") or fname)
slot = slot_key(f, is_standalone)
@@ -757,7 +762,9 @@ def _cross_reference_profile(
# Check platform declaration: by name, by an alias the core answers
# to (RetroArch lists amiberry's cd32.rom as kick40060.CD32), or by
# archive.
in_platform = declaration_settles(
# A platform declares files of its BIOS tree: none answers for one
# the core reads below the save directory.
in_platform = not save_root and declaration_settles(
f, fname, platform_names, effective_path, index.get("folded_slots", frozenset())
)
@@ -836,6 +843,8 @@ def _cross_reference_profile(
# What tells this file from a homonym another emulator wants.
"identity": _first_hash(f),
}
if save_root:
entry["root"] = "save"
gaps.append(entry)
for ag in sorted(archive_gaps.values(), key=lambda e: e["name"]):
+76 -27
View File
@@ -12,7 +12,7 @@ and 3-tier storage (embedded/external/user_provided).
from __future__ import annotations
from collections.abc import Callable, Iterator
from collections.abc import Callable, Iterable, Iterator
from artifacts import _build_timestamp, set_plain_mode
import argparse
@@ -1074,6 +1074,7 @@ def generate_pack(
)
core_count = 0
save_count = 0
extra_resolver = ExtraResolver(
db, bios_dir, zip_contents, data_registry, validation_index,
validation_owners, offline, profiles=emu_profiles,
@@ -1089,15 +1090,13 @@ def generate_pack(
# Core extras: _collect_emulator_extras already adjusted
# destinations for slug-based platforms. Apply the effective
# prefix (base_dest, or inferred from YAML when base_dest is
# empty, as RetroDECK infers "bios").
extras_pfx = _detect_extras_prefix(config, base_dest)
if extras_pfx:
if not dest.startswith(f"{extras_pfx}/"):
full_dest = f"{extras_pfx}/{dest}"
else:
full_dest = dest
else:
full_dest = dest
# empty, as RetroDECK infers "bios"). A file read below the save
# directory goes to the pack's save folder instead.
full_dest = extra_full_destination(
fe, _detect_extras_prefix(config, base_dest)
)
if not full_dest:
continue
# Taken, a case twin on a case-insensitive filesystem, or a
# file/directory conflict (SGB1.sfc vs SGB1.sfc/): verify replays
# the same rule (packextras.hidden_extras).
@@ -1158,6 +1157,8 @@ def generate_pack(
if case_insensitive:
seen_lower.add(full_dest.lower())
core_count += 1
if fe.get("root") == "save":
save_count += 1
total_files += 1
total_files += _pack_data_directories(
@@ -1190,6 +1191,7 @@ def generate_pack(
_pack_registry.get(platform_name, {}).get("install", {}) or {}
).get("standalone_copies", []),
launch_folders=len(config.get("core_system_dirs") or {}),
save_folder=(save_layout(config).folder if save_count else ""),
)
_write_generated_member(zf, "README.txt", readme_text)
@@ -3856,13 +3858,10 @@ def _manifest_core_entries(
continue
if region_drops and dest in region_drops:
continue
if extras_pfx:
if not dest.startswith(f"{extras_pfx}/"):
full_dest = f"{extras_pfx}/{dest}"
else:
full_dest = dest
else:
full_dest = dest
full_dest = extra_full_destination(fe, extras_pfx)
if not full_dest:
continue
save_root = fe.get("root") == "save"
if (
case_insensitive
@@ -3874,9 +3873,14 @@ def _manifest_core_entries(
# entry kept, or a pack built without the first core loses the
# file the installer would still have to place. Collisions are
# rare, so the entry is looked up only when one happens.
folded = (
save_manifest_dest(fe) if save_root
else _manifest_dest_of(full_dest, base_dest)
).lower()
held = next(
(e for e in manifest_files
if e["dest"].lower() == _manifest_dest_of(full_dest, base_dest).lower()),
if e["dest"].lower() == folded
and (e.get("root") == "save") == save_root),
None,
)
if held is not None and held.get("cores") is not None:
@@ -3917,13 +3921,19 @@ def _manifest_core_entries(
# Manifest dests are relative to base_destination. Keep the inferred
# extras prefix when it is an internal layout dir (RetroDECK bios/).
manifest_dest = _manifest_dest_of(full_dest, base_dest)
# A file read below the save directory is written relative to the
# directory the core is handed, and says so (save_entry_fields).
manifest_dest = (
save_manifest_dest(fe) if save_root else _manifest_dest_of(full_dest, base_dest)
)
entry, file_size = _manifest_entry(
local_path, manifest_dest, [source_emu] if source_emu else [],
db, repo_root,
selection.fields(dest, fe.get("name", "")),
)
if entry is not None and save_root:
entry.update(save_entry_fields(fe))
if entry is None:
if not _claim_platform_omission(
omitted_by_destination.get(full_dest), source_emu
@@ -3965,6 +3975,24 @@ def _manifest_core_entries(
return total_size
def _check_save_destinations(files: list[dict], omitted: Iterable[dict]) -> None:
"""Refuse a manifest naming one destination below both directories.
The installer keys every entry by its ``dest``: a file read below the
save directory and one of the BIOS tree written alike would be taken
for one file.
"""
roots: dict[str, set[bool]] = {}
for entry in [*files, *omitted]:
roots.setdefault(entry["dest"], set()).add(entry.get("root") == "save")
clashes = sorted(dest for dest, kinds in roots.items() if len(kinds) > 1)
if clashes:
raise ValueError(
"destinations below both the system and save directories: "
+ ", ".join(clashes[:10])
)
def _manifest_dest_of(full_dest: str, base_dest: str) -> str:
"""A pack destination as the manifest writes it, below base_destination."""
if base_dest and full_dest.startswith(f"{base_dest}/"):
@@ -4061,10 +4089,9 @@ def _record_absent(
dest = sanitize_pack_path(fe.get("destination", fe.get("name", "")))
if not dest or (region_drops and dest in region_drops):
continue
full_dest = (
dest if not extras_pfx or dest.startswith(f"{extras_pfx}/")
else f"{extras_pfx}/{dest}"
)
full_dest = extra_full_destination(fe, extras_pfx)
if not full_dest:
continue
if full_dest in seen_destinations or full_dest in omitted_by_destination:
continue
source_emu = fe.get("source_profile") or fe.get("source_emulator", "")
@@ -4081,9 +4108,16 @@ def _record_unplaceable(unplaceable: list[dict], record_omission: Callable[...,
Said in the manifest, where RomM's DOSBox Pure ROMs were simply absent.
"""
for u in unplaceable:
where = u.get("path") or u.get("name", "")
record_omission(
u.get("path") or u.get("name", ""), u, u.get("system") or "",
u.get("unplaceable", "no_platform_slug"), [u.get("emulator", "")],
# Keyed apart from the BIOS tree's paths, which a file read below
# the save directory can repeat.
f"save:{save_manifest_dest(u)}" if u.get("root") == "save" else where,
u, u.get("system") or "",
# The profile key, the name a target's core list and the
# installer's narrowing match: the display name matched none.
u.get("unplaceable", "no_platform_slug"),
[u.get("profile") or u.get("emulator", "")],
)
@@ -4222,8 +4256,13 @@ def generate_manifest(
bare = sanitize_pack_path(
file_entry.get("destination", file_entry.get("name", ""))
)
save_root = file_entry.get("root") == "save"
omitted_by_destination[full_destination] = {
"dest": manifest_destination(full_destination),
"dest": (
save_manifest_dest(file_entry) if save_root
else manifest_destination(full_destination)
),
**(save_entry_fields(file_entry) if save_root else {}),
"name": str(file_entry.get("name") or ""),
"system": system_id,
"required": bool(file_entry.get("required", True)),
@@ -4534,7 +4573,8 @@ def generate_manifest(
"base_destination": base_dest,
"detect": detect,
"standalone_copies": _placed_copies(
standalone_copies, [entry["dest"] for entry in manifest_files]
standalone_copies,
[entry["dest"] for entry in manifest_files if entry.get("root") != "save"],
),
"total_files": len(manifest_files),
"total_size": total_size,
@@ -4556,6 +4596,11 @@ def generate_manifest(
}
if data_directories:
result["data_directories"] = data_directories
saved = [entry for entry in manifest_files if entry.get("root") == "save"]
if saved:
_check_save_destinations(manifest_files, omitted_by_destination.values())
layout = save_layout(config)
result["save_directory"] = {"folder": layout.folder, "by_core": layout.by_core}
if case_insensitive and fold_case is None and any(
d.get("os") not in CASE_FOLDING_OS for d in detect
):
@@ -4992,9 +5037,13 @@ from packpaths import ( # noqa: E402,F401
_flat,
_resolve_destination,
placement_block,
save_layout,
)
from packextras import ( # noqa: E402,F401
ExtraResolver,
extra_full_destination,
save_entry_fields,
save_manifest_dest,
_detect_extras_prefix,
_detect_slug_structure,
_map_emulator_to_slug,
+90 -11
View File
@@ -19,7 +19,8 @@ from common import core_system_dirs, platform_layouts, system_dir_path
from common import standalone_set as platform_standalone_set
from common import sanitize_pack_path
from common import size_fits
from validation import deliverable, read_from_system_dir
from validation import deliverable, read_from_save_dir, read_from_system_dir
from packpaths import save_destination, save_layout
from region import FILE_GROUP
@@ -31,6 +32,55 @@ def _detect_extras_prefix(config: dict, base_dest: str) -> str:
"""
return base_dest or declared_root(config)
def extra_full_destination(fe: dict, prefix: str) -> str:
"""Where a pack writes a core extra, from the pack root.
A file the core reads below the frontend's save directory goes to the
pack path its placement recorded (ExtraPlacement.place). Any other one
goes below the platform's prefix, unless its destination carries it.
The builder, the manifest and verify's replay all read this.
"""
if fe.get("root") == "save":
return str(fe.get("pack_path") or "")
dest = sanitize_pack_path(fe.get("destination", fe.get("name", "")))
if not dest or not prefix or dest.startswith(f"{prefix}/"):
return dest
return f"{prefix}/{dest}"
def save_entry_fields(fe: dict) -> dict:
"""What a manifest entry adds for a file read below the save directory.
``root: save`` puts its ``dest`` below the frontend's save directory,
laid out as the pack lays it out. ``core_folder`` is the core's
library_name when the platform's frontend inserts it as the first folder
of ``dest`` (RetroArch's sort_savefiles_enable): the installer drops it
where the frontend's own config turns sorting off.
"""
fields = {"root": "save"}
if fe.get("core_folder"):
fields["core_folder"] = str(fe["core_folder"])
return fields
def save_manifest_dest(fe: dict) -> str:
"""A manifest dest for a file read below the save directory.
The pack path below the platform's save folder when the file is placed,
the core's own folder first where the frontend sorts saves by core.
Without a save folder (an omission), the same shape: the core's folder,
then the path the core reads, so two cores reading one name below their
own folders stay two entries.
"""
pack_path = str(fe.get("pack_path") or "")
if pack_path:
return pack_path.split("/", 1)[1]
rel = sanitize_pack_path(fe.get("destination") or fe.get("path") or fe.get("name", ""))
folder = fe.get("core_folder")
return f"{folder}/{rel}" if folder else rel
def _detect_slug_structure(config: dict) -> tuple[bool, dict[str, str]]:
"""Detect whether a platform uses per-system slug destinations.
@@ -287,7 +337,9 @@ def hidden_extras(
dest = sanitize_pack_path(fe.get("destination", fe.get("name", "")))
if not dest or (region_drops and dest in region_drops):
continue
full = dest if not prefix or dest.startswith(f"{prefix}/") else f"{prefix}/{dest}"
full = extra_full_destination(fe, prefix)
if not full:
continue
reason = placement_block(full, seen, lower, parents, case_insensitive)
if reason == "taken":
# Another core's claim on the same path, settled by the claims
@@ -348,18 +400,16 @@ def unplaceable_extras(
the pack. The answer comes from the builder's own placement.
"""
placement = ExtraPlacement(config, "", profiles)
if not placement.slug_based:
return {}
unplaced: dict[tuple[str, str, str], str] = {}
for u in undeclared:
if not u.get("in_repo"):
continue
if not placement.slug_based and u.get("root") != "save":
continue
dest = _extra_dest(u)
if placement.place(u, dest) is None:
key = (u.get("emulator", ""), u.get("name", ""), u.get("path") or "")
unplaced[key] = (
"below_platform_folder" if placement.nested(dest) else "no_platform_slug"
)
unplaced[key] = placement.unplaced_reason(u, dest)
return unplaced
@@ -438,6 +488,10 @@ def _agnostic_scan_extras(
mode == "libretro" and standalone
):
continue
# The scan walks the BIOS tree: a file read below the save
# directory is placed by the gap report's pass.
if read_from_save_dir(f, standalone):
continue
# An agnostic BIOS mode says the BIOS filename is free, not that
# every file the emulator loads is. A free filename still has a
# known shape, and that shape is what identifies a candidate: an
@@ -838,6 +892,7 @@ class ExtraPlacement:
self.platform_systems = set(config.get("systems", {}).keys())
self.native_systems = native_slug_systems(config)
self.flat = bool(config.get("firmware_folder_flat"))
self.save = save_layout(config)
self.norm_map = (
{_norm_system_id(sid): sid for sid in self.platform_systems}
if self.slug_based
@@ -850,8 +905,16 @@ class ExtraPlacement:
``entry`` names its profile and may name its own system. A
slug-based platform cannot place a file of a system it has no
folder for.
folder for. A file the core reads below the frontend's save
directory (``root: save``) keeps its path relative to that
directory and goes below the platform's save folder, which a
platform keeping saves beside the content does not have.
"""
if entry.get("root") == "save":
if self.save is None:
return None
full = save_destination(dest, entry.get("core_folder"), self.save)
return (dest, full) if full else None
if self.slug_based:
slug = _slug_for(
entry, self.profiles, self.platform_systems, self.norm_map,
@@ -866,6 +929,12 @@ class ExtraPlacement:
"""True when the file would sit below a folder the frontend lists flat."""
return self.slug_based and self.flat and "/" in dest
def unplaced_reason(self, entry: dict, dest: str) -> str:
"""Why ``place`` returned None for this entry."""
if entry.get("root") == "save":
return "no_save_directory" if self.save is None else "no_core_folder"
return "below_platform_folder" if self.nested(dest) else "no_platform_slug"
def undeclared_report(
config: dict,
@@ -988,13 +1057,16 @@ def _collect_emulator_extras(
placed = placement.place(u, dest)
if not u["in_repo"]:
if placed is not None:
absent.append(_report_extra(u, name, placed[0]))
missing = _report_extra(u, name, placed[0])
if u.get("root") == "save":
missing["pack_path"] = placed[1]
absent.append(missing)
continue
if placed is None:
if unplaceable is not None:
reason = placement.unplaced_reason(u, dest)
unplaceable.append(
{**u, "unplaceable": "below_platform_folder"}
if placement.nested(dest) else u
u if reason == "no_platform_slug" else {**u, "unplaceable": reason}
)
continue
dest, full_dest = placed
@@ -1005,6 +1077,9 @@ def _collect_emulator_extras(
candidates = [
_report_extra(c, name, dest) for c in (u, *u.get("other_claims", []))
]
if u.get("root") == "save":
for item in candidates:
item["pack_path"] = full_dest
# Where the builder meets each claim: the strongest proof first, the
# report's profile order between equals, archives after loose files.
for item in candidates:
@@ -1248,6 +1323,10 @@ def _report_extra(u: dict, name: str, dest: str) -> dict:
}
if u.get("members"):
extra["members"] = u["members"]
if u.get("root") == "save":
extra["root"] = "save"
if u.get("core_folder") is not None:
extra["core_folder"] = u["core_folder"]
if u.get("system_dir"):
extra["system_dir"] = u["system_dir"]
# The archive layout the core opens ranks same-named copies by what
+87 -1
View File
@@ -6,7 +6,88 @@ on Windows is not a pack."""
from __future__ import annotations
from dataclasses import dataclass
from common import sanitize_pack_path
from validation import read_from_save_dir
@dataclass(frozen=True)
class SaveLayout:
"""Where a platform's frontend keeps the save directory it hands a core.
``folder`` names it twice: the folder of the pack that holds the files
read below it, and its default place beside the system directory
(RetroArch's own folder holds system/ and saves/). A pack is extracted
into the system directory, so that folder is moved by hand, and the
installer writes to the save directory the frontend's config names.
``by_core`` is set when the frontend hands each core <save
directory>/<library_name>, RetroArch's sort_savefiles_enable.
"""
folder: str
by_core: bool
def save_layout(config: dict) -> SaveLayout | None:
"""The platform's save directory, or None when it is no fixed place.
Read from the registry's ``save_directory``. A platform that keeps
saves beside the content, or in one folder per launched system, has
none: a file read below it has no single destination.
"""
block = config.get("save_directory")
if not isinstance(block, dict):
return None
folder = sanitize_pack_path(str(block.get("folder") or ""))
if not folder or "/" in folder:
return None
return SaveLayout(folder, bool(block.get("by_core")))
def save_destination(
path: str, core_folder: str | None, layout: SaveLayout
) -> str | None:
"""The pack path of a file a core reads below the save directory.
``path`` is relative to the directory the core is handed. ``core_folder``
is the folder a frontend sorting saves by core inserts: the core's
library_name, empty when it inserts none (an empty library_name, or a
core that reads the same place sorted or not), None when the profile
does not say. None then, where the frontend sorts by core.
"""
rel = sanitize_pack_path(path)
if not rel:
return None
if layout.by_core and core_folder:
return f"{layout.folder}/{sanitize_pack_path(core_folder)}/{rel}"
if layout.by_core and core_folder is None:
return None
return f"{layout.folder}/{rel}"
def core_save_folder(profile: dict) -> str | None:
"""The folder a frontend sorting saves by core inserts for this core.
RetroArch appends a non-empty library_name only (runloop.c
runloop_path_set_redirect). A core declaring ``save_folder_once`` adds
its own folder to the save directory unless it is already named so
(Cemu's in_named_dir): its paths start with that folder and land in the
same place sorted or not. None when the profile names no library_name.
"""
if "library_name" not in profile:
return None
if profile.get("save_folder_once"):
return ""
return str(profile.get("library_name") or "")
# Top folder of an emulator pack holding the files a libretro core reads
# below the frontend's save directory, apart from those of its system
# directory at the root.
EMULATOR_PACK_SAVE_FOLDER = "saves"
def _path_parents(dest: str) -> list[str]:
"""Return all parent directory segments of a path."""
parts = dest.split("/")
@@ -79,7 +160,12 @@ def _resolve_destination(file_entry: dict, standalone: bool) -> str:
else:
rel = file_entry.get("name", "")
return sanitize_pack_path(rel)
dest = sanitize_pack_path(rel)
# Read below the frontend's save directory: kept apart from the system
# directory's files at the root, in the folder the pack names for it.
if dest and read_from_save_dir(file_entry, standalone):
return f"{EMULATOR_PACK_SAVE_FOLDER}/{dest}"
return dest
def _archive_destination(
+24
View File
@@ -116,6 +116,24 @@ def _generic_manual(bios_paths: list[str]) -> str:
)
def _save_section(display: str, folder: str) -> str:
"""Where the files of the pack's save folder go.
Some cores read their system data below the save directory the frontend
hands them, not below the system directory the archive is extracted
into: Azahar keeps its user folder there.
"""
return (
" Files read from the save directory\n"
" ----------------------------------\n"
f" Some cores read files from {display}'s save directory, not its\n"
f" system directory. The {folder}/ folder of this archive holds them.\n"
f" Move its contents into the save directory ({folder}/ beside\n"
" system/ by default, see Settings > Directory > Save Files), keeping\n"
" the folders it holds. The automatic install writes them there.\n\n"
)
def _build_readme(
platform_name: str,
platform_display: str,
@@ -133,11 +151,14 @@ def _build_readme(
detect: list[dict] | None = None,
standalone_copies: list[dict] | None = None,
launch_folders: int = 0,
save_folder: str = "",
) -> str:
"""Build a personalized step-by-step README for each platform pack.
``launch_folders`` counts the folders a platform that hands each core
its own system directory gives them, where it names no system.
``save_folder`` is the pack folder holding files cores read below the
frontend's save directory, empty when the pack holds none.
"""
narrowings = narrowings or []
sep = "=" * 50
@@ -167,6 +188,9 @@ def _build_readme(
+ (pack_manual(platform_name) or _generic_manual(bios_paths))
)
if save_folder:
guide += _save_section(platform_display, save_folder)
if regions:
region_help = (
" - Wrong region? This pack was filtered. Only the\n"
+10 -5
View File
@@ -39,7 +39,7 @@ from common import (
variant_base_name,
)
from packresolve import preferred_entries
from validation import outside_gap_scope, with_archive_members
from validation import outside_gap_scope, read_from_save_dir, with_archive_members
if TYPE_CHECKING:
from packextras import ExtraPlacement
@@ -216,9 +216,12 @@ def profile_claims(
if not isinstance(entry, dict):
continue
# The same rule as the gap report and the builder: an entry the
# pack never carries (read from the save directory, a
# placeholder, a filename-agnostic scan) arbitrates nothing.
if outside_gap_scope(entry, is_standalone):
# pack never carries (a placeholder, a filename-agnostic scan)
# arbitrates nothing. One read below the save directory sits
# outside the BIOS tree, where no platform declares a file.
if outside_gap_scope(entry, is_standalone) or read_from_save_dir(
entry, is_standalone
):
continue
# A member of a romset is not a file at the BIOS root: the archive
# holding it occupies the destination (_archive_claims). Claiming
@@ -291,7 +294,9 @@ def _archive_claims(
"""
files = [
entry for entry in profile.get("files") or []
if isinstance(entry, dict) and not outside_gap_scope(entry, is_standalone)
if isinstance(entry, dict)
and not outside_gap_scope(entry, is_standalone)
and not read_from_save_dir(entry, is_standalone)
]
prefix = profile.get("archive_prefix", "")
claims: list[Claim] = []
+102
View File
@@ -60,6 +60,7 @@ def _validate_yaml_directory(
out.extend(_unreachable_citations(path, data))
if schema_name == "emulator.schema.json":
out.extend(_checks_without_values(path, data))
out.extend(_save_dir_without_library_name(path, data))
out.extend(_systems_outside_profile(path, data))
out.extend(_doubled_paths(path))
return out
@@ -343,6 +344,88 @@ def _semantic_database_checks(database: dict) -> list[str]:
return out
def _save_dir_without_library_name(path: Path, document: object) -> list[str]:
"""A file the libretro build reads below the save directory, in a profile
naming no library_name.
RetroArch hands each core <save directory>/<library_name> by default
(sort_savefiles_enable): without the name, packs and the installer
cannot say where the file goes, and leave it out.
"""
from validation import read_from_save_dir
if not isinstance(document, dict):
return []
if "library_name" in document:
return _save_folder_once_paths(path, document)
return [
f"{path.relative_to(ROOT)}: files[{index}] ({entry.get('name')}) is read "
"below the save directory and the profile declares no library_name"
for index, entry in enumerate(document.get("files") or [])
if isinstance(entry, dict)
and read_from_save_dir(entry)
and not ("path" in entry and entry["path"] is None)
]
def _save_folder_once_paths(path: Path, document: dict) -> list[str]:
"""A core adding its own folder once: each save path starts with it.
The pack places such a file at <save directory>/<path> whether the
frontend sorts saves by core or not, which holds only when the path
already names the folder the core reads below.
"""
from validation import read_from_save_dir
if not document.get("save_folder_once"):
return []
folder = f"{document.get('library_name') or ''}/"
return [
f"{path.relative_to(ROOT)}: files[{index}] ({entry.get('name')}) does not "
f"start with {folder}, the folder save_folder_once says the core adds"
for index, entry in enumerate(document.get("files") or [])
if isinstance(entry, dict)
and read_from_save_dir(entry)
and entry.get("path")
and not str(entry["path"]).startswith(folder)
]
def _save_directory_blocks() -> list[str]:
"""Each registry save_directory is null or a folder and a by_core flag.
The folder names the pack folder and the save directory's default place
beside the BIOS directory: one path segment.
"""
registry_path = ROOT / "platforms" / "_registry.yml"
if not registry_path.is_file():
return []
with registry_path.open(encoding="utf-8") as handle:
platforms = (yaml_load(handle) or {}).get("platforms") or {}
out: list[str] = []
for name, entry in sorted(platforms.items()):
if not isinstance(entry, dict) or "save_directory" not in entry:
continue
block = entry["save_directory"]
if block is None:
continue
folder = block.get("folder") if isinstance(block, dict) else None
if (
not isinstance(block, dict)
or set(block) != {"folder", "by_core"}
or not isinstance(folder, str)
or not folder
or re.search(r"[/\\]", folder)
or folder in (".", "..")
or not isinstance(block.get("by_core"), bool)
):
out.append(
f"platforms/_registry.yml: {name}.save_directory must be null or "
"{folder: <one path segment>, by_core: <bool>}"
)
return out
def _semantic_install_checks(path: Path, manifest: dict) -> list[str]:
out: list[str] = []
files = manifest.get("files", [])
@@ -367,6 +450,24 @@ def _semantic_install_checks(path: Path, manifest: dict) -> list[str]:
out.append(
f"{path.relative_to(ROOT)}: destinations are both downloadable and omitted"
)
saved = [
entry for entry in [*files, *manifest.get("case_variants", [])]
if entry.get("root") == "save"
]
layout = manifest.get("save_directory")
if saved and not layout:
out.append(
f"{path.relative_to(ROOT)}: entries read below the save directory "
"and no save_directory"
)
stray = sorted(
entry["dest"] for entry in [*files, *omitted]
if entry.get("core_folder") and entry.get("root") != "save"
)
if stray:
out.append(
f"{path.relative_to(ROOT)}: core_folder outside root: save ({stray[0]})"
)
return out
@@ -412,6 +513,7 @@ def main() -> int:
)
errors.extend(_unknown_data_dir_refs())
errors.extend(_repository_layout_in_profiles())
errors.extend(_save_directory_blocks())
if not args.source_only:
database_path = ROOT / "database.json"
+36 -9
View File
@@ -429,16 +429,40 @@ def check_file_validation(
def read_from_system_dir(file_entry: dict, standalone: bool = False) -> bool:
"""Whether the emulator reads this entry from the system directory.
A file loaded from the save, content or working directory is not a BIOS:
no platform lists it, no pack carries it, and the truth model must not
ask a platform to declare it. Two builds can read one file from two
A file loaded from the save, content or working directory is not in the
BIOS tree: no platform lists it, and the truth model must not ask a
platform to declare it. Packs place one read below the save directory
there (read_from_save_dir), never in the BIOS tree. Two builds can read one file from two
places: libretro Dolphin keeps its User tree in the save directory, the
standalone app in its own folder, so ``load_from`` can be keyed by build.
"""
load_from = _load_from(file_entry, standalone)
return not load_from or load_from == "system_dir"
def _load_from(file_entry: dict, standalone: bool) -> str:
"""The ``load_from`` value that applies to one build."""
load_from = file_entry.get("load_from", "")
if isinstance(load_from, dict):
load_from = load_from.get("standalone" if standalone else "libretro", "")
return not load_from or load_from == "system_dir"
return load_from or ""
def read_from_save_dir(file_entry: dict, standalone: bool = False) -> bool:
"""Whether the libretro build reads this entry below the frontend's save
directory, the one RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY returns.
``path`` is then relative to that directory. A standalone app has no
frontend to hand it one: its files are placed by ``standalone_path``.
"""
return not standalone and _load_from(file_entry, False) == "save_dir"
def entry_root(file_entry: dict, standalone: bool = False) -> str:
"""The frontend directory an entry's ``path`` is relative to: ``save``
for a file the libretro build reads below the save directory, else
``system``."""
return "save" if read_from_save_dir(file_entry, standalone) else "system"
def outside_gap_scope(file_entry: dict, is_standalone: bool) -> bool:
@@ -447,8 +471,10 @@ def outside_gap_scope(file_entry: dict, is_standalone: bool) -> bool:
Read by the gap report and by verify's undeclared-file pass, so both set
aside the same entries: a placeholder standing for a family of files, a
file the user imports through the UI (``path: null``), an entry of the
build the platform does not run, a file read outside the system
directory, and a filename-agnostic entry the builder scans for itself.
build the platform does not run, a file read outside the system and
save directories, and a filename-agnostic entry the builder scans for
itself. A file the libretro build reads below the save directory is in
scope: the pack places it there (``entry_root``).
"""
name = file_entry.get("name", "")
if "<" in name or ">" in name or "*" in name:
@@ -460,9 +486,10 @@ def outside_gap_scope(file_entry: dict, is_standalone: bool) -> bool:
mode == "libretro" and is_standalone
):
return True
return not read_from_system_dir(file_entry, is_standalone) or bool(
file_entry.get("agnostic")
)
return not (
read_from_system_dir(file_entry, is_standalone)
or read_from_save_dir(file_entry, is_standalone)
) or bool(file_entry.get("agnostic"))
def case_folded_slots(
+23 -4
View File
@@ -106,10 +106,12 @@ from validation import (
installed_with_emulator,
unread_on,
outside_gap_scope,
read_from_save_dir,
with_archive_members,
core_size_refusal,
same_slot_entries,
)
from packpaths import core_save_folder
DEFAULT_DB = "database.json"
DEFAULT_PLATFORMS_DIR = "platforms"
@@ -574,8 +576,14 @@ def find_undeclared_files(
# The key used to skip that fallback, so clk's Acorn/basic.rom and
# Electron/basic.rom both keyed as "basic.rom" under Batocera's
# standalone clk and the second was dropped.
# A file the libretro build reads below the save directory is
# placed there, apart from the system directory the platform
# lays out: no declaration of the BIOS tree answers for it.
save_root = read_from_save_dir(f, is_standalone)
if is_standalone:
dest = f.get("standalone_path") or f.get("path") or fname
elif save_root:
dest = f.get("path") or fname
else:
dest = system_dir_path(f, f.get("path") or fname)
raw_regions = f.get("region") or []
@@ -593,6 +601,10 @@ def find_undeclared_files(
f.get("system"),
f.get("variant_group"),
region_key,
# Below the save directory each core is handed a folder
# of its own: two cores reading hiscore.dat there read
# two files.
("save", core_save_folder(profile)) if save_root else None,
)
if not fname or unread_on(f, config.get("_key", "")):
continue
@@ -601,8 +613,8 @@ def find_undeclared_files(
if duplicate and (first is None or first["profile"] == emu_name):
continue
verdict = _candidate_verdict(
f, fname, is_standalone, include_all, declared_names, dest,
folded_slots,
f, fname, is_standalone, include_all,
{} if save_root else declared_names, dest, folded_slots,
)
if verdict == "settled":
seen_files.add(seen_key)
@@ -742,12 +754,17 @@ def find_undeclared_files(
}
if f.get("contents"):
entry["contents"] = f["contents"]
if f.get("system_dir"):
if save_root:
entry["root"] = "save"
folder = core_save_folder(profile)
if folder is not None:
entry["core_folder"] = folder
elif f.get("system_dir"):
entry["system_dir"] = f["system_dir"]
members = with_archive_members(f, profile.get("files", [])).get("members")
if members:
entry["members"] = members
if launcher_layer:
if launcher_layer and not save_root:
launcher_candidates.append((emu_name, is_standalone, f, entry))
if duplicate:
first.setdefault("other_claims", []).append(entry)
@@ -857,6 +874,8 @@ def find_exclusion_notes(
_UNPLACED_REASONS = {
"no_platform_slug": "no platform slug for its system",
"below_platform_folder": "below the folder the platform lists",
"no_save_directory": "read below a save directory the platform keeps in no fixed place",
"no_core_folder": "read below the save directory and the profile names no library_name",
}
+12 -3
View File
@@ -3253,11 +3253,16 @@ class KidVidTapesStayWithTheContent(unittest.TestCase):
working directory) or from its base directory (stella2023, the content
folder, and stella, a folder under the save directory): never the system
directory. Without load_from every Linux pack put about 68 MB of WAVs at
the BIOS root, in two cases."""
the BIOS root, in two cases. The save directory is a place a pack
reaches: stella's tapes go there, never to the BIOS tree."""
def test_no_pack_places_them_in_the_system_directory(self):
from scripts.common import load_emulator_profiles
from scripts.validation import outside_gap_scope
from scripts.validation import (
outside_gap_scope,
read_from_save_dir,
read_from_system_dir,
)
profiles = load_emulator_profiles(str(ROOT / "emulators"))
for name in ("stella", "stella2014", "stella2023"):
@@ -3265,7 +3270,11 @@ class KidVidTapesStayWithTheContent(unittest.TestCase):
if f["name"].lower().endswith(".wav")]
with self.subTest(profile=name):
self.assertEqual(len(tapes), 7)
self.assertTrue(all(outside_gap_scope(f, False) for f in tapes))
self.assertFalse(any(read_from_system_dir(f, False) for f in tapes))
for f in tapes:
self.assertTrue(
outside_gap_scope(f, False) or read_from_save_dir(f, False)
)
class ModuleConstantsDeclaredOnce(unittest.TestCase):
+22 -2
View File
@@ -18,7 +18,11 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from packpaths import _resolve_destination # noqa: E402
from validation import outside_gap_scope, read_from_system_dir # noqa: E402
from validation import ( # noqa: E402
outside_gap_scope,
read_from_save_dir,
read_from_system_dir,
)
class PathIsTheDestination(unittest.TestCase):
@@ -38,7 +42,11 @@ class LoadFromPerBuild(unittest.TestCase):
def test_each_build_reads_its_own_place(self):
self.assertFalse(read_from_system_dir(self.ENTRY, False))
self.assertTrue(read_from_system_dir(self.ENTRY, True))
self.assertTrue(outside_gap_scope(self.ENTRY, False))
# The libretro build's copy goes below the save directory, the
# standalone one below the folder the platform hands it.
self.assertTrue(read_from_save_dir(self.ENTRY, False))
self.assertFalse(read_from_save_dir(self.ENTRY, True))
self.assertFalse(outside_gap_scope(self.ENTRY, False))
self.assertFalse(outside_gap_scope(self.ENTRY, True))
def test_a_plain_value_holds_for_both(self):
@@ -67,11 +75,23 @@ class RetroArchCarriesDolphinWhereItReads(unittest.TestCase):
dolphin = [
e["destination"] for e in extras
if e.get("source_emulator") in ("Dolphin", "Ishiiruka")
and e.get("root") != "save"
]
self.assertTrue(dolphin)
self.assertEqual(
[d for d in dolphin if not d.startswith("dolphin-emu/Sys/")], []
)
# The User tree goes below the save directory, each core's folder
# first (RetroArch sorts saves by core).
user = {
e["pack_path"] for e in extras
if e.get("source_emulator") in ("Dolphin", "Ishiiruka")
and e.get("root") == "save"
}
self.assertIn("saves/dolphin-emu/User/GBA/gba_bios.bin", user)
self.assertTrue(all(
p.startswith(("saves/dolphin-emu/User/", "saves/Ishiiruka/User/")) for p in user
))
self.assertIn("dolphin-emu/Sys/Triforce/segaboot.gcm", dolphin)
+6 -1
View File
@@ -330,8 +330,13 @@ class CandidateVerdict(unittest.TestCase):
)
def test_a_file_read_from_elsewhere_is_not_a_bios_gap(self):
for where in ("content_dir", "working_directory", "internal_storage"):
self.assertEqual(
self.verdict({"name": "save.bin", "load_from": where}), "skip"
)
# Below the save directory: the pack places it there.
self.assertEqual(
self.verdict({"name": "save.bin", "load_from": "save_dir"}), "skip"
self.verdict({"name": "save.bin", "load_from": "save_dir"}), "keep"
)
self.assertEqual(
self.verdict({"name": "b.bin", "load_from": "system_dir"}), "keep"
+553
View File
@@ -0,0 +1,553 @@
"""Files a libretro core reads below the frontend's save directory.
Azahar's libretro core moves its user folder to <save_dir>/Azahar/
(src/citra_libretro/core_settings.cpp:1048-1080), so keys.txt, the shared
font and the NAND files are read there and not below the system directory.
The profile says so with ``load_from: save_dir``. The pack places these
files in the platform's save folder, the manifest marks them ``root: save``,
verify credits the same paths, and the installer writes them below the save
directory the frontend's config names.
"""
from __future__ import annotations
import functools
import hashlib
import http.server
import importlib.util
import json
import os
import shutil
import subprocess
import sys
import tempfile
import threading
import unittest
import zipfile
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import yaml # noqa: E402
from packpaths import ( # noqa: E402
EMULATOR_PACK_SAVE_FOLDER,
SaveLayout,
_resolve_destination,
save_destination,
save_layout,
)
from validation import ( # noqa: E402
entry_root,
outside_gap_scope,
read_from_save_dir,
read_from_system_dir,
)
_spec = importlib.util.spec_from_file_location("install", REPO_ROOT / "install.py")
install = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(install)
def _scratch(test: unittest.TestCase) -> Path:
"""A folder under the repository's tmp/, removed after the test."""
base = REPO_ROOT / "tmp"
base.mkdir(exist_ok=True)
path = Path(tempfile.mkdtemp(dir=str(base)))
test.addCleanup(shutil.rmtree, path, True)
return path
class TheEntryNamesItsRoot(unittest.TestCase):
ENTRY = {"name": "keys.txt", "path": "CoreX/sysdata/keys.txt",
"standalone_path": "sysdata/keys.txt",
"load_from": {"libretro": "save_dir"}}
def test_the_libretro_build_reads_below_the_save_directory(self):
self.assertTrue(read_from_save_dir(self.ENTRY))
self.assertEqual(entry_root(self.ENTRY), "save")
self.assertFalse(read_from_system_dir(self.ENTRY))
def test_the_standalone_build_has_no_frontend_save_directory(self):
self.assertFalse(read_from_save_dir(self.ENTRY, standalone=True))
self.assertEqual(entry_root(self.ENTRY, standalone=True), "system")
plain = {"name": "a.bin", "path": "a.bin", "load_from": "save_dir"}
self.assertTrue(read_from_save_dir(plain))
self.assertFalse(read_from_save_dir(plain, standalone=True))
def test_save_entries_are_in_the_pack_scope(self):
self.assertFalse(outside_gap_scope(self.ENTRY, False))
for other in ("content_dir", "working_directory", "internal_storage"):
with self.subTest(load_from=other):
self.assertTrue(outside_gap_scope({**self.ENTRY, "load_from": other}, False))
def test_an_emulator_pack_keeps_them_in_their_own_folder(self):
self.assertEqual(
_resolve_destination(self.ENTRY, False),
f"{EMULATOR_PACK_SAVE_FOLDER}/CoreX/sysdata/keys.txt",
)
self.assertEqual(_resolve_destination(self.ENTRY, True), "sysdata/keys.txt")
class ThePlatformNamesItsSaveDirectory(unittest.TestCase):
def test_the_registry_block(self):
self.assertEqual(
save_layout({"save_directory": {"folder": "saves", "by_core": True}}),
SaveLayout("saves", True),
)
for block in (None, {}, {"folder": ""}, {"folder": "a/b", "by_core": True}):
with self.subTest(block=block):
self.assertIsNone(save_layout({"save_directory": block}))
self.assertIsNone(save_layout({}))
def test_a_frontend_sorting_by_core_inserts_the_library_name(self):
layout = SaveLayout("saves", True)
self.assertEqual(save_destination("Azahar/sysdata/keys.txt", "Azahar", layout),
"saves/Azahar/Azahar/sysdata/keys.txt")
# Unknown: nowhere to put it. Empty: the core reads the same place
# sorted or not (an empty library_name, Cemu's own folder).
self.assertIsNone(save_destination("Azahar/sysdata/keys.txt", None, layout))
self.assertEqual(save_destination("Cemu/mlc01/x.ttf", "", layout),
"saves/Cemu/mlc01/x.ttf")
self.assertEqual(
save_destination("Azahar/sysdata/keys.txt", "", SaveLayout("savefiles", False)),
"savefiles/Azahar/sysdata/keys.txt",
)
def test_retroarch_and_lakka_have_one_retropie_none(self):
from common import load_platform_config
platforms = str(REPO_ROOT / "platforms")
self.assertEqual(save_layout(load_platform_config("retroarch", platforms)),
SaveLayout("saves", True))
self.assertEqual(save_layout(load_platform_config("lakka", platforms)),
SaveLayout("savefiles", True))
# RetroPie inherits RetroArch but keeps saves beside each game.
self.assertIsNone(save_layout(load_platform_config("retropie", platforms)))
class _Fixture(unittest.TestCase):
"""A RetroArch-like platform with a save directory, one without, and a
core reading one file below the system directory and one below the save
directory. The platform declares a keys.txt of its own in the BIOS tree:
it does not answer for the one the core reads below the save directory."""
SYS = b"SYSTEM FILE"
KEYS = b"SAVE DIR KEYS"
OTHER_KEYS = b"PLATFORM KEYS"
def setUp(self) -> None:
from common import _emulator_profiles_cache, _platform_config_cache
_emulator_profiles_cache.clear()
_platform_config_cache.clear()
self.addCleanup(_platform_config_cache.clear)
self.addCleanup(_emulator_profiles_cache.clear)
self.root = _scratch(self)
self.bios = self.root / "bios"
self.platforms = self.root / "platforms"
self.emulators = self.root / "emulators"
for folder in (self.bios / "X", self.bios / "Y", self.platforms, self.emulators):
folder.mkdir(parents=True)
files: dict[str, dict] = {}
by_name: dict[str, list[str]] = {}
by_md5: dict[str, str] = {}
for rel, data in (("X/sys.bin", self.SYS), ("X/keys.txt", self.KEYS),
("Y/keys.txt", self.OTHER_KEYS)):
path = self.bios / rel
path.write_bytes(data)
sha1 = hashlib.sha1(data).hexdigest()
md5 = hashlib.md5(data).hexdigest()
files[sha1] = {"path": str(path), "name": path.name, "md5": md5,
"size": len(data), "crc32": ""}
by_name.setdefault(path.name, []).append(sha1)
by_md5[md5] = sha1
self.db = {"files": files, "indexes": {
"by_md5": by_md5, "by_name": by_name, "by_crc32": {}, "by_path_suffix": {},
}}
system = {"files": [{"name": "keys.txt", "destination": "keys.txt",
"sha1": hashlib.sha1(self.OTHER_KEYS).hexdigest(),
"required": False}]}
for name in ("testra", "testnosave"):
(self.platforms / f"{name}.yml").write_text(yaml.safe_dump({
"platform": name, "verification_mode": "existence",
"base_destination": "system", "cores": ["corex"],
"systems": {"test-console": system},
}))
(self.platforms / "_registry.yml").write_text(yaml.safe_dump({"platforms": {
"testra": {"save_directory": {"folder": "saves", "by_core": True}},
"testnosave": {"save_directory": None},
}}))
(self.emulators / "corex.yml").write_text(yaml.safe_dump({
"emulator": "CoreX", "type": "libretro", "library_name": "CoreX",
"systems": ["test-console"],
"files": [
{"name": "sys.bin", "path": "sys.bin", "required": True,
"sha1": hashlib.sha1(self.SYS).hexdigest()},
{"name": "keys.txt", "path": "CoreX/sysdata/keys.txt",
"load_from": "save_dir", "required": False,
"sha1": hashlib.sha1(self.KEYS).hexdigest()},
],
}))
def config(self, name: str) -> dict:
from common import load_platform_config
return load_platform_config(name, str(self.platforms))
def profiles(self) -> dict:
from common import load_emulator_profiles
return load_emulator_profiles(str(self.emulators))
def pack(self, name: str) -> list[str]:
from generate_pack import generate_pack
out = self.root / f"out-{name}"
out.mkdir()
zip_path = generate_pack(
name, str(self.platforms), self.db, str(self.bios), str(out),
emulators_dir=str(self.emulators), offline=True,
)
with zipfile.ZipFile(zip_path) as zf:
self.readme = zf.read("README.txt").decode()
return sorted(zf.namelist())
def manifest(self, name: str) -> dict:
from generate_pack import generate_manifest
return generate_manifest(
name, str(self.platforms), self.db, str(self.bios),
str(self.platforms / "_registry.yml"),
emulators_dir=str(self.emulators), offline=True,
)
def verify(self, name: str) -> dict:
from verify import verify_platform
return verify_platform(self.config(name), self.db, str(self.emulators))
class ThePackPlacesThemInTheSaveFolder(_Fixture):
SAVED = "saves/CoreX/CoreX/sysdata/keys.txt"
def test_the_report_marks_the_entry(self):
from verify import find_undeclared_files
report = find_undeclared_files(
self.config("testra"), str(self.emulators), self.db, self.profiles()
)
saved = [u for u in report if u["name"] == "keys.txt"]
self.assertEqual(len(saved), 1, "the platform's keys.txt answers nothing here")
self.assertEqual(
{k: saved[0].get(k) for k in ("root", "core_folder", "path", "in_repo")},
{"root": "save", "core_folder": "CoreX",
"path": "CoreX/sysdata/keys.txt", "in_repo": True},
)
def test_pack_manifest_and_verify_agree(self):
names = self.pack("testra")
self.assertIn(self.SAVED, names)
self.assertIn("keys.txt", names, "the platform's own keys.txt stays")
self.assertIn("Files read from the save directory", self.readme)
report = self.verify("testra")
written = {n for n in names if n != "README.txt" and not n.endswith("/")}
self.assertEqual(set(report["pack_destinations"]), written)
manifest = self.manifest("testra")
saved = [e for e in manifest["files"] if e.get("root") == "save"]
self.assertEqual(len(saved), 1)
self.assertEqual(
{k: saved[0][k] for k in ("dest", "root", "core_folder", "sha1")},
{"dest": "CoreX/CoreX/sysdata/keys.txt", "root": "save", "core_folder": "CoreX",
"sha1": hashlib.sha1(self.KEYS).hexdigest()},
)
self.assertEqual(manifest["save_directory"], {"folder": "saves", "by_core": True})
# The fixture's copies live outside a repository checkout: give them
# the repository path the installer downloads from.
for entry in manifest["files"]:
entry["repo_path"] = f"bios/{Path(entry['repo_path']).name}"
install._validate_manifest(manifest, "testra")
def test_a_platform_without_a_save_directory_says_so(self):
names = self.pack("testnosave")
self.assertFalse(any(n.startswith("saves/") for n in names))
self.assertNotIn("Files read from the save directory", self.readme)
manifest = self.manifest("testnosave")
self.assertNotIn("save_directory", manifest)
omitted = [e for e in manifest["omitted_files"] if e.get("root") == "save"]
self.assertEqual([(e["dest"], e["reason"]) for e in omitted],
[("CoreX/CoreX/sysdata/keys.txt", "no_save_directory")])
report = self.verify("testnosave")
entry = next(u for u in report["undeclared_files"] if u.get("root") == "save")
self.assertFalse(entry["in_pack"])
self.assertIn("no fixed place", entry["omitted"])
self.assertFalse(any(p.startswith("saves/") for p in report["pack_destinations"]))
def test_the_emulator_pack_and_verify_emulator_agree(self):
from generate_pack import PackRecord, generate_emulator_pack
from verify import verify_emulator
record = PackRecord()
generate_emulator_pack(
["corex"], str(self.emulators), self.db, str(self.bios), str(self.root),
offline=True, platforms_dir=str(self.platforms), record=record,
)
self.assertIn("saves/CoreX/sysdata/keys.txt", record.files)
report = verify_emulator(["corex"], str(self.emulators), self.db,
platforms_dir=str(self.platforms))
self.assertEqual(set(report["pack_destinations"]), set(record.files))
class TheInstallerFindsTheSaveDirectory(unittest.TestCase):
MANIFEST = {"base_destination": "system",
"save_directory": {"folder": "saves", "by_core": True}}
def setUp(self) -> None:
self.root = _scratch(self)
self.app = self.root / "RetroArch"
self.bios = self.app / "system"
self.cfg = self.app / "retroarch.cfg"
self.bios.mkdir(parents=True)
self.addCleanup(install._RETROARCH_CONFIGS.pop, self.bios, None)
self.addCleanup(install._SAVE_DIRS.pop, self.bios, None)
def place(self, cfg: str | None, override: Path | None = None):
if cfg is not None:
self.cfg.write_text(cfg)
install._RETROARCH_CONFIGS[self.bios] = (self.cfg, self.app)
return install.save_directory_for(self.bios, self.MANIFEST, override)
def test_a_config_left_at_its_defaults(self):
self.assertEqual(self.place('system_directory = ":\\\\system"\n'),
install.SavePlace(self.app / "saves", True))
def test_a_configured_folder_and_sorting(self):
saves = self.root / "elsewhere"
saves.mkdir()
cfg = f'savefile_directory = "{saves}"\nsort_savefiles_enable = "false"\n'
self.assertEqual(self.place(cfg), install.SavePlace(saves, False))
def test_a_configured_folder_that_does_not_exist_is_ignored(self):
cfg = f'savefile_directory = "{self.root / "gone"}"\n'
self.assertEqual(self.place(cfg), install.SavePlace(self.app / "saves", True))
def test_saves_beside_each_game_have_no_single_place(self):
for key in ("savefiles_in_content_dir", "sort_savefiles_by_content_enable"):
with self.subTest(key=key):
found = self.place(f'{key} = "true"\n')
self.assertIsInstance(found, str)
self.assertIn(key, found)
def test_without_a_config(self):
self.assertEqual(self.place(None), install.SavePlace(self.app / "saves", True))
other = self.root / "BIOS"
found = install.save_directory_for(other, self.MANIFEST)
self.assertIsInstance(found, str)
self.assertIn("--save-dir", found)
self.assertEqual(install.save_directory_for(other, self.MANIFEST, self.root / "s"),
install.SavePlace(self.root / "s", True))
def test_the_entry_path(self):
saves = (self.app / "saves").resolve()
install._SAVE_DIRS[self.bios] = install.SavePlace(self.app / "saves", True)
entry = {"dest": "Azahar/Azahar/sysdata/keys.txt", "root": "save",
"core_folder": "Azahar"}
self.assertEqual(install._entry_path(self.bios, entry),
saves / "Azahar/Azahar/sysdata/keys.txt")
# sort_savefiles_enable off: the core is handed the save directory.
install._SAVE_DIRS[self.bios] = install.SavePlace(self.app / "saves", False)
self.assertEqual(install._entry_path(self.bios, entry),
saves / "Azahar/sysdata/keys.txt")
# A list laid out unsorted, a frontend sorting: the folder goes in.
install._SAVE_DIRS[self.bios] = install.SavePlace(self.app / "saves", True, False)
self.assertEqual(
install._entry_path(self.bios, {**entry, "dest": "Azahar/sysdata/keys.txt"}),
saves / "Azahar/Azahar/sysdata/keys.txt",
)
# Cemu adds its folder once: no core_folder, the same place either way.
cemu = {"dest": "Cemu/mlc01/a.ttf", "root": "save"}
for by_core in (True, False):
install._SAVE_DIRS[self.bios] = install.SavePlace(self.app / "saves", by_core)
self.assertEqual(install._entry_path(self.bios, cemu), saves / "Cemu/mlc01/a.ttf")
plain = {"dest": "Azahar/sysdata/keys.txt"}
self.assertEqual(install._entry_path(self.bios, plain),
self.bios.resolve() / "Azahar/sysdata/keys.txt")
class TheManifestBoundary(unittest.TestCase):
ENTRY = {"dest": "Azahar/Azahar/sysdata/keys.txt", "sha1": "a" * 40, "sha256": "b" * 64,
"size": 1, "repo_path": "bios/x/keys.txt", "cores": ["azahar"],
"root": "save", "core_folder": "Azahar"}
def manifest(self, entry: dict, layout: object = ...) -> dict:
data = {"manifest_version": 2, "platform": "retroarch", "files": [entry],
"total_files": 1, "total_size": 1}
if layout is not ...:
data["save_directory"] = layout
return data
def test_a_save_entry_crosses_it(self):
install._validate_manifest(
self.manifest(self.ENTRY, {"folder": "saves", "by_core": True}), "retroarch"
)
def test_what_is_refused(self):
layout = {"folder": "saves", "by_core": True}
for label, data in (
("no save_directory", self.manifest(self.ENTRY)),
("other root", self.manifest({**self.ENTRY, "root": "content"}, layout)),
("climbing core folder", self.manifest({**self.ENTRY, "core_folder": ".."}, layout)),
("nested core folder", self.manifest({**self.ENTRY, "core_folder": "a/b"}, layout)),
("core folder on a BIOS file", self.manifest(
{k: v for k, v in self.ENTRY.items() if k != "root"}, layout)),
("climbing save folder", self.manifest(self.ENTRY, {"folder": "..", "by_core": True})),
("save folder with a path", self.manifest(self.ENTRY, {"folder": "a/b", "by_core": True})),
("by_core not a flag", self.manifest(self.ENTRY, {"folder": "saves", "by_core": 1})),
("dest without its core folder", self.manifest(
{**self.ENTRY, "dest": "sysdata/keys.txt"}, layout)),
):
with self.subTest(label), self.assertRaises(ValueError):
install._validate_manifest(data, "retroarch")
class _Repository:
"""A loopback copy of the repository serving one manifest and its files."""
def __init__(self, test: unittest.TestCase, files: dict[str, tuple[bytes, dict]]):
self.root = _scratch(test)
(self.root / "install").mkdir()
(self.root / "bios").mkdir()
entries = []
for dest, (payload, extra) in files.items():
stored = dest.replace("/", "_")
(self.root / "bios" / stored).write_bytes(payload)
entries.append({
"dest": dest, "sha1": hashlib.sha1(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "size": len(payload),
"repo_path": f"bios/{stored}", "cores": None, **extra,
})
manifest = {"manifest_version": 2, "platform": "retroarch",
"base_destination": "system", "files": entries,
"save_directory": {"folder": "saves", "by_core": True}}
(self.root / "install" / "retroarch.json").write_text(json.dumps(manifest))
handler = functools.partial(_Quiet, directory=str(self.root))
self.httpd = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler)
threading.Thread(target=self.httpd.serve_forever, daemon=True).start()
test.addCleanup(self.httpd.server_close)
test.addCleanup(self.httpd.shutdown)
def run(self, *options: str) -> subprocess.CompletedProcess:
env = dict(os.environ)
env["RETROBIOS_BASE_URL"] = f"http://127.0.0.1:{self.httpd.server_address[1]}"
return subprocess.run(
[sys.executable, str(REPO_ROOT / "install.py"), "--platform", "retroarch",
"--yes", *options],
env=env, capture_output=True, text=True, timeout=120, check=False,
stdin=subprocess.DEVNULL,
)
class _Quiet(http.server.SimpleHTTPRequestHandler):
def log_message(self, *args) -> None: # noqa: D102
pass
class TheInstallerWritesThem(unittest.TestCase):
FILES = {
"sys.bin": (b"system file", {}),
"Azahar/Azahar/sysdata/keys.txt": (
b"keys below the save directory", {"root": "save", "core_folder": "Azahar"},
),
}
def test_beside_the_default_bios_folder(self):
repo = _Repository(self, self.FILES)
app = _scratch(self)
proc = repo.run("--dest", str(app / "system"))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertEqual((app / "system" / "sys.bin").read_bytes(), b"system file")
self.assertEqual(
(app / "saves" / "Azahar" / "Azahar" / "sysdata" / "keys.txt").read_bytes(),
b"keys below the save directory",
)
self.assertFalse((app / "system" / "Azahar").exists())
self.assertIn("read from RetroArch's save directory go to", proc.stdout)
def test_where_save_dir_points(self):
repo = _Repository(self, self.FILES)
app = _scratch(self)
saves = app / "my saves"
proc = repo.run("--dest", str(app / "BIOS"), "--save-dir", str(saves))
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertTrue((saves / "Azahar" / "Azahar" / "sysdata" / "keys.txt").is_file())
def test_left_out_and_said_when_the_save_directory_is_unknown(self):
repo = _Repository(self, self.FILES)
app = _scratch(self)
proc = repo.run("--dest", str(app / "BIOS"))
self.assertTrue((app / "BIOS" / "sys.bin").is_file(), proc.stdout + proc.stderr)
self.assertFalse(any(app.rglob("keys.txt")))
self.assertIn("--save-dir", proc.stdout)
class TheProfilesFollowTheContract(unittest.TestCase):
def test_every_save_dir_entry_has_a_library_name(self):
try:
from validate_schemas import _save_dir_without_library_name
except ImportError:
self.skipTest("validate_schemas needs jsonschema")
for path in sorted((REPO_ROOT / "emulators").glob("*.yml")):
document = yaml.safe_load(path.read_text(encoding="utf-8"))
with self.subTest(profile=path.name):
self.assertEqual(_save_dir_without_library_name(path, document), [])
def test_azahar_reads_its_user_folder_below_the_save_directory(self):
profile = yaml.safe_load((REPO_ROOT / "emulators" / "azahar.yml").read_text())
self.assertEqual(profile["library_name"], "Azahar")
for entry in profile["files"]:
with self.subTest(entry=entry["name"]):
self.assertTrue(read_from_save_dir(entry))
self.assertTrue(entry["path"].startswith("Azahar/"))
def test_the_registry_blocks(self):
try:
from validate_schemas import _save_directory_blocks
except ImportError:
self.skipTest("validate_schemas needs jsonschema")
self.assertEqual(_save_directory_blocks(), [])
def test_the_published_retroarch_list_places_azahar(self):
manifest = json.loads((REPO_ROOT / "install" / "retroarch.json").read_text())
self.assertEqual(manifest.get("save_directory"), {"folder": "saves", "by_core": True})
azahar = {e["dest"]: e for e in manifest["files"] if e.get("core_folder") == "Azahar"}
self.assertIn("Azahar/Azahar/sysdata/keys.txt", azahar)
self.assertTrue(all(e["root"] == "save" for e in azahar.values()))
class TheSchemasCarryTheFields(unittest.TestCase):
def test_install_manifest_and_profile_schemas(self):
try:
from jsonschema import Draft202012Validator
except ImportError:
self.skipTest("jsonschema is not installed")
schema = json.loads((REPO_ROOT / "schemas" / "install-manifest.schema.json").read_text())
validator = Draft202012Validator(schema)
manifest = json.loads((REPO_ROOT / "install" / "retroarch.json").read_text())
self.assertEqual(list(validator.iter_errors(manifest)), [])
bad = {**manifest, "files": [{**manifest["files"][0], "root": "content"}]}
self.assertTrue(list(validator.iter_errors(bad)))
emulator = json.loads((REPO_ROOT / "schemas" / "emulator.schema.json").read_text())
profile = yaml.safe_load((REPO_ROOT / "emulators" / "azahar.yml").read_text())
self.assertEqual(list(Draft202012Validator(emulator).iter_errors(profile)), [])
self.assertTrue(list(Draft202012Validator(emulator).iter_errors(
{**profile, "library_name": "a/b"}
)))
if __name__ == "__main__":
unittest.main()
+6
View File
@@ -284,6 +284,12 @@ that text belongs in a guide field.
case_insensitive_fs: true # if the platform runs on case-insensitive filesystems
extract_to: /userdata/bios/ # folder the pack extracts into, shown in the README,
# the site and the release notes
save_directory: # the save directory the frontend hands libretro cores,
folder: saves # where it is one place: the pack folder holding the
by_core: true # files cores read there (load_from: save_dir), its
# default place beside the BIOS folder, and whether
# the frontend appends the core's library_name.
# null when it is one folder per game or system
zip_digest: composite # the frontend hashes a .zip by its members only
firmware_folder_flat: true # the frontend lists each system folder without
# descending into it
+19
View File
@@ -87,6 +87,24 @@ from the text and calling it passes the arguments through:
[Files you already had](#files-you-already-had).
7. Copies into standalone emulator directories, only with `--standalone-copies`.
Some cores read their system data below the frontend's save directory, not
below the BIOS folder: Azahar's keys, shared font and NAND files, Citra's
`sysdata`, Dolphin's `User` tree. The file list marks them `root: save`, and
the run writes them below the save directory, which it names before checking
files (`5 files read from RetroArch's save directory go to ...`). For a
RetroArch found by its `retroarch.cfg`, that is `savefile_directory` as
RetroArch expands it (`:` for its own folder, `~` for the home), `saves/`
beside the config when the value is `default`, empty, or names no folder, and
the core's own folder below it when `sort_savefiles_enable` is on, as it is by
default. Without a config, a BIOS folder at its default name (`system`) puts
the save directory beside it (`saves`, `/storage/savefiles` on Lakka).
`--save-dir PATH` names the save directory itself. When none of these
applies, or when `savefiles_in_content_dir` or
`sort_savefiles_by_content_enable` keeps saves beside each game, those files
are left out and the run says why, like the other entries the list cannot
install. Batocera, RetroBat and RetroPie hand each system or game a save folder
of its own: their lists record these files as omitted.
A RetroArch run into an empty folder printed this on 2026-10-21:
```
@@ -398,6 +416,7 @@ what the options name, everything by default.
| `--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices |
| `--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) |
| `--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` |
| `--save-dir PATH` | The frontend's save directory (RetroArch's `savefile_directory`), for the files some cores read there, when the run cannot find it. The core's own folder is still added below it when the frontend sorts saves by core |
| `--system NAMES` | Install only these systems. Comma-separated or repeated. `--list-systems` prints the names, `other` included |
| `--core NAMES` | Keep the platform's own list and only the extra files these cores load. `--list-cores` prints the names |
| `--emulator NAMES` | Install what these emulators read on the detected platform, and nothing else: their own files and the platform's files of the systems they run. Each emulator page on the site gives this command. `--list-emulators` prints the names, buildbot names such as `mednafen_psx` included |
+23 -2
View File
@@ -523,6 +523,8 @@ reports the profile as uncheckable, and the CI job that runs
| `systems` | no | list of system IDs this core handles |
| `cores` | no | every upstream name the core is known by, for buildbot/target matching |
| `alias_of` | for `type: alias` | profile key this one aliases. The alias carries no `files` |
| `library_name` | when a libretro file has `load_from: save_dir` | the `library_name` the core reports in `retro_get_system_info`, read from the code. RetroArch sorts saves by core by default and hands the core `<save directory>/<library_name>`, so packs and the installer need it to place a file read there. Empty when the core reports an empty name, for which RetroArch adds no folder (FS-UAE, `libretro/libretro.c:575`) |
| `save_folder_once` | no | `true` when the core adds its own folder to the save directory only where the directory it is handed is not already named so (Cemu's `in_named_dir`, `src/libretro/CemuLibretro.cpp:1670-1681`). Every `load_from: save_dir` path then starts with that folder, the `library_name`, and the file lands in the same place whether the frontend sorts saves by core or not |
| `mode` | no | default mode: `standalone`, `libretro`, or `both` |
| `verification` | no | how the core verifies BIOS: `existence`, `md5`, `sha1`, `crc32` |
| `files` | yes, unless `type: alias` | list of file entries |
@@ -546,6 +548,25 @@ profile whose BIOS set is the same), `resources_directory`, `rom_path`,
firmware installs), `dev_flash`, `dev_flash2`, `dev_flash3` (PS3 flash
partitions), and `game_count`.
### Files read from the save directory
Some libretro cores read system data below the save directory the frontend
hands them, not below the system directory. Azahar moves its whole user folder
to `<save_dir>/Azahar/` under the default option "LibRetro Default"
(`src/citra_libretro/core_settings.cpp:1048-1080`), falling back to the system
directory only when the frontend gives none. Such an entry carries
`load_from: save_dir` (or `{libretro: save_dir}`), a `path` relative to the
save directory, and the profile names its `library_name`.
The save directory itself is the platform's: `save_directory` in
`platforms/_registry.yml` names it where the frontend keeps it in one place.
RetroArch keeps `saves/` beside `system/` and sorts by core, so Azahar's keys
land in `saves/Azahar/Azahar/sysdata/keys.txt`. Lakka keeps
`/storage/savefiles`. Batocera, RetroBat and RetroPie hand each game or system
a folder of its own: those entries are listed as omitted
(`no_save_directory`), never placed in the BIOS tree where the core does not
look.
### File entry fields
| Field | Description |
@@ -581,10 +602,10 @@ partitions), and `game_count`.
| `installed_on` | platforms whose own package installs the file beside the emulator although the release does not: their packs leave it out (Batocera and RetroPie copy blueMSX's machines beside the core) |
| `has_builtin` | true if the core falls back to a built-in copy when the file is absent |
| `config_key` | configuration key the emulator exposes to point at the file |
| `load_from` | directory the core reads the file from when it is not the system directory |
| `load_from` | directory the core reads the file from when it is not the system directory, keyed by build (`{libretro: save_dir}`) when the standalone build reads `standalone_path` from its own folder. `save_dir` is the directory the frontend returns for `RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY`: `path` is then relative to it, as the code joins it (Azahar's `Azahar/sysdata/keys.txt`, Dolphin's `User/GBA/gba_bios.bin`), and packs and the installer place the libretro build's file below the save directory (see [Files read from the save directory](#files-read-from-the-save-directory)). `content_dir`, `working_directory`, `config_dir`, `data_dir` and the others describe places no pack reaches |
| `found_by` | `name` when the code opens the file by its name (the default), `content` when it scans a folder and identifies the file by hash, so the name is only a label (DuckStation by MD5, `bios.cpp:208`). Set on the profile or on one file, a dict splits `standalone` from `libretro`. A label is never written into a platform file as a name to hold, and pairs with a platform entry only by hash |
| `source_ref` | source file and line number (e.g. `boot.cpp:42`), read at the profile's `source_commit` when set. A dict splits `standalone` from `libretro` when they diverge. In a profile that declares both `source` and `upstream`, each cited path opens with the one its lines are in: `source libretro.c:120` for the port's own code, `upstream emu/memory.c:46` for the emulator's, including the copy a port carries. `profile_sync --attribute-refs` writes these words from where the lines are found, and the site labels each citation with them |
| `path` | destination path relative to system directory |
| `path` | destination path relative to the system directory, or to the save directory under `load_from: save_dir` |
| `standalone_path` | where the standalone build reads the file, under its own data folder, when it differs from `path` (the libretro destination). Packs for a platform running the emulator standalone use it |
| `on_mismatch` | what the code does when a declared check fails: `reject` (default) or `warn` and run with it, for every check or check by check (`{size: warn, md5: reject}`) |
| `variant_group` | one slot of one core that several entries fill, whatever system each is filed under (PicoDrive's twelve Mega CD BIOS). Region selection keeps one per group |
+1 -1
View File
@@ -49,7 +49,7 @@ Every module in `tests/`, by the area it guards.
| Core regression suites | synthetic | `test_e2e`, `test_audit_regressions`, `test_tool_messages` |
| File resolution and identity | synthetic + collection | `test_adler32_resolution`, `test_agnostic_scan`, `test_agnostic_scan_mode`, `test_agnostic_scope`, `test_agnostic_substitute`, `test_archive_choice`, `test_archive_recipes`, `test_bios_companions`, `test_bundled_identity`, `test_casefold_cache`, `test_db_aliases`, `test_declared_names`, `test_derivations`, `test_directory_entries`, `test_extra_identity`, `test_foreign_tree`, `test_foreign_tree_names`, `test_frontend_digest`, `test_game_data_homonyms`, `test_hashing`, `test_identity`, `test_md5_pack_context`, `test_name_tiebreak`, `test_nec_filing`, `test_owner_resolution`, `test_path_suffix`, `test_resolution_evidence`, `test_sega_filing`, `test_shipped_content`, `test_twin_declarations`, `test_validated_choice`, `test_variant_choice`, `test_variant_identity`, `test_zipped_member` |
| Verification, cross-reference and profile checks | synthetic + profiles | `test_cache_independent_counts`, `test_case_insensitive_platforms`, `test_checks_have_values`, `test_core_file_fills_declaration`, `test_core_size_refusal`, `test_cross_reference_sources`, `test_crypto_aes`, `test_crypto_unique_data`, `test_crypto_verify`, `test_entry_categories`, `test_file_consumers`, `test_fileless_audit`, `test_flycast_layout`, `test_gap_settlement`, `test_hidden_extras`, `test_launcher_aliases`, `test_launcher_layer`, `test_native_mode`, `test_no_case_collisions`, `test_path_conflict_status`, `test_path_null_copies`, `test_profile_checks`, `test_profile_repositories`, `test_romm_hash_rule`, `test_shared_groups`, `test_standalone_links`, `test_standalone_mode`, `test_undeclared_dedup`, `test_unplaced_extras`, `test_validation_scope`, `test_verify_all_dir`, `test_verify_emulator_evidence`, `test_verify_listing`, `test_verify_pack_choice`, `test_verify_release_asset` |
| Packs and manifests | synthetic + real packs | `test_artifact_lock`, `test_atomic_artifacts`, `test_batocera_cores`, `test_deterministic_zip`, `test_emulator_pack_counts`, `test_finalize_pack_names`, `test_large_file_cache`, `test_libretro_layout`, `test_manifest_entry`, `test_minui_platform`, `test_no_game_data`, `test_pack_conformance_dirs`, `test_pack_core_extras`, `test_pack_counts`, `test_pack_destinations`, `test_pack_failures`, `test_pack_file_names`, `test_pack_grouping`, `test_pack_integrity`, `test_pack_member_identity`, `test_pack_readme`, `test_platform_inheritance`, `test_platform_keys`, `test_required_only_archives`, `test_required_only_twins`, `test_split_extras`, `test_split_names`, `test_split_pack`, `test_system_pack_extras`, `test_system_pack_parity`, `test_torrentzip`, `test_which_pack` |
| Packs and manifests | synthetic + real packs | `test_artifact_lock`, `test_atomic_artifacts`, `test_batocera_cores`, `test_deterministic_zip`, `test_emulator_pack_counts`, `test_finalize_pack_names`, `test_large_file_cache`, `test_libretro_layout`, `test_manifest_entry`, `test_minui_platform`, `test_no_game_data`, `test_pack_conformance_dirs`, `test_pack_core_extras`, `test_pack_counts`, `test_pack_destinations`, `test_pack_failures`, `test_pack_file_names`, `test_pack_grouping`, `test_pack_integrity`, `test_pack_member_identity`, `test_pack_readme`, `test_platform_inheritance`, `test_platform_keys`, `test_required_only_archives`, `test_required_only_twins`, `test_save_root`, `test_split_extras`, `test_split_names`, `test_split_pack`, `test_system_pack_extras`, `test_system_pack_parity`, `test_torrentzip`, `test_which_pack` |
| Slots and regions | synthetic + profiles | `test_region`, `test_region_agnostic_scan`, `test_region_groups`, `test_region_held`, `test_region_platform_index`, `test_region_required_only`, `test_slot`, `test_slots`, `test_slots_shipped_file` |
| Hardware targets and systems | synthetic + target files | `test_extract_targets`, `test_lakka_targets`, `test_libretro_slugs`, `test_system_ids`, `test_system_selection`, `test_target_names`, `test_target_scrapers`, `test_target_systems`, `test_target_verify` |
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_selection` |