From c2e3d643439a85db17a12adaf73c72b1245f234e Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:39:28 +0200 Subject: [PATCH] fix: keep only the system's entries in --system A multi-system profile shipped every entry: the Lynx system pack carried 34 PlayStation, Saturn and Apple II files from mednafen. The entry's own system decides, as the platform pack and the installer already do. --- scripts/common.py | 12 ++++++++ scripts/generate_pack.py | 17 +++++++++-- scripts/verify.py | 8 ++++- tests/test_system_selection.py | 54 ++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index 909af157..f798481b 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -1628,6 +1628,18 @@ def profiles_for_systems( return matching +def entry_serves_systems(entry: dict, profile: dict, system_ids: list[str]) -> bool: + """Whether a profile entry belongs to one of the systems, spelled either way. + + The entry's own `system:` decides; one without it serves every system + of its profile, the rule a platform pack applies to core extras. + """ + own = entry.get("system") + systems = [own] if own else profile.get("systems", []) + wanted = {_norm_system_id(sid) for sid in system_ids} + return bool(wanted & {_norm_system_id(str(sid)) for sid in systems}) + + def select_emulator_profiles( profile_names: list[str], all_profiles: dict, diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index bf6920a1..8520d5e0 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -62,6 +62,7 @@ from common import ( MANUFACTURER_PREFIXES, require_yaml, ProfileSelectionError, + entry_serves_systems, sanitize_pack_path, select_emulator_profiles, system_core_reach, @@ -1374,12 +1375,16 @@ def generate_emulator_pack( offline: bool | None = None, platforms_dir: str | None = None, pack_name: str | None = None, + system_ids: list[str] | None = None, + final_dir: str | None = None, ) -> str | None: """Generate a ZIP pack for specific emulator profiles. `pack_name` is the caller's own file name: the system pack names its archive after the system, and joining 23 SNES emulator names went past - the 255 bytes a file name may hold. + the 255 bytes a file name may hold. `system_ids` keeps only the entries + of those systems, as a platform pack narrows core extras. `final_dir` + is where the caller moves the archive, the path the report names. """ all_profiles = load_emulator_profiles(emulators_dir, skip_aliases=False) if zip_contents is None: @@ -1449,6 +1454,10 @@ def generate_emulator_pack( db, standalone, ) + if system_ids: + files = [ + fe for fe in files if entry_serves_systems(fe, profile, system_ids) + ] if not files: print(f" No files needed for {profile.get('emulator', emu_name)}") @@ -1694,7 +1703,6 @@ def generate_emulator_pack( os.unlink(zip_path) # Report - " + ".join(p.get("emulator", n) for n, p in selected) missing_count = len(missing_files) ok_count = total_files parts = [f"{ok_count} files packed"] @@ -1706,7 +1714,8 @@ def generate_emulator_pack( parts.append(f"{len(unsourceable_files)} unsourceable") if instruction_files: parts.append(f"{len(instruction_files)} to provide") - print(f" {zip_path}: {', '.join(parts)}") + shown = os.path.join(final_dir, zip_name) if final_dir else zip_path + print(f" {shown}: {', '.join(parts)}") for name in missing_files: print(f" MISSING: {name}") for name in unsourceable_files: @@ -1801,6 +1810,8 @@ def generate_system_pack( offline=offline, platforms_dir=platforms_dir, pack_name=final_name, + system_ids=system_ids, + final_dir=output_dir, ) if not result: return None diff --git a/scripts/verify.py b/scripts/verify.py index dcd93e9a..ee67b23b 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -69,6 +69,7 @@ from common import ( platform_layouts, runs_standalone, standalone_set as platform_standalone_set, + entry_serves_systems, sanitize_pack_path, select_emulator_profiles, ) @@ -1547,11 +1548,13 @@ def verify_emulator( standalone: bool = False, regions: list[str] | None = None, platforms_dir: str = _REPO_PLATFORMS, + system_ids: list[str] | None = None, ) -> dict: """Verify files for specific emulator profiles. A region priority list narrows the report the same way a pack built with the same list would be narrowed, through the emulator pack's own drop set. + `system_ids` keeps the entries of those systems, as the system pack does. """ from packpaths import ( _archive_destination, @@ -1625,6 +1628,8 @@ def verify_emulator( db, standalone, ) + if system_ids: + files = [fe for fe in files if entry_serves_systems(fe, profile, system_ids)] if region_drops: # Loose files only, as the emulator pack: an archive ships whole, # and dropping kronos' regional stvbios.zip members rebuilt the @@ -1996,7 +2001,8 @@ def verify_system( sys.exit(1) return verify_emulator( - matching, emulators_dir, db, standalone, regions=regions, platforms_dir=platforms_dir + matching, emulators_dir, db, standalone, regions=regions, + platforms_dir=platforms_dir, system_ids=system_ids, ) diff --git a/tests/test_system_selection.py b/tests/test_system_selection.py index 6fdc8ef7..889bec18 100644 --- a/tests/test_system_selection.py +++ b/tests/test_system_selection.py @@ -369,5 +369,59 @@ class CoverageWithoutProfilesMatchesTheCli(unittest.TestCase): self.assertEqual(discrepancies(bare), discrepancies(full)) +class ASystemPackHoldsThatSystem(unittest.TestCase): + """--system atari-lynx without --platform shipped 36 files: every entry + of mednafen.yml, PlayStation, Saturn and Apple II included, though each + declares its system. A platform pack narrows core extras per entry.""" + + def test_pack_and_report_keep_the_entries_of_the_system(self): + import hashlib + import io + import shutil + import zipfile + from contextlib import redirect_stdout + + import yaml + from verify import verify_system + + common._emulator_profiles_cache.clear() + (REPO_ROOT / "tmp").mkdir(exist_ok=True) + root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + previous = os.getcwd() + os.chdir(root) + self.addCleanup(os.chdir, previous) + db = {"files": {}, "indexes": {"by_md5": {}, "by_name": {}, "by_crc32": {}, + "by_path_suffix": {}}} + for name in ("lynx.img", "psx.bin", "shared.dat"): + data = name.encode() * 4 + rel = f"bios/Maker/Console/{name}" + Path(rel).parent.mkdir(parents=True, exist_ok=True) + Path(rel).write_bytes(data) + sha1 = hashlib.sha1(data).hexdigest() + db["files"][sha1] = {"path": rel, "name": name, "size": len(data), + "md5": hashlib.md5(data).hexdigest(), "crc32": ""} + db["indexes"]["by_name"][name] = [sha1] + Path("emulators").mkdir() + Path("emulators/multi.yml").write_text(yaml.safe_dump({ + "emulator": "Multi", "type": "libretro", "cores": ["multi"], + "systems": ["atari-lynx", "sony-playstation"], + "files": [ + {"name": "lynx.img", "system": "atari-lynx", "required": True}, + {"name": "psx.bin", "system": "sony-playstation", "required": True}, + {"name": "shared.dat", "required": False}, + ], + })) + report = verify_system(["atari-lynx"], "emulators", db) + with redirect_stdout(io.StringIO()): + path = builder.generate_system_pack( + ["atari-lynx"], "emulators", db, "bios", "out", offline=True, + ) + with zipfile.ZipFile(path) as zf: + packed = sorted(zf.namelist()) + self.assertEqual(packed, ["lynx.img", "shared.dat"]) + self.assertEqual(report["total_files"], 2) + + if __name__ == "__main__": unittest.main()