mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
fix: keep another emulator's tree out of resolution
This commit is contained in:
1 parent
5ebb141dbf
commit
5090208a64
2 files changed
+126
-17
No files matched your search
+54
-17
@@ -489,6 +489,51 @@ def _owner_tokens(file_entry: dict) -> set[str]:
|
||||
return tokens
|
||||
|
||||
|
||||
def _foreign_tree(path: str, owners: set[str]) -> bool:
|
||||
"""Whether a path lives in the tree of an emulator that is not the asker.
|
||||
|
||||
bios/Other/<emulator>/ holds what one emulator ships. A same-named file
|
||||
found there answers that emulator's entry, not another's: EmuCoreX's
|
||||
unlock.wav was served from NetherSX2's tree, and upstream PCSX2's
|
||||
GameIndex.yaml from EmuCoreX's, while the system directory held a copy.
|
||||
"""
|
||||
parts = path.split("/")
|
||||
if not owners or len(parts) < 4 or parts[0] != "bios" or parts[1] != "Other":
|
||||
return False
|
||||
return not (owners & _affinity_tokens(parts[2]))
|
||||
|
||||
|
||||
def _rank_tail_candidates(
|
||||
candidates: list[str], start: int, tail: str, files_db: dict, owners: set[str]
|
||||
) -> list[str]:
|
||||
"""Order the files a destination tail designates, the likeliest first.
|
||||
|
||||
A shortened tail found only in another emulator's tree is that
|
||||
emulator's asset, not evidence about this entry. A full tail claimed
|
||||
twice goes to the file whose path carries the least on top of the
|
||||
destination, then to the owner's own copy (super3's Games.xml was served
|
||||
to Supermodel-Dojo by index order alone), then to any copy outside
|
||||
another emulator's tree (upstream PCSX2 took EmuCoreX's GameIndex.yaml
|
||||
over the system directory's).
|
||||
"""
|
||||
def path_of(sha1: str) -> str:
|
||||
return files_db.get(sha1, {}).get("path", "")
|
||||
|
||||
if start:
|
||||
candidates = [h for h in candidates if not _foreign_tree(path_of(h), owners)]
|
||||
if len(candidates) < 2:
|
||||
return candidates
|
||||
depth = len(tail.split("/"))
|
||||
return sorted(
|
||||
candidates,
|
||||
key=lambda h: (
|
||||
len(path_of(h).split("/")) - depth,
|
||||
not owners & _affinity_tokens(path_of(h).rsplit("/", 1)[0]),
|
||||
_foreign_tree(path_of(h), owners),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]:
|
||||
"""Same-named candidates, the likeliest first.
|
||||
|
||||
@@ -507,14 +552,15 @@ def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str
|
||||
owners = _owner_tokens(file_entry)
|
||||
sized = any(file_entry.get(k) for k in ("size", "min_size", "max_size"))
|
||||
|
||||
def score(path: str) -> tuple[int, bool, int]:
|
||||
def score(path: str) -> tuple[int, bool, bool, int]:
|
||||
fits = 0
|
||||
if sized and os.path.exists(path):
|
||||
fits = int(size_fits(file_entry, os.path.getsize(path)))
|
||||
tokens = _affinity_tokens(path.rsplit("/", 1)[0])
|
||||
# The owner's own copy ranks above any shared tree segment: NetherSX2's
|
||||
# shaders/common/ beat armsx2's own fxaa.fx on two directory tokens.
|
||||
return fits, bool(owners & tokens), len(wanted & tokens)
|
||||
# Another emulator's own tree ranks below everything else.
|
||||
return fits, bool(owners & tokens), not _foreign_tree(path, owners | wanted), len(wanted & tokens)
|
||||
|
||||
return sorted(paths, key=score, reverse=True)
|
||||
|
||||
@@ -716,9 +762,14 @@ def resolve_local_file(
|
||||
# stops describing this file and starts describing whichever emulator
|
||||
# happens to store the same asset tree.
|
||||
hint_parts = dest_hint.split("/")
|
||||
# The asking profile and the destination's own directories both name
|
||||
# whose tree a copy may come from.
|
||||
owners = _owner_tokens(file_entry) | _affinity_tokens("/".join(hint_parts[:-1]))
|
||||
for start in range(min(2, len(hint_parts) - 1)):
|
||||
tail = "/".join(hint_parts[start:])
|
||||
candidates = by_path_suffix.get(tail, [])
|
||||
candidates = _rank_tail_candidates(
|
||||
by_path_suffix.get(tail, []), start, tail, files_db, owners
|
||||
)
|
||||
if start and len(candidates) > 1:
|
||||
continue
|
||||
# The index names the file this destination designates. Not on
|
||||
@@ -729,20 +780,6 @@ def resolve_local_file(
|
||||
for h in candidates
|
||||
):
|
||||
own_file_absent = True
|
||||
if len(candidates) > 1:
|
||||
depth = len(tail.split("/"))
|
||||
# At equal depth the owner's own copy: super3's Games.xml was
|
||||
# served to Supermodel-Dojo by index order alone.
|
||||
owners = _owner_tokens(file_entry)
|
||||
candidates = sorted(
|
||||
candidates,
|
||||
key=lambda h: (
|
||||
len(files_db.get(h, {}).get("path", "").split("/")) - depth,
|
||||
not owners & _affinity_tokens(
|
||||
files_db.get(h, {}).get("path", "").rsplit("/", 1)[0]
|
||||
),
|
||||
),
|
||||
)
|
||||
for match_sha1 in candidates:
|
||||
if match_sha1 not in files_db:
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""A file in another emulator's tree does not answer for this one.
|
||||
|
||||
bios/Other/<emulator>/ holds what that emulator ships. The path step took a
|
||||
shortened tail that only NetherSX2's tree answered as proof for EmuCoreX's
|
||||
unlock.wav, and broke a full-tail tie between EmuCoreX's GameIndex.yaml and
|
||||
the system directory's copy by index order, serving a fork's game database
|
||||
to upstream PCSX2.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
import generate_db # noqa: E402
|
||||
from common import resolve_local_file # noqa: E402
|
||||
|
||||
|
||||
class ForeignTree(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self._cwd = os.getcwd()
|
||||
os.chdir(self._tmp.name)
|
||||
files = {}
|
||||
for rel, payload in (
|
||||
("bios/Other/forkemu/resources/GameIndex.yaml", b"fork db"),
|
||||
("bios/Sony/PlayStation 2/resources/GameIndex.yaml", b"system copy"),
|
||||
("bios/Other/forkemu/resources/sounds/unlock.wav", b"fork sound"),
|
||||
):
|
||||
path = Path(rel)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(payload)
|
||||
sha1 = hashlib.sha1(payload).hexdigest()
|
||||
files[sha1] = {
|
||||
"path": rel, "name": path.name, "size": len(payload), "sha1": sha1,
|
||||
"md5": hashlib.md5(payload).hexdigest(),
|
||||
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000001",
|
||||
}
|
||||
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
|
||||
|
||||
def tearDown(self):
|
||||
os.chdir(self._cwd)
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _resolve(self, owner: str, path: str):
|
||||
entry = {"name": path.rsplit("/", 1)[-1], "path": path, "source_profile": owner}
|
||||
return resolve_local_file(entry, self.db, {}, dest_hint=path)
|
||||
|
||||
def test_a_full_tail_tie_goes_to_the_system_directory(self):
|
||||
local, status = self._resolve("pcsx2", "resources/GameIndex.yaml")
|
||||
self.assertEqual(status, "path_exact")
|
||||
self.assertTrue(local.startswith("bios/Sony/"), local)
|
||||
|
||||
def test_the_fork_still_gets_its_own_copy(self):
|
||||
local, status = self._resolve("forkemu", "resources/GameIndex.yaml")
|
||||
self.assertEqual(status, "path_exact")
|
||||
self.assertTrue(local.startswith("bios/Other/forkemu/"), local)
|
||||
|
||||
def test_a_shortened_tail_in_a_foreign_tree_is_not_path_evidence(self):
|
||||
_local, status = self._resolve("otheremu", "assets/sounds/unlock.wav")
|
||||
self.assertNotEqual(status, "path_exact")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user