mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
fix: judge a core file by the cores that read it
This commit is contained in:
1 parent
840f9ceaf6
commit
f5a5d6f4bd
14 files changed
+308
-65
No files matched your search
@@ -4837,10 +4837,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
]
|
||||
@@ -47899,10 +47899,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -1840,10 +1840,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
],
|
||||
|
||||
@@ -1036,10 +1036,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
],
|
||||
@@ -1270,10 +1270,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
+6
-6
@@ -6110,10 +6110,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
]
|
||||
@@ -61637,10 +61637,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -9824,10 +9824,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -6349,10 +6349,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
]
|
||||
@@ -61876,10 +61876,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -4813,10 +4813,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
]
|
||||
@@ -47905,10 +47905,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -22689,10 +22689,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "bios/sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
],
|
||||
@@ -25535,10 +25535,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "bios/GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -6423,10 +6423,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
]
|
||||
@@ -101392,10 +101392,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -801,10 +801,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "sysdata/otp.bin",
|
||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
||||
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
||||
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||
"cores": [
|
||||
"azahar"
|
||||
]
|
||||
@@ -2779,10 +2779,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
+3
-3
@@ -6472,10 +6472,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "ngc/GC/dsp_rom.bin",
|
||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
||||
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
||||
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||
"cores": [
|
||||
"dolphin"
|
||||
],
|
||||
|
||||
@@ -1068,6 +1068,16 @@ def generate_pack(
|
||||
)
|
||||
if status in ("not_found", "external", "user_provided") or not local_path:
|
||||
continue
|
||||
# The emulator's own check picks the copy, as for a declared file
|
||||
# and in the emulator pack: the name led RetroArch's GC/dsp_rom.bin
|
||||
# to Dolphin's obsolete v0.3.1 free ROM, beside Nintendo's coef
|
||||
# ROM, a pair Dolphin warns about at every start.
|
||||
local_path, disagreement = validated_choice(
|
||||
fe, local_path, db, validation_index, bios_dir, None, dest,
|
||||
validation_owners,
|
||||
)
|
||||
if disagreement:
|
||||
core_discrepancies.append(f"{full_dest} -{disagreement}")
|
||||
if status == "hash_mismatch":
|
||||
# The core's declared hash comes from its source, the local dump
|
||||
# is what the collection holds. Shipping it keeps the emulator
|
||||
@@ -3100,6 +3110,8 @@ def _manifest_core_entries(
|
||||
pack_only_sizes: list[int],
|
||||
selection: SelectionIndex,
|
||||
required_only: bool = False,
|
||||
validation_index: dict | None = None,
|
||||
owners: dict | None = None,
|
||||
) -> int:
|
||||
"""Add the files a platform's cores need but its list does not name.
|
||||
|
||||
@@ -3153,6 +3165,10 @@ def _manifest_core_entries(
|
||||
[source_emu] if source_emu else [],
|
||||
)
|
||||
continue
|
||||
# The copy the emulator accepts, as the pack ships it.
|
||||
local_path, _disagreement = validated_choice(
|
||||
fe, local_path, db, validation_index or {}, bios_dir, None, dest, owners,
|
||||
)
|
||||
|
||||
source_emu = fe.get("source_profile") or fe.get("source_emulator", "")
|
||||
|
||||
@@ -3479,7 +3495,7 @@ def generate_manifest(
|
||||
zip_contents, offline, region_drops, case_insensitive,
|
||||
seen_destinations, seen_lower, seen_parents, manifest_files,
|
||||
omitted_by_destination, record_omission, pack_only_sizes, selection,
|
||||
required_only,
|
||||
required_only, manifest_validation, manifest_owners,
|
||||
)
|
||||
|
||||
# Phase 3: data directories. The installer does not fetch them, so they
|
||||
|
||||
+118
-6
@@ -56,6 +56,84 @@ def _parse_validation(validation: list | dict | None) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def _entry_rule(emu_name: str, f: dict, checks: set[str]) -> dict:
|
||||
"""One profile entry's own checks, kept apart from the merged view.
|
||||
|
||||
Merged by name, Azahar's crypto check on the 3DS otp.bin judged Cemu's
|
||||
1 KiB Wii U otp.bin, and the variant search handed Cemu the 3DS file.
|
||||
"""
|
||||
def values(field: str) -> set[str]:
|
||||
raw = f.get(field)
|
||||
if not raw or field not in checks:
|
||||
return set()
|
||||
items = raw if isinstance(raw, list) else str(raw).split(",")
|
||||
out = set()
|
||||
for item in items:
|
||||
norm = str(item).strip().lower()
|
||||
if field == "crc32" and norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
if norm:
|
||||
out.add(norm)
|
||||
return out
|
||||
|
||||
raw_size = f.get("size") if "size" in checks else None
|
||||
adler = f.get("known_hash_adler32") or f.get("adler32")
|
||||
adler_values = adler if isinstance(adler, list) else [adler] if adler else []
|
||||
return {
|
||||
"emulator": emu_name,
|
||||
"checks": set(checks),
|
||||
"sizes": set(raw_size if isinstance(raw_size, list) else [raw_size])
|
||||
- {None},
|
||||
"min_size": f.get("min_size") if "size" in checks else None,
|
||||
"max_size": f.get("max_size") if "size" in checks else None,
|
||||
"crc32": values("crc32"),
|
||||
"md5": values("md5"),
|
||||
"sha1": values("sha1"),
|
||||
"sha256": values("sha256"),
|
||||
"adler32": {
|
||||
str(v).lower().removeprefix("0x") for v in adler_values if v
|
||||
},
|
||||
"adler32_byteswap": bool(f.get("adler32_byteswap")),
|
||||
"crypto": {c for c in checks if c in _CRYPTO_CHECKS},
|
||||
}
|
||||
|
||||
|
||||
def _rule_failure(
|
||||
rule: dict, local_path: str, filename: str, bios_dir: str, hashes: dict,
|
||||
) -> str | None:
|
||||
"""Why one entry's checks reject a file, or None when they accept it."""
|
||||
actual_size = os.path.getsize(local_path)
|
||||
if rule["sizes"] and actual_size not in rule["sizes"]:
|
||||
accepted = ",".join(str(s) for s in sorted(rule["sizes"]))
|
||||
return f"size mismatch: got {actual_size}, accepted [{accepted}]"
|
||||
if rule["min_size"] is not None and actual_size < rule["min_size"]:
|
||||
return f"size too small: min {rule['min_size']}, got {actual_size}"
|
||||
if rule["max_size"] is not None and actual_size > rule["max_size"]:
|
||||
return f"size too large: max {rule['max_size']}, got {actual_size}"
|
||||
for hash_type in ("crc32", "md5", "sha1", "sha256"):
|
||||
if rule[hash_type]:
|
||||
if not hashes:
|
||||
hashes.update(compute_hashes(local_path))
|
||||
if hashes[hash_type].lower() not in rule[hash_type]:
|
||||
accepted = ",".join(sorted(rule[hash_type]))
|
||||
return f"{hash_type} mismatch: got {hashes[hash_type]}, accepted [{accepted}]"
|
||||
if rule["adler32"]:
|
||||
if rule["adler32_byteswap"]:
|
||||
actual = _adler32_byteswapped(local_path)
|
||||
else:
|
||||
if not hashes:
|
||||
hashes.update(compute_hashes(local_path))
|
||||
actual = hashes["adler32"].lower()
|
||||
if actual not in rule["adler32"]:
|
||||
accepted = ",".join(sorted(rule["adler32"]))
|
||||
return f"adler32 mismatch: got 0x{actual}, accepted [{accepted}]"
|
||||
if rule["crypto"]:
|
||||
from crypto_verify import check_crypto_validation
|
||||
|
||||
return check_crypto_validation(local_path, filename, bios_dir)
|
||||
return None
|
||||
|
||||
|
||||
def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
"""Build per-filename validation rules from emulator profiles.
|
||||
|
||||
@@ -98,7 +176,9 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
"crypto_only": set(),
|
||||
"emulators": set(),
|
||||
"per_emulator": {},
|
||||
"rules": [],
|
||||
}
|
||||
index[fname]["rules"].append(_entry_rule(emu_name, f, checks))
|
||||
index[fname]["emulators"].add(emu_name)
|
||||
index[fname]["checks"].update(checks)
|
||||
# Track non-reproducible crypto checks
|
||||
@@ -143,8 +223,8 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
# Adler32 -stored as known_hash_adler32 field (not in validation: list
|
||||
# for Dolphin, but support it in both forms for future profiles)
|
||||
adler_val = f.get("known_hash_adler32") or f.get("adler32")
|
||||
if adler_val:
|
||||
norm = adler_val.lower()
|
||||
for value in adler_val if isinstance(adler_val, list) else [adler_val] if adler_val else []:
|
||||
norm = str(value).lower()
|
||||
if norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
index[fname]["adler32"].add(norm)
|
||||
@@ -233,12 +313,18 @@ def check_file_validation(
|
||||
filename: str,
|
||||
validation_index: dict[str, dict],
|
||||
bios_dir: str = "bios",
|
||||
emulators: set[str] | None = None,
|
||||
) -> tuple[str, list[str]] | None:
|
||||
"""Check emulator-level validation on a resolved file.
|
||||
|
||||
Supports: size (exact/min/max), crc32, md5, sha1, adler32,
|
||||
signature (RSA-2048 PKCS1v15 SHA256), crypto (AES-128-CBC + SHA256).
|
||||
|
||||
With ``emulators``, the cores that read the file's destination, each of
|
||||
them must accept it through one of its own entries for the name, the
|
||||
way each runs its own check. Without, the checks of every core naming
|
||||
the file are merged.
|
||||
|
||||
Returns None if all checks pass or no validation applies.
|
||||
Returns (reason, emulators) tuple on failure, where *emulators*
|
||||
lists only those cores whose check actually failed.
|
||||
@@ -246,6 +332,23 @@ def check_file_validation(
|
||||
entry = validation_index.get(filename)
|
||||
if not entry:
|
||||
return None
|
||||
if emulators is not None:
|
||||
hashes: dict = {}
|
||||
failures: dict[str, str] = {}
|
||||
for emu in sorted(emulators):
|
||||
rules = [r for r in entry.get("rules", []) if r["emulator"] == emu]
|
||||
if not rules:
|
||||
continue
|
||||
reasons = [
|
||||
_rule_failure(rule, local_path, filename, bios_dir, hashes)
|
||||
for rule in rules
|
||||
]
|
||||
if all(reasons):
|
||||
failures[emu] = reasons[0]
|
||||
if not failures:
|
||||
return None
|
||||
first = sorted(failures)[0]
|
||||
return failures[first], sorted(failures)
|
||||
checks = entry["checks"]
|
||||
pe = entry.get("per_emulator", {})
|
||||
|
||||
@@ -366,6 +469,7 @@ def find_validated_variant(
|
||||
validation_index: dict,
|
||||
bios_dir: str = "bios",
|
||||
platform_digest: str | None = None,
|
||||
emulators: set[str] | None = None,
|
||||
) -> str | None:
|
||||
"""A held file the emulator's own checks accept, in place of current_path.
|
||||
|
||||
@@ -424,7 +528,9 @@ def find_validated_variant(
|
||||
seen.add(real)
|
||||
if accepted and str(entry.get(platform_digest, "")).lower() not in accepted:
|
||||
continue
|
||||
if check_file_validation(path, fname, validation_index, bios_dir) is None:
|
||||
if check_file_validation(
|
||||
path, fname, validation_index, bios_dir, emulators
|
||||
) is None:
|
||||
return path
|
||||
return None
|
||||
|
||||
@@ -575,18 +681,24 @@ def validated_choice(
|
||||
return local_path, None
|
||||
name = file_entry.get("name", "")
|
||||
rules = validation_index.get(name)
|
||||
judges: set[str] | None = None
|
||||
if rules and owners and destination:
|
||||
owning = _owners_of(destination, name, owners)
|
||||
if owning and not owning & set(rules["emulators"]):
|
||||
if not owning & set(rules["emulators"]):
|
||||
# No core that checks this name reads this destination: PC-98's
|
||||
# bios.rom rules have no say over roms/ibmpcjr/bios.rom.
|
||||
return local_path, None
|
||||
# The cores that read this destination judge it, each by its own
|
||||
# entries.
|
||||
judges = owning & set(rules["emulators"])
|
||||
check = check_file_validation(
|
||||
local_path, file_entry.get("name", ""), validation_index, bios_dir
|
||||
local_path, file_entry.get("name", ""), validation_index, bios_dir, judges
|
||||
)
|
||||
if not check:
|
||||
return local_path, None
|
||||
better = find_validated_variant(
|
||||
file_entry, db, local_path, validation_index, bios_dir,
|
||||
platform_digest=platform_digest,
|
||||
platform_digest=platform_digest, emulators=judges,
|
||||
)
|
||||
if better:
|
||||
return better, None
|
||||
|
||||
@@ -97,8 +97,123 @@ class ValidatedChoice(unittest.TestCase):
|
||||
for call in ast.walk(node)
|
||||
)
|
||||
}
|
||||
self.assertLessEqual({"generate_pack", "generate_manifest"}, callers)
|
||||
self.assertLessEqual(
|
||||
{"generate_pack", "generate_manifest", "_manifest_core_entries"}, callers
|
||||
)
|
||||
|
||||
def test_core_extras_are_judged_too(self):
|
||||
"""The baseline loop judged a platform's own files; the core-extras loop
|
||||
shipped whatever the name found. Both loops live in generate_pack."""
|
||||
tree = ast.parse((REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8"))
|
||||
builder = next(
|
||||
node for node in ast.walk(tree)
|
||||
if isinstance(node, ast.FunctionDef) and node.name == "generate_pack"
|
||||
)
|
||||
calls = [
|
||||
call for call in ast.walk(builder)
|
||||
if isinstance(call, ast.Call) and getattr(call.func, "id", None) == "validated_choice"
|
||||
]
|
||||
self.assertGreaterEqual(len(calls), 2)
|
||||
|
||||
|
||||
class ManifestsServeWhatTheCoreAccepts(unittest.TestCase):
|
||||
"""RetroArch's GC/dsp_rom.bin was Dolphin's obsolete v0.3.1 free ROM while
|
||||
Nintendo's dump, which Dolphin's check accepts, sat in .variants/."""
|
||||
|
||||
def test_no_served_core_file_fails_its_check_beside_a_passing_variant(self):
|
||||
import json # noqa: PLC0415
|
||||
|
||||
from common import load_emulator_profiles, load_platform_config # noqa: PLC0415
|
||||
from generate_pack import _platform_cores # noqa: PLC0415
|
||||
from validation import check_file_validation # noqa: PLC0415
|
||||
|
||||
manifest_path = REPO_ROOT / "install" / "retroarch.json"
|
||||
if not (REPO_ROOT / "database.json").is_file() or not manifest_path.is_file():
|
||||
self.skipTest("database.json or the manifest is not built")
|
||||
db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8"))
|
||||
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
|
||||
config = load_platform_config("retroarch", str(REPO_ROOT / "platforms"))
|
||||
cores = {n: profiles[n] for n in _platform_cores(config, profiles)}
|
||||
index = _build_validation_index(cores)
|
||||
owners = destination_owners(cores)
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
wrong = []
|
||||
for entry in manifest["files"]:
|
||||
name = entry["dest"].rsplit("/", 1)[-1]
|
||||
path = entry.get("repo_path")
|
||||
if not entry.get("cores") or not path or name not in index:
|
||||
continue
|
||||
local = str(REPO_ROOT / path)
|
||||
if not Path(local).is_file() or not check_file_validation(local, name, index):
|
||||
continue
|
||||
chosen, _ = validated_choice(
|
||||
{"name": name}, local, db, index, str(REPO_ROOT / "bios"), None,
|
||||
entry["dest"], owners,
|
||||
)
|
||||
if chosen != local:
|
||||
wrong.append(entry["dest"])
|
||||
self.assertEqual(wrong, [])
|
||||
|
||||
|
||||
|
||||
class EachReaderJudgesWithItsOwnRules(unittest.TestCase):
|
||||
"""Merged by name, Azahar's 256-byte 3DS otp.bin rule judged Cemu's 1 KiB
|
||||
Wii U otp.bin, and the variant search gave Cemu the 3DS file."""
|
||||
|
||||
def test_rules_of_another_emulator_do_not_judge(self):
|
||||
from validation import check_file_validation # noqa: PLC0415
|
||||
|
||||
index = _build_validation_index({
|
||||
"azahar": {"files": [{"name": "otp.bin", "path": "sysdata/otp.bin",
|
||||
"size": 256, "validation": ["size"]}]},
|
||||
"cemu": {"files": [{"name": "otp.bin", "path": "Cemu/otp.bin",
|
||||
"size": 1024, "validation": ["size"]}]},
|
||||
})
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||
wiiu = Path(tmp) / "otp.bin"
|
||||
wiiu.write_bytes(b"w" * 1024)
|
||||
self.assertIsNone(check_file_validation(str(wiiu), "otp.bin", index, tmp, {"cemu"}))
|
||||
self.assertIsNotNone(check_file_validation(str(wiiu), "otp.bin", index, tmp, {"azahar"}))
|
||||
|
||||
def test_one_of_an_emulators_entries_is_enough(self):
|
||||
from validation import check_file_validation # noqa: PLC0415
|
||||
|
||||
index = _build_validation_index({"raze": {"files": [
|
||||
{"name": "SW.GRP", "size": 100, "validation": ["size"]},
|
||||
{"name": "SW.GRP", "size": 200, "validation": ["size"]},
|
||||
]}})
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||
grp = Path(tmp) / "SW.GRP"
|
||||
grp.write_bytes(b"s" * 200)
|
||||
self.assertIsNone(check_file_validation(str(grp), "SW.GRP", index, tmp, {"raze"}))
|
||||
|
||||
|
||||
class DolphinForksJudgeTheirOwnDspTable(unittest.TestCase):
|
||||
"""VerifyRoms (Source/Core/Core/DSP/DSPCore.cpp) accepts, without a prompt,
|
||||
Nintendo's pair and the newest free pair its table knows: v0.4 in
|
||||
Dolphin, PrimeHack, MMJR2 and Triforce, v0.3.1 in MMJR and Ishiiruka,
|
||||
whose table stops there. Adler-32 over the byte-swapped words."""
|
||||
|
||||
FREE_V04 = REPO_ROOT / "data" / "dolphin-sys" / "GC" / "dsp_rom.bin"
|
||||
FREE_V031 = REPO_ROOT / "bios" / "Nintendo" / "GameCube" / "Sys" / "GC" / "dsp_rom.bin"
|
||||
|
||||
def test_each_fork_accepts_its_own_free_rom(self):
|
||||
from common import load_emulator_profiles # noqa: PLC0415
|
||||
from validation import check_file_validation # noqa: PLC0415
|
||||
|
||||
if not self.FREE_V04.is_file() or not self.FREE_V031.is_file():
|
||||
self.skipTest("free DSP ROMs not present")
|
||||
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
|
||||
expect = {"dolphin": "v04", "primehack": "v04", "dolphin-mmjr2": "v04",
|
||||
"triforce": "v04", "dolphin-mmjr": "v031", "ishiiruka": "v031"}
|
||||
for name, accepted in expect.items():
|
||||
index = _build_validation_index({name: profiles[name]})
|
||||
with self.subTest(emulator=name):
|
||||
for label, path in (("v04", self.FREE_V04), ("v031", self.FREE_V031)):
|
||||
verdict = check_file_validation(
|
||||
str(path), "dsp_rom.bin", index, "bios", {name}
|
||||
)
|
||||
self.assertEqual(verdict is None, label == accepted, label)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user