diff --git a/wiki/adding-a-platform.md b/wiki/adding-a-platform.md index d564b04c..0b14cab5 100644 --- a/wiki/adding-a-platform.md +++ b/wiki/adding-a-platform.md @@ -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 diff --git a/wiki/installer.md b/wiki/installer.md index 3b299712..3d74d79e 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -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 | diff --git a/wiki/profiling.md b/wiki/profiling.md index 921a48e6..d2d6c6ae 100644 --- a/wiki/profiling.md +++ b/wiki/profiling.md @@ -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 `/`, 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 `/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 | diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index 2a72186e..d3b4fe96 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -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` |