diff --git a/scripts/packextras.py b/scripts/packextras.py index 544958f47..739be2718 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -1296,11 +1296,12 @@ def emulator_region_drops( """ import region as region_mod from packpaths import _resolve_destination - from validation import filter_files_by_mode + from validation import _build_validation_index, filter_files_by_mode 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 + validation_index = _build_validation_index(dict(selected)) if db is not None else {} 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. @@ -1312,13 +1313,35 @@ def emulator_region_drops( region_groups.setdefault(group_id, []).append( (dest, fe.get("name", "")) ) - if held is not None and resolve_local_file( - dict(fe, source_profile=emu_name), db, dest_hint=dest - )[0]: + if held is not None and _emulator_ships( + fe, emu_name, profile, db, dest, validation_index + ): held.add(dest) return region_mod.resolve_region_drops(region_groups, region_index, regions, held) +def _emulator_ships( + file_entry: dict, emu_name: str, profile: dict, db: dict, dest: str, + validation_index: dict, +) -> bool: + """Whether the emulator pack ships this entry: held, and not refused by + the core's own check, the test _ships applies on the platform side. + """ + from validation import validated_choice, with_archive_members + + local, _status = resolve_local_file( + {**with_archive_members(file_entry, profile.get("files", [])), + "source_profile": emu_name}, + db, dest_hint=dest, + ) + if not local: + return False + _chosen, disagreement = validated_choice( + file_entry, local, db, validation_index, "bios", None, emulators={emu_name}, + ) + return disagreement is None + + def _emulator_region_group( emu_name: str, profile: dict, file_entry: dict, dest: str ) -> str: diff --git a/tests/test_region.py b/tests/test_region.py index cf0771fff..cd3f79412 100644 --- a/tests/test_region.py +++ b/tests/test_region.py @@ -1113,5 +1113,63 @@ class NoMarketStopsAtAustralia(unittest.TestCase): self.assertEqual(tagged, []) +class ARefusedRegionalFileHoldsNoSlot(unittest.TestCase): + """rg_us.bin is held but its md5 contradicts the one the core checks, so + the emulator pack refuses it. Counted held, it won the region and the + Japanese BIOS was withdrawn: --emulator --region us shipped no BIOS for + the system, and verify, reading the same drops, did not see it.""" + + def test_the_fallback_stays_in_pack_and_report(self): + import hashlib + import shutil + import tempfile + import zipfile + + import yaml + + from generate_pack import generate_emulator_pack + from verify import verify_emulator + + common._emulator_profiles_cache.clear() + repo = Path(__file__).resolve().parents[1] + (repo / "tmp").mkdir(exist_ok=True) + root = Path(tempfile.mkdtemp(dir=repo / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + previous = os.getcwd() + os.chdir(root) + self.addCleanup(os.chdir, previous) + files, by_name = {}, {} + for name, data in (("rg_us.bin", b"NOT THE US DUMP"), ("rg_jp.bin", b"JP DUMP")): + path = Path("bios") / name + path.parent.mkdir(exist_ok=True) + path.write_bytes(data) + sha1 = hashlib.sha1(data).hexdigest() + files[sha1] = {"path": str(path), "name": name, "size": len(data), + "md5": hashlib.md5(data).hexdigest()} + by_name[name] = [sha1] + db = {"files": files, "indexes": {"by_name": by_name, "by_md5": {}, + "by_crc32": {}, "by_path_suffix": {}}} + Path("emulators").mkdir() + Path("emulators/rgemu.yml").write_text(yaml.safe_dump({ + "emulator": "RgEmu", "type": "libretro", "systems": ["sys"], + "files": [ + {"name": "rg_us.bin", "region": ["north-america"], "required": True, + "md5": hashlib.md5(b"THE US DUMP").hexdigest(), "validation": ["md5"]}, + {"name": "rg_jp.bin", "region": ["japan"], "required": True}, + ], + })) + with contextlib.redirect_stdout(io.StringIO()): + zip_path = generate_emulator_pack( + ["rgemu"], "emulators", db, "bios", "out", regions=["north-america"], + offline=True, platforms_dir="platforms", + ) + with zipfile.ZipFile(zip_path) as zf: + self.assertIn("rg_jp.bin", zf.namelist()) + report = verify_emulator( + ["rgemu"], "emulators", db, regions=["north-america"], platforms_dir="platforms", + ) + self.assertIn("rg_jp.bin", report["pack_destinations"]) + + if __name__ == "__main__": unittest.main()