diff --git a/install.ps1 b/install.ps1 index bd38670a..93c025ba 100644 --- a/install.ps1 +++ b/install.ps1 @@ -8,7 +8,7 @@ param( $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -$defaultInstallSha256 = "a1bae5feab3e85224526eaab7ed99ae0d711081c925aa520d3768532cd11a797" +$defaultInstallSha256 = "edc57fe82a964b271bdae9d20985421f7a6ba88859f9c6926f323c909ec2d651" $maximumInstallerBytes = 2MB $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $temporary = $null diff --git a/install.py b/install.py index 14e9c173..f88590db 100644 --- a/install.py +++ b/install.py @@ -130,6 +130,10 @@ _REGION_PARENT = { REGIONS = frozenset({WORLD_REGION} | set(REGION_TREE) | set(_REGION_PARENT)) MAX_SELECTION_TAGS = 1000 SELECTION_FIELDS = ("systems", "regions", "region_groups", "region_system_groups") +# The files cores load for systems the platform does not list (game engines, +# computers): no system of the platform owns them, and choosing systems +# leaves them out unless this name is chosen too. +OTHER_SYSTEMS = "other" # Platforms with a manifest in install/. Manifest URLs are case sensitive, # so user input is normalized against this list before any fetch. @@ -1153,11 +1157,12 @@ def region_drops( def entry_systems(entry: dict) -> list[str]: - """The systems a file serves: a list on files, one name on omissions.""" - systems = entry.get("systems") - if systems: - return list(systems) - return [entry["system"]] if entry.get("system") else [] + """The platform systems a file or an omission belongs to. + + An omission's `system` is spelled the way its profile spells it; only + `systems` names the platform's own, the ones --system accepts. + """ + return list(entry.get("systems") or []) def narrow( @@ -1183,11 +1188,18 @@ def narrow( omitted = [o for o in omitted if o["dest"] not in dropped] if systems: wanted = set(systems) - files = [f for f in files if wanted & set(entry_systems(f))] - omitted = [o for o in omitted if wanted & set(entry_systems(o))] + files = [f for f in files if _chosen(f, wanted)] + omitted = [o for o in omitted if _chosen(o, wanted)] return files, omitted +def _chosen(entry: dict, wanted: set[str]) -> bool: + systems = entry_systems(entry) + if not systems: + return OTHER_SYSTEMS in wanted + return bool(wanted & set(systems)) + + def records_selection(files: list[dict]) -> bool: """Whether a manifest carries what narrowing reads; older ones do not.""" return any("systems" in entry for entry in files) @@ -1196,13 +1208,16 @@ def records_selection(files: list[dict]) -> bool: def available_choices(files: list[dict], omitted: list[dict]) -> dict[str, list[str]]: """The systems, cores and regions a manifest lets one narrow to.""" systems = {s for e in files + omitted for s in entry_systems(e)} + ordered = sorted(systems) + if any(not entry_systems(f) for f in files): + ordered.append(OTHER_SYSTEMS) cores = {c for f in files for c in (f.get("cores") or [])} regions = { r for e in files + omitted for r in (e.get("regions") or []) if r != WORLD_REGION } return { - "systems": sorted(systems), + "systems": ordered, "cores": sorted(cores), "regions": sorted(regions), } @@ -1812,8 +1827,11 @@ def _prompt_custom_selection( break choices = available_choices(files, omitted) + if OTHER_SYSTEMS in choices["systems"]: + print(f"\n'{OTHER_SYSTEMS}' holds what cores load for systems this platform") + print("does not list, such as game engines and extra computers.") systems = _pick("Systems:", [ - (name, [f for f in files if name in entry_systems(f)]) + (name, [f for f in files if _chosen(f, {name})]) for name in choices["systems"] ]) kept, kept_omitted = narrow(files, omitted, systems, [], []) @@ -1866,7 +1884,7 @@ def _print_choices( width = min(max(len(name) for name in names), 40) for name in names: held = ( - [f for f in files if name in entry_systems(f)] + [f for f in files if _chosen(f, {name})] if systems else [f for f in files if name in (f.get("cores") or [])] ) diff --git a/install.sh b/install.sh index ea8b993b..231ad57a 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="a1bae5feab3e85224526eaab7ed99ae0d711081c925aa520d3768532cd11a797" +DEFAULT_INSTALL_SHA256="edc57fe82a964b271bdae9d20985421f7a6ba88859f9c6926f323c909ec2d651" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install_selection.py b/tests/test_install_selection.py index eea4439a..4fb054ea 100644 --- a/tests/test_install_selection.py +++ b/tests/test_install_selection.py @@ -106,6 +106,7 @@ class Narrowing(unittest.TestCase): ] OMITTED = [ {"dest": "scph5502.bin", "name": "scph5502.bin", "system": "sony-playstation", + "systems": ["sony-playstation"], "required": True, "reason": "not_found", "cores": None, "regions": ["europe"], "region_system_groups": ["sony-playstation"]}, ] @@ -143,6 +144,19 @@ class Narrowing(unittest.TestCase): self.assertIn("scph5500.bin", files) self.assertIn("scph5501.bin", files) + def test_other_holds_what_no_platform_system_owns(self): + files = [ + {"dest": "pak0.pak", "size": 1, "cores": ["tyrquake"]}, + {"dest": "gba_bios.bin", "size": 1, "cores": None, + "systems": ["nintendo-gba"]}, + ] + self.assertEqual(install.available_choices(files, [])["systems"], + ["nintendo-gba", "other"]) + kept, _ = install.narrow(files, [], ["nintendo-gba"], [], []) + self.assertEqual([f["dest"] for f in kept], ["gba_bios.bin"]) + kept, _ = install.narrow(files, [], ["other"], [], []) + self.assertEqual([f["dest"] for f in kept], ["pak0.pak"]) + def test_a_core_variant_competes_across_systems(self): """PicoDrive files its US Mega CD BIOS under sega-segacd and the EU one under sega-mega-cd, one variant group. The pack groups every core diff --git a/wiki/installer.md b/wiki/installer.md index 25549109..0f824c1f 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -118,7 +118,10 @@ Retroarch: 5861 files, 6.0 GB. `c` walks through three numbered lists, each answered with numbers such as `1,4,7-9`, or Enter to keep everything on that axis. -- **Systems.** The consoles and computers the platform declares. +- **Systems.** The consoles and computers the platform declares. A last + entry, `other`, holds what cores load for systems the platform does not + list, such as game engines and extra computers. Choosing systems leaves it + out unless it is chosen too, as `generate_pack.py --system` does. - **Cores.** The platform's own BIOS list for the chosen systems is always kept; this narrows only the extra files each emulator core loads beyond it. Asked only when the chosen systems have such files. @@ -164,7 +167,7 @@ what the options name, everything by default. | `--platform NAME` | Install for this platform instead of the detected one. Unknown names are refused with the available list | | `--dest PATH` | Destination directory, overriding detection. With `--dest` alone the file list is RetroArch's | | `--target NAME` | Keep only the files the cores of that hardware target need. An unknown target is refused rather than ignored, since carrying on would install everything | -| `--system NAMES` | Install only these systems. Comma-separated or repeated; `--list-systems` prints the names | +| `--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 | | `--region LIST` | Preferred regions, best first (`us,eu,jp`): keep one BIOS per region where a system has several. Accepts the names and aliases of `generate_pack.py --region` | | `--list-systems` | Print a platform's systems with the files and size each holds |