feat: install the fewest files for a card's games

--for-games --required-only on a dArkOS card from a computer kept every
file of the list: --dest skipped the card's detection, so its rom
folders were never found. The games part beside the BIOS folder now
names them. The manifest marks a data tree every core runs without
(optional), and --required-only leaves it out: one GBA and one SNES game
took FBNeo's cheats and samples, 3,472 files no game opened.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 19:58:41 +01:00
1 parent adce8dd0d4
commit 072380c268
24 files changed
+184 -78

No files matched your search

+2
View File
@@ -51,10 +51,12 @@ data_directories:
- ref: fbneo-cheats
unread_on: [romm]
destination: fbneo/cheats
required: false
source_ref: "source src/burner/conc.cpp:167"
- ref: fbneo-samples
unread_on: [romm]
destination: fbneo
required: false
source_ref: "source src/burn/snd/samples.cpp:21-35"
notes: |
+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "57e3cb6739a701181908773e603f17d2b10e1b0e2a5cde5cc4d5c97426da5dd4"
$defaultInstallSha256 = "3a78bf2e5a910d0f5c21bc2dc490aa600fc3273c56ae01e9ce5061d9b3578f07"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+25 -8
View File
@@ -3724,6 +3724,8 @@ def _check_data_directory(entry: object, where: str) -> None:
values = entry.get(field, [])
if not isinstance(values, list) or not all(isinstance(v, str) for v in values):
raise ValueError(f"{where}.{field} is invalid")
if entry.get("optional", True) is not True:
raise ValueError(f"{where}.optional is invalid")
files = entry.get("files")
if not isinstance(files, dict) or not files or len(files) > MAX_MANIFEST_FILES:
raise ValueError(f"{where}.files is invalid")
@@ -7393,11 +7395,16 @@ def _install_data_member(
scratch.unlink(missing_ok=True)
def select_data_directories(manifest: dict, files: list[dict], narrowed: bool) -> list[dict]:
"""The data trees this install keeps: all, or those of a kept system."""
entries = manifest.get("data_directories") or []
def select_data_directories(manifest: dict, files: list[dict], narrowed: bool,
required_only: bool = False) -> list[dict]:
"""The data trees this install keeps: all, or those of a kept system.
--required-only leaves out a tree every core reading it runs without
(`optional`): a GBA and a SNES game took FBNeo's cheats and samples,
3,472 files no game opened."""
entries = [e for e in manifest.get("data_directories") or []
if not (required_only and e.get("optional"))]
if not narrowed:
return list(entries)
return entries
kept = {system for f in files for system in entry_systems(f)}
return [e for e in entries if kept & set(e.get("systems") or [])]
@@ -8824,7 +8831,8 @@ def _prompt_custom_selection(
if bios_path is not None and not dry_run and current != INSTALLED_ONLY:
kept = menu_choice(files, omitted, installed, current, False)[0]
kept_trees = select_data_directories({"data_directories": trees}, kept,
narrowed or current is not None)
narrowed or current is not None,
isinstance(current, GamesChoice) and current.required)
if kept and all_in_place(kept, kept_trees, bios_path):
# Read before the menu, as the screen reads before it asks: a
# rerun was offered an install that installs nothing, then told
@@ -8953,7 +8961,8 @@ def _print_menu_head(
# The trees of the systems kept: 2636 data files of blueMSX, Dolphin
# and PPSSPP were counted for every --system tried.
kept_trees = select_data_directories({"data_directories": trees}, shown,
narrowed or current is not None)
narrowed or current is not None,
isinstance(current, GamesChoice) and current.required)
data = sum(len(entry.get("files") or {}) for entry in kept_trees)
data_size = sum(meta.get("size", 0) for entry in kept_trees
for meta in (entry.get("files") or {}).values())
@@ -9764,6 +9773,12 @@ def games_setups(index: dict, plat: str, bios_path: Path, os_type: str = "") ->
if not named:
above = card_root_above(bios_path)
named = above[0] if above else ""
if not named:
# --dest on a handheld's games part from a computer (ArkOS's
# EASYROMS/bios): detection never ran there, and the card's own rom
# folders sit beside the BIOS folder.
part = games_part(bios_path.parent)
named = part.name if part else ""
key = _setup_key(named) if named else ""
found = [name for name in sorted(setups) if key and (name == key or name.startswith(key + "-"))]
if not found and plat in setups:
@@ -15671,7 +15686,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
!= len(manifest.get("files", [])) + len(variants if case_kept else [])
or games_applied
)
data_dirs = select_data_directories(manifest, files, narrowed)
data_dirs = select_data_directories(manifest, files, narrowed,
bool(args.required_only))
# The kinds as the plan and the menu head say them (parts_words),
# the data files of the folders kept among them: this head said
# BIOS files and game data beside a plan of three kinds, neither
@@ -21812,7 +21828,8 @@ def _plan_data(state: TuiState, check: dict) -> tuple[int, int | None]:
if not _tui_estimating(state):
data = check.get("data_missing", 0)
return data, check.get("data_bytes") if data else None
trees = select_data_directories(state.manifest or {}, _choice_entries(state), True)
required = any(flag == "--required-only" for flag, _value in tui_flags(state))
trees = select_data_directories(state.manifest or {}, _choice_entries(state), True, required)
sizes = [meta.get("size", 0) for entry in trees for meta in (entry.get("files") or {}).values()]
return len(sizes), sum(sizes)
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="57e3cb6739a701181908773e603f17d2b10e1b0e2a5cde5cc4d5c97426da5dd4"
DEFAULT_INSTALL_SHA256="3a78bf2e5a910d0f5c21bc2dc490aa600fc3273c56ae01e9ce5061d9b3578f07"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "amberelec",
"display_name": "AmberELEC",
"version": "prerelease-20261019_1835",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "",
"detect": [
{
@@ -77381,7 +77381,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -77615,7 +77616,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -77988,7 +77990,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
}
]
}
+5 -3
View File
@@ -6,7 +6,7 @@
"platform": "batocera",
"display_name": "Batocera",
"version": "43.1",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "bios",
"detect": [
{
@@ -225291,7 +225291,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -225525,7 +225526,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
}
],
"save_directory": {
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "emudeck",
"display_name": "EmuDeck",
"version": "2.7EA",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "bios",
"detect": [
{
@@ -123400,7 +123400,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -123634,7 +123635,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -124007,7 +124009,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
},
{
"ref": "lrps2",
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "emuelec",
"display_name": "EmuELEC",
"version": "4.8",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "",
"detect": [
{
@@ -83639,7 +83639,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -83873,7 +83874,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -84246,7 +84248,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
}
]
}
+5 -3
View File
@@ -6,7 +6,7 @@
"platform": "knulli",
"display_name": "KNULLI",
"version": "20260511",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "bios",
"detect": [
{
@@ -157933,7 +157933,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -158167,7 +158168,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "lrps2",
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "lakka",
"display_name": "Lakka",
"version": "v6.1",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "system",
"detect": [
{
@@ -130677,7 +130677,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -130911,7 +130912,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -131284,7 +131286,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
},
{
"ref": "yaps2-resources",
+9 -5
View File
@@ -6,7 +6,7 @@
"platform": "myminui",
"display_name": "MyMinUI",
"version": "2026-08-09",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "",
"detect": [
{
@@ -18958,7 +18958,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-cheats",
@@ -32688,7 +32689,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -32922,7 +32924,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -33156,7 +33159,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
}
]
}
+5 -3
View File
@@ -6,7 +6,7 @@
"platform": "nextui",
"display_name": "NextUI",
"version": "2026-10-20",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "",
"detect": [
{
@@ -19366,7 +19366,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -19600,7 +19601,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
}
]
}
+5 -3
View File
@@ -6,7 +6,7 @@
"platform": "recalbox",
"display_name": "Recalbox",
"version": "10.1.1",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "bios",
"detect": [
{
@@ -62834,7 +62834,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -63068,7 +63069,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
}
],
"save_directory": {
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "retroarch",
"display_name": "RetroArch",
"version": "v1.22.2",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "system",
"detect": [
{
@@ -140789,7 +140789,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -141023,7 +141024,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -141396,7 +141398,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
},
{
"ref": "ppsspp-assets",
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "retrobat",
"display_name": "RetroBat",
"version": "8.3.1",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "bios",
"detect": [
{
@@ -89076,7 +89076,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -89310,7 +89311,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -89683,7 +89685,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
}
],
"save_directory": {
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "retrodeck",
"display_name": "RetroDECK",
"version": "0.10.10b",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "",
"detect": [
{
@@ -153141,7 +153141,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -153375,7 +153376,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -153748,7 +153750,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
},
{
"ref": "yaps2-resources",
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "retropie",
"display_name": "RetroPie",
"version": "4.8.12",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "BIOS",
"detect": [
{
@@ -146474,7 +146474,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -146708,7 +146709,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -147081,7 +147083,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
},
{
"ref": "yaps2-resources",
+7 -4
View File
@@ -6,7 +6,7 @@
"platform": "rocknix",
"display_name": "ROCKNIX",
"version": "20261001",
"generated": "2026-11-05T23:59:37Z",
"generated": "2026-11-06T07:21:27Z",
"base_destination": "bios",
"detect": [
{
@@ -109453,7 +109453,8 @@
}
},
"strip_components": 1,
"archive_size": 5083767
"archive_size": 5083767,
"optional": true
},
{
"ref": "fbneo-samples",
@@ -109687,7 +109688,8 @@
}
},
"strip_components": 0,
"archive_size": 53006540
"archive_size": 53006540,
"optional": true
},
{
"ref": "freeintv-overlays",
@@ -110060,7 +110062,8 @@
}
},
"strip_components": 1,
"archive_size": 21311170
"archive_size": 21311170,
"optional": true
},
{
"ref": "lrps2",
+5
View File
@@ -452,6 +452,11 @@
},
"description": "Platform systems whose cores read the tree."
},
"optional": {
"type": "boolean",
"const": true,
"description": "Every core reading the tree runs without it (required: false in its profile): --required-only leaves it out."
},
"files": {
"type": "object",
"additionalProperties": {
+21 -8
View File
@@ -518,7 +518,8 @@ def _data_directory_members(
gets it from the tree's archive.
``refs_out``, when given, maps each yielded destination to
(registry key, system ids, directory destination), for the manifest.
(registry key, system ids, directory destination, whether the core
needs the tree), for the manifest.
These are not BIOS and carry no hash of their own: they are whole
trees an emulator reads, refreshed from upstream, so what ships is
@@ -532,7 +533,8 @@ def _data_directory_members(
ref_key = dd.get("ref", "")
if not ref_key or not data_registry or ref_key not in data_registry:
continue
if required_only and dd.get("required") is False:
needed = dd.get("required") is not False
if required_only and not needed:
continue
if ref_key in installed_data_refs(platform_name):
continue
@@ -567,7 +569,7 @@ def _data_directory_members(
):
del reclaim[full]
if refs_out is not None:
refs_out[full] = (ref_key, systems, dd_prefix)
refs_out[full] = (ref_key, systems, dd_prefix, needed)
yield src, full
continue
if full in seen_destinations or (
@@ -581,7 +583,7 @@ def _data_directory_members(
if case_insensitive:
seen_lower.add(full.lower())
if refs_out is not None:
refs_out[full] = (ref_key, systems, dd_prefix)
refs_out[full] = (ref_key, systems, dd_prefix, needed)
yield src, full
@@ -4910,7 +4912,7 @@ def generate_manifest(
# Phase 3: data directories. They are not files of the collection: the
# installer fetches each from its source archive and keeps the members
# whose hash the list below records, the same tree the pack carries.
data_refs: dict[str, tuple[str, tuple[str, ...], str]] = {}
data_refs: dict[str, tuple[str, tuple[str, ...], str, bool]] = {}
reclaim = {full: path for full, (path, _size) in cache_held.items()}
data_members = list(_data_directory_members(
pack_systems, data_registry, platform_name, base_dest,
@@ -5021,7 +5023,7 @@ def generate_manifest(
def _manifest_data_directories(
members: list[tuple[str, str]],
refs: dict[str, tuple[str, tuple[str, ...], str]],
refs: dict[str, tuple[str, tuple[str, ...], str, bool]],
registry: dict,
base_dest: str,
) -> list[dict]:
@@ -5034,11 +5036,14 @@ def _manifest_data_directories(
changed since is refused rather than installed. archive_size is the
size of the archive refresh_data_dirs last downloaded, what fetching
the tree costs: a tarball whose tree is 10 KB is a download of 21 MB.
``optional`` marks a tree every core reading it runs without
(``required: false``): install.py --required-only leaves it out, as the
pack built with --required-only does.
"""
grouped: dict[tuple[str, str], dict] = {}
versions: dict[str, dict[str, dict]] = {}
for src, full in members:
ref, systems, prefix = refs[full]
ref, systems, prefix, needed = refs[full]
entry = registry.get(ref, {})
key = (ref, prefix)
group = grouped.get(key)
@@ -5072,11 +5077,19 @@ def _manifest_data_directories(
size = versions[str(recorded)].get(ref, {}).get("archive_size")
if isinstance(size, int) and size > 0:
group["archive_size"] = size
if needed:
group["needed"] = True
group["systems"].extend(s for s in systems if s not in group["systems"])
rel = full[len(prefix):].lstrip("/") if prefix else full
hashes = compute_hashes(src, frozenset({"sha1"}))
group["files"][rel] = {"sha1": hashes["sha1"], "size": os.path.getsize(src)}
return [grouped[key] for key in sorted(grouped)]
trees = []
for key in sorted(grouped):
group = grouped[key]
if not group.pop("needed", False):
group["optional"] = True
trees.append(group)
return trees
def _manifest_core_index(
+5 -3
View File
@@ -6923,9 +6923,11 @@ class RequiredOnlyReachesDataDirectories(unittest.TestCase):
def test_every_tree_walk_reads_the_flag(self):
source = (ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8")
self.assertEqual(
source.count('if required_only and dd.get("required") is False:'), 2
)
# The pack's walk and the manifest's, which records the flag for
# install.py --required-only.
self.assertEqual(source.count('if required_only and dd.get("required") is False:'), 1)
self.assertIn('needed = dd.get("required") is not False\n if required_only and not needed:',
source)
self.assertIn("seen_parents, required_only=required_only,", source)
self.assertIn("data_refs, required_only=required_only,", source)
+14
View File
@@ -3880,6 +3880,20 @@ class DataDirectoriesAreInstalled(unittest.TestCase):
},
}
def test_required_only_leaves_out_an_optional_tree(self):
"""A GBA and a SNES game with --for-games --required-only took
FBNeo's cheats and samples, 3,472 files no game opened."""
cheats = {**self._entry(), "ref": "fbneo-cheats", "systems": ["snes"], "optional": True}
sys_tree = {**self._entry(), "systems": ["snes"]}
manifest = {"data_directories": [cheats, sys_tree]}
files = [{"dest": "a.rom", "systems": ["snes"]}]
self.assertEqual(install.select_data_directories(manifest, files, True), [cheats, sys_tree])
self.assertEqual(install.select_data_directories(manifest, files, True, True), [sys_tree])
self.assertEqual(install.select_data_directories(manifest, files, False, True), [sys_tree])
install._check_data_directory(cheats, "data_directories[0]")
with self.assertRaises(ValueError):
install._check_data_directory({**cheats, "optional": False}, "data_directories[0]")
def test_the_archive_is_drawn_and_counted(self):
"""82 MB of archives came with no progress line, and the run ended
on "310.5 KB downloaded", the BIOS files alone."""
+12
View File
@@ -12912,6 +12912,18 @@ class DriveIsACardByItsFiles(unittest.TestCase):
self.assertEqual([install.card_system(mount) for mount in mounts],
[None, None, None])
def test_a_dest_on_a_games_part_reads_its_rom_folders(self):
"""--dest EASYROMS/bios --for-games from a computer: detection never
ran on the card, the generic RetroArch folders were tried, and the
run kept every file of the list."""
arkos = games_part(self.root / "media" / "EASYROMS")
index = {"setups": {"arkos": {}, "retroarch": {}}}
with mock.patch.object(install, "_mount_points", return_value=frozenset()), \
mock.patch.object(install, "_drive_not_removable", return_value=False):
self.assertEqual(install.games_setups(index, "retroarch", arkos / "bios"), ["arkos"])
self.assertEqual(install.games_setups(index, "retroarch", self.root / "bios"),
["retroarch"])
def test_a_card_beside_a_data_drive_comes_first(self):
"""The ArkOS card and the data drive mounted,
the data drive sorted first and took Enter. The card comes first in
+6 -4
View File
@@ -405,7 +405,7 @@ lists record these files as omitted.
<!-- generated: dry-run -->
A RetroArch dry run into an empty folder, with the list of 2026-11-05, printed this:
A RetroArch dry run into an empty folder, with the list of 2026-11-06, printed this:
```
Fetching file index for retroarch...
@@ -435,7 +435,7 @@ the installer can fetch them.
<!-- generated: run-date -->
Each line a first install prints, and what it means. The run below is a
Batocera install into an empty folder, from the list of 2026-11-05.
Batocera install into an empty folder, from the list of 2026-11-06.
The figures change with each release.
<!-- /generated: run-date -->
@@ -531,7 +531,7 @@ would save, once that saving is over half.
<!-- generated: bulk-hint -->
RetroArch with `--system sony-playstation`, from the list of 2026-11-05:
RetroArch with `--system sony-playstation`, from the list of 2026-11-06:
```
duckstation pulls 316.5 MB of this 329.0 MB download for sony-playstation. --core pcsx1 keeps 11.0 MB of it and saves 318.0 MB (96%).
@@ -876,7 +876,9 @@ BIOS sets of its parent chain (`neogeo.zip` for `mslug.zip`).
A game that is not recognised gets every BIOS of its system, and the run names
it. So does an arcade set the core does not know. **Only what starts**
(`--required-only`) leaves out the files the cores mark optional.
(`--required-only`) leaves out the files the cores mark optional, and the data
folders every core reading them runs without (FinalBurn Neo's cheats and
samples).
The games are read without hashing whole discs, and a cache keeps what was
read, so a second run over the same card reads only new files. It is a