fix: check a recalbox zip by its composite only

Bios::Scan takes Zip::Md5Composite for every .zip (Bios.cpp:64-68). The resolver already honoured zip_digest: composite, but digest_verdict accepted the container's own md5 first, so the pack check passed bytes Recalbox marks red. The page described that order.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 09:57:06 +02:00
1 parent d8e9ad23d3
commit f997bdc81a
4 files changed
+52 -4

No files matched your search

+6 -1
View File
@@ -793,7 +793,12 @@ def digest_verdict(
actual = hashes()[algorithm].lower()
if algorithm != "md5":
return actual in declared
if any(actual == d or (len(d) < 32 and actual.startswith(d)) for d in declared):
# Recalbox hashes a .zip by its members only (Bios.cpp:64-68), so the
# container's own md5 answers nothing there.
composite_only = file_entry.get("zip_digest") == "composite"
if not composite_only and any(
actual == d or (len(d) < 32 and actual.startswith(d)) for d in declared
):
return True
member_digest = composite()
return bool(member_digest) and member_digest.lower() in declared
+24
View File
@@ -1206,6 +1206,30 @@ class OneBaselineVerdict(unittest.TestCase):
("frontend_digest_exact", None),
)
def test_a_composite_only_archive_ignores_its_raw_md5(self):
"""Recalbox hashes a .zip by its members (Bios.cpp:64-68), and the
pack check accepted the container's own md5 before the composite:
neocd.bin shipped as neocdz.zip passed while Recalbox marks it red."""
import hashlib
from scripts.common import md5_composite
from scripts.validation import digest_verdict
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
archive = self._archive(Path(directory), "rom.bin")
raw = hashlib.md5(Path(archive).read_bytes()).hexdigest()
members = md5_composite(archive)
for declared, accepted in ((raw, False), (members, True)):
entry = {"name": "dragon32.zip", "md5": declared, "zip_digest": "composite"}
with self.subTest(declared=declared):
self.assertIs(
digest_verdict(entry, 0, lambda: {"md5": raw}, "md5", "",
lambda: members),
accepted,
)
plain = {"name": "dragon32.zip", "md5": raw}
self.assertTrue(digest_verdict(plain, 0, lambda: {"md5": raw}, "md5"))
def test_a_release_asset_keeps_its_answer(self):
from scripts.slots import apply_override
+12
View File
@@ -322,6 +322,18 @@ class Md5CompositeDefinition(unittest.TestCase):
self.assertIn("contents of every member", section)
self.assertNotIn("inner file MD5s", section)
def test_composite_only_is_read_by_both_checks(self):
"""The page said the direct MD5 is tried before the composite, which
Recalbox never does for a .zip, and the pack check did the same."""
import common
import validation
section = _section(_page("verification-modes.md"), "md5_composite (Recalbox ZIP verification)")
self.assertIn("zip_digest: composite", section)
self.assertNotIn("is tried before reporting a mismatch", section)
for function in (common.resolve_local_file, validation.digest_verdict):
self.assertIn('get("zip_digest") == "composite"', inspect.getsource(function))
class ValidationIndexScope(unittest.TestCase):
"""The page said the index reads every profile. verify and the builder
+10 -3
View File
@@ -60,9 +60,16 @@ characters by prefix, and only for a file whose name the entry also names.
Recalbox hashes a ZIP with `Zip::Md5Composite` (`es-core/src/utils/Zip.cpp:71-96` in
recalbox-emulationstation at `f4cd0ae4`): one MD5 over the contents of every member,
read in filename order. The members' own MD5s are not involved. `verify.py` computes
the same value with `md5_composite()` (`scripts/hashing.py`). When a ZIP file's direct
MD5 does not match, the composite is tried before reporting a mismatch.
read in filename order. The members' own MD5s are not involved, and neither is the
MD5 of the ZIP file itself: `Bios::Scan` takes the composite for every `.zip`
(`es-app/src/bios/Bios.cpp:64-68`). `verify.py` computes the same value with
`md5_composite()` (`scripts/hashing.py`).
The registry gives Recalbox `zip_digest: composite`. For a platform so marked, a
`.zip` entry is checked against its composite alone, by the resolver
(`resolve_local_file`) and by the pack check (`digest_verdict` in
`scripts/validation.py`). On other platforms the ZIP file's direct MD5 is tried first
and the composite after it.
### Multi-hash (Recalbox)