fix: let content labels accept their region slot

verify judged a found_by: content entry by its label alone and called ROCKNIX's scph1001.bin (v2.0) a rustation sha256 mismatch, though rustation declares and boots that dump. A label now accepts the dumps its core takes for the same system and declared region.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 08:16:08 +01:00
1 parent b550b9780c
commit 807ffa0ca9
2 files changed
+107 -1

No files matched your search

+68 -1
View File
@@ -16,6 +16,7 @@ from common import (
adler32_of,
archive_members,
compute_hashes,
entry_system_ids,
name_match_size_ok,
sanitize_pack_path,
size_fits,
@@ -145,6 +146,72 @@ def _rule_failure(
return None
def _found_by_content(entry: dict, profile: dict) -> bool:
"""Whether every build of the core identifies this entry by content."""
return not opens_by_name(entry, profile) and not opens_by_name(
entry, profile, standalone=True
)
def _regions(entry: dict) -> set[str]:
value = entry.get("region")
return set(value) if isinstance(value, list) else {value} if value else set()
def content_alternatives(entry: dict, profile: dict) -> list[dict]:
"""The labels a content-found core takes in place of this one.
rustation scans its folder by sha256 (found_by: content): its
scph1001.bin is a label for v2.2, and the v2.0 dump ROCKNIX ships under
that name boots the same North American console, labelled
scph1001_v20.bin. Alternatives share the entry's system and declared
region, the slot slot.py groups by: openMSX also finds its ROMs by
sha1, but each one serves its own machine and declares no region.
"""
if not _found_by_content(entry, profile):
return []
regions = _regions(entry)
if not regions:
return []
systems = set(entry_system_ids(entry, profile))
return [
other for other in profile.get("files") or []
if isinstance(other, dict)
and _found_by_content(other, profile)
and regions & _regions(other)
and systems == set(entry_system_ids(other, profile))
]
def _labels_widened(profile: dict) -> list[dict]:
"""The profile's entries, each content-found label accepting the dumps
of its alternatives (content_alternatives)."""
widened = []
for f in profile.get("files") or []:
if not isinstance(f, dict):
continue
siblings = content_alternatives(f, profile)
if len(siblings) < 2:
widened.append(f)
continue
merged = dict(f)
for field_name in ("crc32", "md5", "sha1", "sha256"):
if f.get(field_name):
merged[field_name] = list(dict.fromkeys(
value for other in siblings
for value in parse_hash_list(other.get(field_name))
))
if f.get("size") is not None:
merged["size"] = sorted({
size for other in siblings
for size in (other["size"] if isinstance(other.get("size"), list)
else [other.get("size")])
if size is not None
})
widened.append(merged)
return widened
def _build_validation_index(profiles: dict) -> dict[str, dict]:
"""Build per-filename validation rules from emulator profiles.
@@ -170,7 +237,7 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
for emu_name, profile in profiles.items():
if profile.get("type") in ("launcher", "alias"):
continue
for f in profile.get("files", []):
for f in _labels_widened(profile):
fname = f.get("name", "")
if not fname:
continue
+39
View File
@@ -47,5 +47,44 @@ class HleIndexScope(unittest.TestCase):
self.assertIn("for profile in platform_profiles.values()", block)
self.assertNotIn("profiles.values()", block.replace("platform_profiles.values()", ""))
class ContentFoundLabelsTakeTheirRegionSlot(unittest.TestCase):
"""rustation scans its folder by sha256, so its scph1001.bin entry is a
label. ROCKNIX ships the v2.0 dump under that name, which rustation
boots (labelled scph1001_v20.bin), and verify called it a mismatch."""
def test_an_alternative_dump_passes_under_the_label(self):
import hashlib
import sys
import tempfile
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from validation import _build_validation_index, check_file_validation
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "scph1001.bin"
path.write_bytes(b"v2.0")
v20 = hashlib.sha256(b"v2.0").hexdigest()
profile = {
"found_by": "content",
"systems": ["sony-playstation"],
"files": [
{"name": "scph1001.bin", "region": ["north-america"],
"validation": ["sha256"], "sha256": "a" * 64},
{"name": "scph1001_v20.bin", "region": ["north-america"],
"validation": ["sha256"], "sha256": v20},
{"name": "scph5500.bin", "region": ["japan"],
"validation": ["sha256"], "sha256": "b" * 64},
],
}
index = _build_validation_index({"rustation": profile})
self.assertIsNone(check_file_validation(str(path), "scph1001.bin", index))
# Another region is another slot.
self.assertIsNotNone(check_file_validation(str(path), "scph5500.bin", index))
# A core that opens the file by name rejects it.
index = _build_validation_index({"rustation": {**profile, "found_by": "name"}})
self.assertIsNotNone(check_file_validation(str(path), "scph1001.bin", index))
if __name__ == "__main__":
unittest.main()