mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-06 10:03:49 -06:00
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:
1 parent
b550b9780c
commit
807ffa0ca9
2 files changed
+107
-1
No files matched your search
+68
-1
@@ -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
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user