diff --git a/scripts/common.py b/scripts/common.py index 0085cb43..a0018a25 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -886,7 +886,28 @@ def resolve_local_file( candidates = _by_affinity(candidates, file_entry, dest_hint) primary = [p for p in candidates if "/.variants/" not in p] if primary or candidates: - return (primary[0] if primary else candidates[0]), "name_exact" + chosen = primary[0] if primary else candidates[0] + # Even the best of them is another emulator's own copy: the + # name says nothing about this profile's file, and a data + # directory it reads may hold it. Dolphin got Dolphin-MMJR's + # codehandler.bin while data/dolphin-sys held its own. + if data_dir_registry and _foreign_tree(chosen, _owner_tokens(file_entry)): + hit, _mismatch = _resolve_in_data_dirs( + names_to_try, data_dir_registry, + functools.partial( + declared_hash_verdict, + has_strong_hash=False, + sha1_candidates=set(), + sha256_candidates=set(), + md5_list=[], + crc_candidates=set(), + zipped_file=zipped_file, + declared_size=declared_size, + ), + ) + if hit: + return hit + return chosen, "name_exact" # 4. Name + alias fallback with md5_composite + direct MD5 per candidate md5_set = set(md5_list) diff --git a/tests/test_game_data_homonyms.py b/tests/test_game_data_homonyms.py index 0fbd45d0..018f8a3f 100644 --- a/tests/test_game_data_homonyms.py +++ b/tests/test_game_data_homonyms.py @@ -254,6 +254,44 @@ class ReleaseAssetAnswersEveryDeclaredHash(unittest.TestCase): self.assertEqual(self._resolve(entry, b"second"), ("big.bin", "release_asset")) +class ForeignCopyYieldsToADataDirectory(unittest.TestCase): + """codehandler.bin is held by three Dolphin forks under bios/Other/, all + different; Dolphin, PrimeHack and Triforce got MMJR's by index order + while data/dolphin-sys, which Dolphin reads, held the upstream file.""" + + def test_the_data_directory_answers_before_another_emulators_copy(self): + import tempfile + + from common import resolve_local_file + + with tempfile.TemporaryDirectory() as tmp: + previous = os.getcwd() + os.chdir(tmp) + self.addCleanup(os.chdir, previous) + fork = Path("bios/Other/dolphin-mmjr/codehandler.bin") + fork.parent.mkdir(parents=True) + fork.write_bytes(b"mmjr") + sys_dir = Path("data/dolphin-sys") + sys_dir.mkdir(parents=True) + (sys_dir / "codehandler.bin").write_bytes(b"upstream") + sha = hashlib.sha1(b"mmjr").hexdigest() + db = {"files": {sha: {"path": str(fork), "name": "codehandler.bin"}}, + "indexes": {"by_name": {"codehandler.bin": [sha]}, "by_md5": {}, + "by_crc32": {}, "by_path_suffix": {}}} + registry = {"dolphin-sys": {"local_cache": str(sys_dir)}} + entry = {"name": "codehandler.bin"} + self.assertEqual( + resolve_local_file({**entry, "source_profile": "dolphin"}, db, + data_dir_registry=registry), + (str(sys_dir / "codehandler.bin"), "data_dir"), + ) + self.assertEqual( + resolve_local_file({**entry, "source_profile": "dolphin-mmjr"}, db, + data_dir_registry=registry), + (str(fork), "name_exact"), + ) + + class NameStepPrefersTheOwnersCopy(unittest.TestCase): """Same-named files are ordered by where the asking profile keeps its own.