mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-17 08:53:28 -05:00
feat: let a single check warn while the others refuse
This commit is contained in:
1 parent
ee377d2f57
commit
18af962cb3
4 files changed
+80
-22
No files matched your search
@@ -73,6 +73,7 @@ files:
|
|||||||
required: false
|
required: false
|
||||||
size: [288, 320]
|
size: [288, 320]
|
||||||
validation: [size, signature]
|
validation: [size, signature]
|
||||||
|
on_mismatch: {signature: warn}
|
||||||
note: "console-unique key seed; 288 bytes without the AES MAC block, 320 with it"
|
note: "console-unique key seed; 288 bytes without the AES MAC block, 320 with it"
|
||||||
source_ref: "src/core/hw/unique_data.cpp:170-200, src/core/hw/unique_data.cpp:217, src/core/hw/unique_data.h:103-133"
|
source_ref: "src/core/hw/unique_data.cpp:170-200, src/core/hw/unique_data.cpp:217, src/core/hw/unique_data.h:103-133"
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ files:
|
|||||||
required: false
|
required: false
|
||||||
size: 273
|
size: 273
|
||||||
validation: [size, signature]
|
validation: [size, signature]
|
||||||
|
on_mismatch: {signature: warn}
|
||||||
note: "console serial and region data"
|
note: "console serial and region data"
|
||||||
source_ref: "src/core/hw/unique_data.cpp:43-92, src/core/hw/unique_data.cpp:205"
|
source_ref: "src/core/hw/unique_data.cpp:43-92, src/core/hw/unique_data.cpp:205"
|
||||||
|
|
||||||
@@ -91,5 +93,6 @@ files:
|
|||||||
required: false
|
required: false
|
||||||
size: 272
|
size: 272
|
||||||
validation: [size, signature]
|
validation: [size, signature]
|
||||||
|
on_mismatch: {signature: warn}
|
||||||
note: "friend code generation seed"
|
note: "friend code generation seed"
|
||||||
source_ref: "src/core/hw/unique_data.cpp:94-123, src/core/hw/unique_data.cpp:209, src/core/hw/unique_data.h:76"
|
source_ref: "src/core/hw/unique_data.cpp:94-123, src/core/hw/unique_data.cpp:209, src/core/hw/unique_data.h:76"
|
||||||
@@ -657,10 +657,27 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"on_mismatch": {
|
"on_mismatch": {
|
||||||
"description": "What the code does when a declared check fails: reject the file (default) or warn and run with it.",
|
"description": "What the code does when a declared check fails: reject the file (default) or warn and run with it, for every check or check by check.",
|
||||||
"enum": [
|
"oneOf": [
|
||||||
"reject",
|
{
|
||||||
"warn"
|
"enum": [
|
||||||
|
"reject",
|
||||||
|
"warn"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"minProperties": 1,
|
||||||
|
"additionalProperties": false,
|
||||||
|
"patternProperties": {
|
||||||
|
"^(size|crc32|md5|sha1|sha256|adler32|signature|crypto)$": {
|
||||||
|
"enum": [
|
||||||
|
"reject",
|
||||||
|
"warn"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+41
-18
@@ -95,31 +95,52 @@ def _entry_rule(emu_name: str, f: dict, checks: set[str]) -> dict:
|
|||||||
},
|
},
|
||||||
"adler32_byteswap": bool(f.get("adler32_byteswap")),
|
"adler32_byteswap": bool(f.get("adler32_byteswap")),
|
||||||
"crypto": {c for c in checks if c in _CRYPTO_CHECKS},
|
"crypto": {c for c in checks if c in _CRYPTO_CHECKS},
|
||||||
# A failed check the code only warns about: the file still runs.
|
# The checks whose failure the code only warns about: the file runs.
|
||||||
"warn": f.get("on_mismatch") == "warn",
|
"warn": warned_checks(f, checks),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def warned_checks(f: dict, checks: set[str]) -> set[str]:
|
||||||
|
"""The checks of an entry the code warns about instead of refusing.
|
||||||
|
|
||||||
|
`on_mismatch: warn` covers every check; a map names them one by one, as
|
||||||
|
Azahar refuses a SecureInfo_A of the wrong size and only logs a bad
|
||||||
|
signature.
|
||||||
|
"""
|
||||||
|
level = f.get("on_mismatch")
|
||||||
|
if level == "warn":
|
||||||
|
return set(checks)
|
||||||
|
if isinstance(level, dict):
|
||||||
|
return {check for check, value in level.items() if value == "warn"} & set(checks)
|
||||||
|
return set()
|
||||||
|
|
||||||
|
|
||||||
def _rule_failure(
|
def _rule_failure(
|
||||||
rule: dict, local_path: str, filename: str, bios_dir: str, hashes: dict,
|
rule: dict, local_path: str, filename: str, bios_dir: str, hashes: dict,
|
||||||
|
strict: bool = False,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Why one entry's checks reject a file, or None when they accept it."""
|
"""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"]:
|
A check the code only warns about counts under ``strict`` alone.
|
||||||
accepted = ",".join(str(s) for s in sorted(rule["sizes"]))
|
"""
|
||||||
return f"size mismatch: got {actual_size}, accepted [{accepted}]"
|
skip = set() if strict else rule["warn"]
|
||||||
if rule["min_size"] is not None and actual_size < rule["min_size"]:
|
if "size" not in skip:
|
||||||
return f"size too small: min {rule['min_size']}, got {actual_size}"
|
actual_size = os.path.getsize(local_path)
|
||||||
if rule["max_size"] is not None and actual_size > rule["max_size"]:
|
if rule["sizes"] and actual_size not in rule["sizes"]:
|
||||||
return f"size too large: max {rule['max_size']}, got {actual_size}"
|
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"):
|
for hash_type in ("crc32", "md5", "sha1", "sha256"):
|
||||||
if rule[hash_type]:
|
if rule[hash_type] and hash_type not in skip:
|
||||||
if not hashes:
|
if not hashes:
|
||||||
hashes.update(compute_hashes(local_path))
|
hashes.update(compute_hashes(local_path))
|
||||||
if hashes[hash_type].lower() not in rule[hash_type]:
|
if hashes[hash_type].lower() not in rule[hash_type]:
|
||||||
accepted = ",".join(sorted(rule[hash_type]))
|
accepted = ",".join(sorted(rule[hash_type]))
|
||||||
return f"{hash_type} mismatch: got {hashes[hash_type]}, accepted [{accepted}]"
|
return f"{hash_type} mismatch: got {hashes[hash_type]}, accepted [{accepted}]"
|
||||||
if rule["adler32"]:
|
if rule["adler32"] and "adler32" not in skip:
|
||||||
if rule["adler32_byteswap"]:
|
if rule["adler32_byteswap"]:
|
||||||
actual = _adler32_byteswapped(local_path)
|
actual = _adler32_byteswapped(local_path)
|
||||||
else:
|
else:
|
||||||
@@ -129,7 +150,7 @@ def _rule_failure(
|
|||||||
if actual not in rule["adler32"]:
|
if actual not in rule["adler32"]:
|
||||||
accepted = ",".join(sorted(rule["adler32"]))
|
accepted = ",".join(sorted(rule["adler32"]))
|
||||||
return f"adler32 mismatch: got 0x{actual}, accepted [{accepted}]"
|
return f"adler32 mismatch: got 0x{actual}, accepted [{accepted}]"
|
||||||
if rule["crypto"]:
|
if rule["crypto"] - skip:
|
||||||
from crypto_verify import check_crypto_validation
|
from crypto_verify import check_crypto_validation
|
||||||
|
|
||||||
return check_crypto_validation(local_path, filename, bios_dir)
|
return check_crypto_validation(local_path, filename, bios_dir)
|
||||||
@@ -182,8 +203,11 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
|||||||
}
|
}
|
||||||
index[fname]["rules"].append(_entry_rule(emu_name, f, checks))
|
index[fname]["rules"].append(_entry_rule(emu_name, f, checks))
|
||||||
index[fname]["emulators"].add(emu_name)
|
index[fname]["emulators"].add(emu_name)
|
||||||
if f.get("on_mismatch") == "warn":
|
# A warned check is never a rejection, so it stays out of the
|
||||||
# Never a rejection, so not part of the merged rejection rules.
|
# merged rejection rules.
|
||||||
|
warned = warned_checks(f, set(checks))
|
||||||
|
checks = [check for check in checks if check not in warned]
|
||||||
|
if not checks:
|
||||||
continue
|
continue
|
||||||
index[fname]["checks"].update(checks)
|
index[fname]["checks"].update(checks)
|
||||||
# Track non-reproducible crypto checks
|
# Track non-reproducible crypto checks
|
||||||
@@ -348,8 +372,7 @@ def check_file_validation(
|
|||||||
if not rules:
|
if not rules:
|
||||||
continue
|
continue
|
||||||
reasons = [
|
reasons = [
|
||||||
None if rule["warn"] and not strict
|
_rule_failure(rule, local_path, filename, bios_dir, hashes, strict)
|
||||||
else _rule_failure(rule, local_path, filename, bios_dir, hashes)
|
|
||||||
for rule in rules
|
for rule in rules
|
||||||
]
|
]
|
||||||
if all(reasons):
|
if all(reasons):
|
||||||
|
|||||||
@@ -329,5 +329,20 @@ class AltirraHappy810(_Probe):
|
|||||||
self.assertTrue(self.accepts("altirra", "happy-810-prev7-4k.rom", b"\5" * 5000))
|
self.assertTrue(self.accepts("altirra", "happy-810-prev7-4k.rom", b"\5" * 5000))
|
||||||
|
|
||||||
|
|
||||||
|
class AzaharConsoleUniqueData(_Probe):
|
||||||
|
"""LoadSecureInfoA, LoadLocalFriendCodeSeedB and LoadMovable refuse a
|
||||||
|
file of the wrong size, and only log a failed signature: the data is
|
||||||
|
loaded and used (src/core/hw/unique_data.cpp:43-123, 165-202,
|
||||||
|
src/core/hle/service/cfg/cfg.cpp:405-410 at 9dd12da)."""
|
||||||
|
|
||||||
|
def test_size_refuses_and_signature_only_warns(self):
|
||||||
|
for name, size in (("SecureInfo_A", 273), ("LocalFriendCodeSeed_B", 272),
|
||||||
|
("movable.sed", 288)):
|
||||||
|
with self.subTest(name=name):
|
||||||
|
self.assertTrue(self.accepts("azahar", name, b"\0" * size))
|
||||||
|
self.assertFalse(self.accepts("azahar", name, b"\0" * size, strict=True))
|
||||||
|
self.assertFalse(self.accepts("azahar", name, b"\0" * (size + 7)))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
Reference in new issue
Block a user