From 2dcb49fa4cd83a13911084982701db509f8fb7c5 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 03:52:47 +0200 Subject: [PATCH] fix: keep a held file in emulator region packs Emulator mode reduced region groups without knowing which files the collection holds, so a requested-region image nobody had collected withdrew the held one and left the slot empty. Platform mode already kept it; both emulator readers now pass the database. --- scripts/generate_pack.py | 2 +- scripts/packextras.py | 13 ++++++++++++- scripts/verify.py | 2 +- tests/test_region.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 41d7da75..b611b652 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1351,7 +1351,7 @@ def generate_emulator_pack( os.makedirs(output_dir, exist_ok=True) region_drops: set[str] = ( - emulator_region_drops(selected, standalone, regions, required_only) + emulator_region_drops(selected, standalone, regions, required_only, db) if regions else set() ) diff --git a/scripts/packextras.py b/scripts/packextras.py index cdb4df03..99fd4af6 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -1028,6 +1028,7 @@ def emulator_region_drops( standalone: bool, regions: list[str], required_only: bool = False, + db: dict | None = None, ) -> set[str]: """Destinations an emulator-mode `--region` withdraws. @@ -1035,6 +1036,11 @@ def emulator_region_drops( a multi-system core such as O2EM keeps a fallback BIOS for every system instead of letting one matching system empty another. The emulator pack and `verify --emulator` both read this, keyed by pack destination. + + With `db`, a requested-region file the collection lacks never displaces + a held one, as in platform mode: a held scph5501.bin was withdrawn for a + Japanese image nobody had collected, and the pack shipped no PlayStation + BIOS at all. """ import region as region_mod from packpaths import _resolve_destination @@ -1042,6 +1048,7 @@ def emulator_region_drops( region_index = region_mod.build_region_index(dict(selected)) region_groups: dict[str, list[tuple[str, str]]] = {} + held: set[str] | None = set() if db is not None else None for emu_name, profile in sorted(selected): # Decided over what the pack keeps, as on the platform side: an # optional winner removed afterwards left its slot empty. @@ -1053,7 +1060,11 @@ def emulator_region_drops( region_groups.setdefault(group_id, []).append( (dest, fe.get("name", "")) ) - return region_mod.resolve_region_drops(region_groups, region_index, regions) + if held is not None and resolve_local_file( + dict(fe, source_profile=emu_name), db, dest_hint=dest + )[0]: + held.add(dest) + return region_mod.resolve_region_drops(region_groups, region_index, regions, held) def _emulator_region_group(emu_name: str, profile: dict, file_entry: dict) -> str: diff --git a/scripts/verify.py b/scripts/verify.py index 6f9f682a..cd406e7e 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -1519,7 +1519,7 @@ def verify_emulator( if regions: from packextras import emulator_region_drops - region_drops = emulator_region_drops(selected, standalone, regions) + region_drops = emulator_region_drops(selected, standalone, regions, db=db) # The emulator pack walks profiles in name order and keeps the first # file at a path, folding case: a later profile's different file there diff --git a/tests/test_region.py b/tests/test_region.py index 6d869170..c77d4983 100644 --- a/tests/test_region.py +++ b/tests/test_region.py @@ -877,5 +877,38 @@ class FontRomsCarryNoTerritory(unittest.TestCase): ] self.assertEqual(tagged, []) +class EmulatorModeKeepsAHeldFile(unittest.TestCase): + """Platform mode never lets a missing file displace a held one; emulator + mode did. A held scph5501.bin (north-america) was withdrawn under + --region jp for a Japanese image the collection lacks, and the pack + shipped no PlayStation BIOS.""" + + def test_a_missing_match_does_not_withdraw_the_held_file(self): + import hashlib # noqa: PLC0415 + import shutil # noqa: PLC0415 + import tempfile # noqa: PLC0415 + + root = Path(tempfile.mkdtemp(dir=Path(__file__).resolve().parents[1] / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + held = root / "scph5501.bin" + held.write_bytes(b"us bios") + sha1 = hashlib.sha1(b"us bios").hexdigest() + db = {"files": {sha1: {"path": str(held), "name": held.name, "sha1": sha1}}, + "indexes": {"by_name": {held.name: [sha1]}, "by_md5": {}, + "by_crc32": {}, "by_path_suffix": {}}} + profile = {"systems": ["sony-playstation"], "files": [ + {"name": "scph5501.bin", "region": ["north-america"]}, + {"name": "scph5500.bin", "region": ["japan"]}, + ]} + selected = [("psx", profile)] + self.assertEqual( + emulator_region_drops(selected, False, ["japan"], db=db), set() + ) + # Without the collection to ask, the old reading stands. + self.assertEqual( + emulator_region_drops(selected, False, ["japan"]), {"scph5501.bin"} + ) + + if __name__ == "__main__": unittest.main()