diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 1e051973..4a3f2386 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -182,16 +182,36 @@ def _trim_prose(chunk: str) -> str: return chunk +HASH_FIELDS = ("sha1", "md5", "crc32", "sha256", "known_hash_adler32") + + +def entry_hashes(entry: dict) -> list[str]: + """Content values an entry declares, its own and its members'. + + An archive entry carries no hash of its own: MAME and FBNeo hash the ROMs + inside the container, so `contents[]` is where its content values live. + Ignoring them leaves the archive stem as the only token, and a stem like + `esh` matches seventy lines of its own driver. + """ + tokens: list[str] = [] + contents = entry.get("contents") + members = contents if isinstance(contents, list) else [] + for source in (entry, *members): + if not isinstance(source, dict): + continue + for field in HASH_FIELDS: + val = source.get(field) + vals = val if isinstance(val, list) else [val] if val else [] + for v in vals: + v = str(v).lower().removeprefix("0x") + if v and v not in tokens: + tokens.append(v) + return tokens + + def collect_tokens(entry: dict) -> list[str]: """Values declared by one file entry: hashes, then name as fallback.""" - tokens: list[str] = [] - for field in ("sha1", "md5", "crc32", "sha256", "known_hash_adler32"): - val = entry.get(field) - vals = val if isinstance(val, list) else [val] if val else [] - for v in vals: - v = str(v).lower().removeprefix("0x") - if v: - tokens.append(v) + tokens = entry_hashes(entry) if not tokens: name = entry.get("name", "") if name: @@ -220,6 +240,8 @@ def _anchor_tokens(entry: dict) -> list[str]: set name alone, so the stem is searched as well as the full name. """ tokens = collect_tokens(entry) + if entry_hashes(entry): + return tokens name = os.path.basename(str(entry.get("name", ""))).lower() stem = name.rsplit(".", 1)[0] if stem and stem not in tokens and len(stem) > 2: diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 4c89f81a..f7ee8ea9 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -445,6 +445,18 @@ class TestExternalCitation(unittest.TestCase): class TestAnchorTokens(unittest.TestCase): + def test_archive_members_supply_the_content_values(self): + tokens = profile_sync._anchor_tokens( + {"name": "esh.zip", "contents": [{"crc32": "c14f36b3"}]} + ) + self.assertEqual(tokens, ["c14f36b3"]) + + def test_a_hashless_archive_still_falls_back_to_its_stem(self): + tokens = profile_sync._anchor_tokens( + {"name": "esh.zip", "contents": [{"name": "rom.u1"}]} + ) + self.assertIn("esh", tokens) + def test_archive_stem_is_searched_too(self): tokens = profile_sync._anchor_tokens({"name": "stvbios.zip"}) self.assertIn("stvbios.zip", tokens)