diff --git a/scripts/packextras.py b/scripts/packextras.py index d21e2d54..d720763a 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -497,20 +497,25 @@ def _collect_emulator_extras( ): continue seen_dests.add(full_dest) - extras.append( - { - "name": fname, - "destination": dest, - "required": f.get("required", False), - "hle_fallback": f.get("hle_fallback", False), - "source_emulator": profile.get("emulator", emu_name), - "source_profile": emu_name, - "source_system": f.get("system"), - "source_systems": list(profile.get("systems", [])), - "region": f.get("region"), - "variant_group": f.get("variant_group"), - } - ) + extra = { + "name": fname, + "destination": dest, + "required": f.get("required", False), + "hle_fallback": f.get("hle_fallback", False), + "source_emulator": profile.get("emulator", emu_name), + "source_profile": emu_name, + "source_system": f.get("system"), + "source_systems": list(profile.get("systems", [])), + "region": f.get("region"), + "variant_group": f.get("variant_group"), + } + # The same identity the first pass carries: without it the copy + # resolves on its name alone and a same-named file of another + # game fills the slot the profile marks as not held. + for identity in ("sha1", "md5", "sha256", "crc32", "size", "unsourceable"): + if f.get(identity) not in (None, "", []): + extra[identity] = f[identity] + extras.append(extra) extras.extend( _archive_prefix_extras( diff --git a/tests/test_game_data_homonyms.py b/tests/test_game_data_homonyms.py index a9bb595a..58bd00f6 100644 --- a/tests/test_game_data_homonyms.py +++ b/tests/test_game_data_homonyms.py @@ -131,6 +131,45 @@ class AbsentFileIsNotReplacedByAHomonym(unittest.TestCase): self.assertEqual(self._resolve(), (None, "not_found")) +class SecondPassKeepsIdentity(unittest.TestCase): + """A copy of a covered file at another core's path keeps the entry's proof. + + The second extras pass copied a name the baseline already covers to the + profile's own path, without its hashes or its `unsourceable:` flag, so + the copy resolved on the name: RetroBat's pack carried Quake III's + baseq3/pak1.pk3 as MOHAA's mainta/pak1.pk3. + """ + + def test_flag_and_hashes_reach_the_alternative_destination(self): + from packextras import _collect_emulator_extras + + sha = "a" * 40 + md5 = "b" * 32 + db = { + "files": {sha: {"path": "bios/Q3/baseq3/pak1.pk3", "name": "pak1.pk3", + "sha1": sha, "md5": md5, "size": 10}}, + "indexes": {"by_name": {"pak1.pk3": [sha]}, "by_md5": {md5: sha}, + "by_path_suffix": {"baseq3/pak1.pk3": [sha]}, "by_crc32": {}}, + } + config = { + "platform": "P", "cores": ["mohaa"], "standalone_cores": ["mohaa"], + "systems": {"q3": {"files": [ + {"name": "pak1.pk3", "destination": "baseq3/pak1.pk3", "md5": md5}]}}, + } + profiles = {"mohaa": { + "emulator": "MOHAA", "type": "standalone", "cores": ["mohaa"], + "systems": ["mohaa"], + "files": [ + {"name": "pak1.pk3", "path": "mainta/pak1.pk3", "unsourceable": "retail"}, + {"name": "pak1.pk3", "path": "maintt/pak1.pk3", "sha1": "c" * 40}, + ], + }} + extras = {e["destination"]: e for e in _collect_emulator_extras( + config, "emulators", db, set(), "", profiles)} + self.assertEqual(extras["mainta/pak1.pk3"].get("unsourceable"), "retail") + self.assertEqual(extras["maintt/pak1.pk3"].get("sha1"), "c" * 40) + + class CollectionCarriesNoGameDataHomonym(unittest.TestCase): def test_the_profiles_resolve_no_game_data_to_another_game(self): database = REPO_ROOT / "database.json"