fix: platform entries skip other emulators' trees

A platform entry names no profile, so bios/Other/<emulator>/ was never foreign to it. EmuELEC's ports/VVVVVV/data.zip resolved to xrick's data.zip, then to xrick's data directory copy. A system that lists its cores now takes no name match only another emulator's tree answers.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 03:21:07 +01:00
1 parent 35ad19ff02
commit 402e688b01
4 files changed
+122 -20

No files matched your search

+17 -17
View File
@@ -20,11 +20,11 @@
}
],
"standalone_copies": [],
"total_files": 5297,
"total_size": 4143074421,
"pack_files": 9169,
"pack_size": 4257242836,
"total_omitted": 197,
"total_files": 5296,
"total_size": 4141616981,
"pack_files": 9168,
"pack_size": 4255785396,
"total_omitted": 198,
"omitted_files": [
{
"dest": "FB Alpha 2012/hiscore.dat",
@@ -2070,6 +2070,18 @@
],
"category": "game_data"
},
{
"dest": "ports/VVVVVV/data.zip",
"name": "data.zip",
"system": "vvvvvv",
"required": true,
"reason": "not_found",
"cores": null,
"systems": [
"vvvvvv"
],
"category": "game_data"
},
{
"dest": "ports/diablo/diabdat.mpq",
"name": "diabdat.mpq",
@@ -8911,18 +8923,6 @@
],
"category": "game_data"
},
{
"dest": "ports/VVVVVV/data.zip",
"sha1": "ecfc092fe6371dbf38e238a8ba5f90785b5db52d",
"sha256": "2a86458696e83eb924fc6c6fda3ca5d320ca90885bd6c2f32d121757ade389bb",
"size": 1457440,
"repo_path": "bios/Other/xrick/data.zip",
"cores": null,
"systems": [
"vvvvvv"
],
"category": "game_data"
},
{
"dest": "ports/xrick/data.zip",
"sha1": "ecfc092fe6371dbf38e238a8ba5f90785b5db52d",
+32 -1
View File
@@ -424,11 +424,24 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
# Each entry carries the system that declares it: a hashless name match is
# narrowed to the maker folder that system names (resolve_local_file).
# A system that names its own cores (EmuELEC, AmberELEC, KNULLI) also
# passes them on, so another emulator's tree is foreign to its entries.
# Fresh dicts, since a YAML anchor can share one entry between systems.
config["systems"] = {
sys_id: {
**system,
"files": [{**f, "platform_system": sys_id} for f in system["files"]],
"files": [
{
**f,
"platform_system": sys_id,
**(
{"platform_cores": list(system["cores"])}
if system.get("cores")
else {}
),
}
for f in system["files"]
],
}
if isinstance(system, dict) and system.get("files")
else system
@@ -1735,6 +1748,24 @@ def resolve_local_file(
continue
if os.path.exists(path) and path not in candidates:
candidates.append(path)
# A platform entry names no profile, but a system that lists its
# cores says whose files it asks for. A name only another emulator's
# tree answers is no evidence, and the data directories hold those
# emulators' trees too: EmuELEC's VVVVVV data.zip was served xrick's
# archive of the same name, then xrick's data directory copy.
core_owners = {
token
for core in file_entry.get("platform_cores") or ()
for token in _affinity_tokens(str(core))
}
if (
not owners
and core_owners
and candidates
and all(_foreign_tree(p, core_owners) for p in candidates)
):
candidates = []
unsourceable = True
if makers and candidates:
candidates = [p for p in candidates if not other_maker(p, makers, db)]
if not candidates:
+7 -2
View File
@@ -1035,13 +1035,18 @@ def _split_contract(config: dict) -> tuple[dict, dict]:
"""
allowed = _contract_keys()
contract = {key: value for key, value in config.items() if key in allowed}
# platform_system is the resolver's copy of the key each entry sits under.
# platform_system and platform_cores are the resolver's copies of the key
# each entry sits under and of that system's cores.
if isinstance(contract.get("systems"), dict):
contract["systems"] = {
sys_id: {
**system,
"files": [
{k: v for k, v in f.items() if k != "platform_system"}
{
k: v
for k, v in f.items()
if k not in ("platform_system", "platform_cores")
}
for f in system["files"]
],
}
+66
View File
@@ -148,6 +148,72 @@ class AbsentFileIsNotReplacedByAHomonym(unittest.TestCase):
self.assertEqual(self._resolve(), (None, "not_found"))
class APlatformEntryTakesNoOtherEmulatorsTree(unittest.TestCase):
"""A platform declaration names no profile, so another emulator's tree
was never foreign to it: EmuELEC's ports/VVVVVV/data.zip was served
xrick's data.zip, Rick Dangerous sounds, then xrick's data directory
copy. A system that lists its cores says whose files it asks for."""
def setUp(self):
import tempfile
import generate_db
self._tmp = tempfile.TemporaryDirectory()
self._cwd = os.getcwd()
os.chdir(self._tmp.name)
payload = b"rick dangerous"
target = Path("bios/Other/xrick/data.zip")
target.parent.mkdir(parents=True)
target.write_bytes(payload)
cache = Path("data/xrick/data.zip")
cache.parent.mkdir(parents=True)
cache.write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
files = {sha1: {
"path": str(target), "name": "data.zip", "size": len(payload),
"sha1": sha1, "md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "0",
}}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
self.registry = {"xrick": {"local_cache": "data/xrick"}}
def tearDown(self):
os.chdir(self._cwd)
self._tmp.cleanup()
def _resolve(self, **fields):
entry = {"name": "data.zip", "destination": "ports/VVVVVV/data.zip",
"platform_system": "vvvvvv", **fields}
return resolve_local_file(
entry, self.db, {}, dest_hint=entry["destination"],
data_dir_registry=self.registry,
)
def test_a_system_naming_other_cores_gets_nothing(self):
self.assertEqual(self._resolve(platform_cores=["VVVVVV"]), (None, "not_found"))
def test_the_owners_own_system_still_gets_it(self):
self.assertEqual(
self._resolve(platform_cores=["xrick"]),
("bios/Other/xrick/data.zip", "name_exact"),
)
def test_the_emuelec_entry_is_not_served_xrick(self):
os.chdir(self._cwd)
from common import load_platform_config
config = load_platform_config("emuelec", str(REPO_ROOT / "platforms"))
entry = config["systems"]["vvvvvv"]["files"][0]
self.assertEqual(entry["platform_cores"], ["VVVVVV"])
db = load_database(str(REPO_ROOT / "database.json"))
path, _status = resolve_local_file(
entry, db, {}, dest_hint=entry["destination"],
data_dir_registry=load_data_dir_registry(str(REPO_ROOT / "platforms")),
)
self.assertFalse(path and "xrick" in path, path)
class SecondPassKeepsIdentity(unittest.TestCase):
"""A copy of a covered file at another core's path keeps the entry's proof.