diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index 09b889989..19405123f 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -27,6 +27,7 @@ from common import ( platform_arg, ProfileSelectionError, _norm_system_id, + archive_entry, directory_members, expand_directory_entries, get_mame_clone_map, @@ -545,6 +546,20 @@ class FileConsumers: if member is not entry } + @staticmethod + def entry_target(entry: dict, files: list[dict]) -> tuple[dict, str]: + """The entry a profile entry resolves through, and its destination. + + A ROM read inside an archive is the archive: FBNeo opens sm1.sm1 in + neogeo.zip, and resolved by its crc32 it credited a loose copy FBNeo + never opens, while no neogeo.zip card named FBNeo. The archive + members the profile reads decide between same-named copies, as in + the emulator pack and verify. + """ + if entry.get("archive"): + return archive_entry(entry["archive"], files), "" + return with_archive_members(entry, files), entry.get("path") or "" + def add_emulators(self, profiles: dict[str, dict]) -> None: """Each file a profile entry resolves to, a directory's members included. @@ -573,12 +588,7 @@ class FileConsumers: for dest, sha1 in members.items() ] else: - # The archive members the profile reads decide between - # same-named copies, as in the emulator pack and verify. - found = [self.sha1_of( - with_archive_members(entry, files), entry.get("path") or "", - owner=name, - )] + found = [self.sha1_of(*self.entry_target(entry, files), owner=name)] for sha1 in found: if sha1: self.emulators.setdefault(sha1, set()).add(name) diff --git a/scripts/generate_site.py b/scripts/generate_site.py index c5206c4f0..f2a47ed66 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -8182,10 +8182,13 @@ def _render_emulator_file( if consumers and f.get("type") == "directory": details.extend(_directory_copies(f, consumers)) if consumers: - sha1 = consumers.sha1_of(f, f.get("path") or "", owner=owner) + sha1 = consumers.sha1_of( + *consumers.entry_target(f, profile.get("files") or []), owner=owner) held = consumers.path_of(sha1) if sha1 and in_repo else "" parts = held.split("/") - if len(parts) >= 3 and parts[0] == "bios": + if len(parts) >= 3 and parts[0] == "bios" and f.get("archive"): + details.append(_held_copy_line((held, f["name"]), consumers)) + elif len(parts) >= 3 and parts[0] == "bios": # Which copy answers this entry, linked to its collection card # (provenance, every hash, download): an entry declaring no hash # said "in repo" without naming the file. diff --git a/tests/test_site_render.py b/tests/test_site_render.py index 0236dc2bd..e943dffde 100644 --- a/tests/test_site_render.py +++ b/tests/test_site_render.py @@ -454,3 +454,33 @@ class OneDefinitionPerName(unittest.TestCase): self.assertNotIn(name, seen, f"{path.name}:{node.lineno} binds {name} " f"again, first at line {seen.get(name)}") seen[name] = node.lineno + + +@unittest.skipUnless((REPO_ROOT / "database.json").is_file(), "database.json is not built") +class AMemberIsReadInItsArchive(unittest.TestCase): + """FBNeo reads sm1.sm1 inside neogeo.zip. Resolved by its crc32, the + entry credited a loose sm1.sm1 FBNeo never opens, and no neogeo.zip card + named FBNeo, while the pack ships it one.""" + + def test_the_archive_is_credited_and_named_on_the_card(self): + import json + + import generate_site as gs + from common import archive_entry, resolve_local_file + from cross_reference import FileConsumers + + db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8")) + profile = yaml.safe_load((REPO_ROOT / "emulators" / "fbneo.yml").read_text(encoding="utf-8")) + files = profile["files"] + consumers = FileConsumers(db) + consumers.add_emulators({"fbneo": profile}) + credited = {consumers.path_of(sha1) for sha1 in consumers.emulators} + shipped, _status = resolve_local_file( + {**archive_entry("neogeo.zip", files), "source_profile": "fbneo"}, db) + self.assertIn(shipped, credited) + self.assertFalse([p for p in credited if p.endswith("/sm1.sm1")]) + sm1 = next(f for f in files if f["name"] == "sm1.sm1") + rows = "\n".join(gs._render_emulator_file(sm1, profile, consumers, files, + lambda _f: True, owner="fbneo")) + self.assertIn("Collection copy: member `sm1.sm1` of", rows) + self.assertIn(shipped, rows)