diff --git a/scripts/common.py b/scripts/common.py index 32405737..79b0e00d 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -783,7 +783,7 @@ def resolve_local_file( # MAME clone fallback: if a file was deduped, resolve via canonical if _depth < 3 and not has_strong_hash: - clone_map = _get_mame_clone_map() + clone_map = get_mame_clone_map() canonical = clone_map.get(name) if canonical and canonical != name: canonical_entry = {"name": canonical} @@ -835,7 +835,10 @@ def resolve_local_file( return "data_dir_hash_exact" data_dir_mismatch: str | None = None - if data_dir_registry: + # Without a hash the cache walk matches on filename alone, which is the + # step an unsourceable entry has to skip: hiscore.dat names one file per + # driver set, so FBNeo's copy would answer for MAME's. + if data_dir_registry and (has_strong_hash or not unsourceable): for _dd_key, dd_entry in data_dir_registry.items(): cache_dir = dd_entry.get("local_cache", "") if not cache_dir or not os.path.isdir(cache_dir): @@ -890,7 +893,7 @@ def resolve_local_file( _mame_clone_map_cache: dict[str, str] | None = None -def _get_mame_clone_map() -> dict[str, str]: +def get_mame_clone_map() -> dict[str, str]: """Load and cache the MAME clone map (clone_name -> canonical_name).""" global _mame_clone_map_cache if _mame_clone_map_cache is not None: diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index 379525b1..a9848ac6 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -20,6 +20,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) from common import ( + get_mame_clone_map, list_registered_platforms, load_database, load_emulator_profiles, @@ -157,6 +158,16 @@ def _resolve_source( # bios/ via by_path_suffix (regional variants) if by_path_suffix and fname in by_path_suffix: return "bios" + # bios/ under the canonical MAME set name, as resolve_local_file does: + # a renamed archive is held once, under the name the dedup kept. + canonical = get_mame_clone_map().get(fname) + if canonical and canonical != fname: + if canonical in by_name and _name_hit(canonical): + return "bios" + if canonical.lower() in by_name_lower and _name_hit( + by_name_lower[canonical.lower()] + ): + return "bios" # data/ supplemental index if data_names: if fname in data_names or key in data_names: diff --git a/tests/test_e2e.py b/tests/test_e2e.py index 66ea49b9..6fdf22ad 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -3114,6 +3114,33 @@ class TestE2E(unittest.TestCase): self.assertEqual(os.path.basename(path), "data_only.bin") self.assertEqual(status, "data_dir") + def test_167b_unsourceable_skips_data_dir_name_match(self): + """The cache walk matches on filename, so unsourceable has to skip it.""" + data_dir = os.path.join(self.root, "data", "other-core") + os.makedirs(data_dir, exist_ok=True) + with open(os.path.join(data_dir, "shared_name.dat"), "wb") as f: + f.write(b"ANOTHER CORE DEFINITIONS") + + registry = {"other-core": {"local_cache": data_dir}} + + fe = {"name": "shared_name.dat", "unsourceable": "ships inside the package"} + path, status = resolve_local_file(fe, self.db, data_dir_registry=registry) + self.assertIsNone(path) + self.assertEqual(status, "not_found") + + # A declared hash is content proof, so the cache still answers for it. + import hashlib + + digest = hashlib.sha1(b"ANOTHER CORE DEFINITIONS").hexdigest() + fe = { + "name": "shared_name.dat", + "sha1": digest, + "unsourceable": "ships inside the package", + } + path, status = resolve_local_file(fe, self.db, data_dir_registry=registry) + self.assertIsNotNone(path) + self.assertEqual(status, "data_dir_hash_exact") + def test_168_generate_truth_basic(self): """generate_platform_truth resolves cores and builds system truth.""" import yaml as _yaml @@ -5991,6 +6018,19 @@ struct BurnDriver BurnDrvneogeo = { entry = {"name": "chip.rom", "aliases": ["OTHER.rom"], "crc32": "deadbeef"} self.assertEqual(self._gap_source(entry, db), "missing") + def test_gap_analysis_resolves_renamed_mame_set(self): + """A renamed set is held under the name dedup kept, as verify reads it.""" + import cross_reference + + db = self._gap_db("v4bios.zip", "a" * 40, "465c4e1c") + original = cross_reference.get_mame_clone_map + cross_reference.get_mame_clone_map = lambda: {"bctvidbs.zip": "v4bios.zip"} + try: + entry = {"name": "bctvidbs.zip", "required": True} + self.assertEqual(self._gap_source(entry, db), "bios") + finally: + cross_reference.get_mame_clone_map = original + if __name__ == "__main__": unittest.main()