From 0c53d165c144005fb02a573d88de79b0bce3f78a Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:50:26 +0200 Subject: [PATCH] fix: parse member md5 lists one way --- scripts/packverify.py | 3 ++- scripts/validation.py | 2 +- scripts/verify.py | 9 +++++---- tests/test_audit_regressions.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/scripts/packverify.py b/scripts/packverify.py index b3da663b..0fe648e7 100644 --- a/scripts/packverify.py +++ b/scripts/packverify.py @@ -22,6 +22,7 @@ from nativemode import hash_mismatch_excludes_file import functools import hashlib from common import filter_systems_by_target +from hashing import parse_md5_list from common import load_emulator_profiles from common import load_platform_config import os @@ -447,7 +448,7 @@ def _repo_satisfies_declaration( def _repo_holds_member(file_entry: dict, db: dict) -> bool: """Whether a held archive of that name carries the declared member.""" files_db = db.get("files", {}) - declared = [m.strip() for m in str(file_entry.get("md5") or "").split(",") if m.strip()] + declared = parse_md5_list(file_entry.get("md5")) for sha1 in db.get("indexes", {}).get("by_name", {}).get(file_entry.get("name", ""), []): path = (files_db.get(sha1) or {}).get("path", "") if not path.endswith(".zip") or not os.path.exists(path): diff --git a/scripts/validation.py b/scripts/validation.py index f4fe917f..fd03db6f 100644 --- a/scripts/validation.py +++ b/scripts/validation.py @@ -703,7 +703,7 @@ def inner_rom_check(file_entry: dict, local_path: str) -> str: matches, else for the last one tried. The pack and the install manifest both read it, so a file one of them ships is a file the other lists. """ - declared = [m.strip() for m in file_entry.get("md5", "").split(",") if m.strip()] + declared = parse_md5_list(file_entry.get("md5")) result = "not_in_zip" for candidate in declared or [""]: result = check_inside_zip(local_path, file_entry["zipped_file"], candidate) diff --git a/scripts/verify.py b/scripts/verify.py index bfd7d13d..dad280a6 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -34,6 +34,7 @@ from collections.abc import Mapping import slots sys.path.insert(0, os.path.dirname(__file__)) +from hashing import parse_md5_list from common import ( platform_arg, target_listing_line, @@ -190,10 +191,10 @@ def verify_entry_md5( required = file_entry.get("required", True) base = {"name": name, "required": required} - if expected_md5 and "," in expected_md5: - md5_list = [m.strip().lower() for m in expected_md5.split(",") if m.strip()] - else: - md5_list = [expected_md5] if expected_md5 else [] + # One parse for verify, the builder and the pack check: Recalbox writes + # md5 in uppercase, and a copy that lowercased only comma lists judged + # a single uppercase value differently from the others. + md5_list = parse_md5_list(expected_md5) if not local_path: return {**base, "status": Status.MISSING} diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index a764432b..6157777f 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -4770,5 +4770,37 @@ class TheCollectionShipsADat(unittest.TestCase): self.assertEqual(roms[("Sony - PlayStation", "scph5501.bin")].crc32, "8d8cb7e4") + +class ZippedFileMd5IsParsedOnce(unittest.TestCase): + """verify lowercased a comma md5 list, the builder and packverify did + not: an uppercase Recalbox-style md5 on a zipped_file entry was OK for + verify and a mismatch for the pack and the manifest.""" + + def test_an_uppercase_member_md5_is_accepted_everywhere(self): + import hashlib + import zipfile as zf_mod + + from scripts.validation import inner_rom_check + from scripts.verify import verify_entry_md5 + + with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory: + archive = Path(directory, "x.zip") + with zf_mod.ZipFile(archive, "w") as zf: + zf.writestr("rom.bin", b"rom") + member = hashlib.md5(b"rom").hexdigest().upper() + for md5 in (member, f"{member},{'0' * 32}"): + with self.subTest(md5=md5): + entry = {"name": "x.zip", "zipped_file": "rom.bin", "md5": md5} + self.assertEqual(inner_rom_check(entry, str(archive)), "ok") + self.assertEqual( + verify_entry_md5(entry, str(archive))["status"], "ok" + ) + + def test_the_three_sites_share_the_parser(self): + for module in ("validation.py", "packverify.py", "verify.py"): + source = (ROOT / "scripts" / module).read_text() + self.assertIn("parse_md5_list(", source, module) + + if __name__ == "__main__": unittest.main()