From 2ba0effd3b63a72905cc88d44464415d9b955545 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 18:52:04 +0200 Subject: [PATCH] fix: check emudeck folders by any file An EmuDeck entry names no file: the system passes when any file directly in the BIOS folder has one of its md5, as checkBIOS.sh loops over "$biosPath/"*. The script looked each one up as a file named system:md5 and reported 134 of 170 entries missing. --- scripts/check_platform_folder.py | 37 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/scripts/check_platform_folder.py b/scripts/check_platform_folder.py index f57e86cd..c5843f4d 100644 --- a/scripts/check_platform_folder.py +++ b/scripts/check_platform_folder.py @@ -14,49 +14,50 @@ from pathlib import Path API = "https://abdess.github.io/retrobios/api/v1/platforms/" -# The digest each check mode compares. A mode missing here (existence) only -# asks that the file be present. +# The digest each mode compares. Without one (existence) a file need only be present. DIGESTS = {"md5": "md5", "sha1": "sha1"} def accepts(entry: dict, path: Path, mode: str) -> bool: """Whether the platform's own check passes the file at path.""" - digest = DIGESTS.get(mode) - raw = entry.get(mode) or "" - values = raw if isinstance(raw, list) else str(raw).split(",") - declared = [value.strip().lower() for value in values if value.strip()] - if digest is None or not declared: + digest, raw = DIGESTS.get(mode), entry.get(mode) or "" + values = [v.strip().lower() for v in (raw if isinstance(raw, list) else str(raw).split(",")) if v.strip()] + if digest is None or not values: return path.is_file() # nothing to compare: the platform checks presence try: if entry.get("zipped_file"): # the hash of one member, its name casefolded with zipfile.ZipFile(path) as archive: names = {name.casefold(): name for name in archive.namelist()} member = names.get(entry["zipped_file"].casefold()) - return bool(member) and hashlib.new(digest, archive.read(member)).hexdigest() in declared + return bool(member) and hashlib.new(digest, archive.read(member)).hexdigest() in values actual = hashlib.new(digest, path.read_bytes()).hexdigest() - if entry.get("zip_digest") != "composite" and any(actual.startswith(d) for d in declared): + if entry.get("zip_digest") != "composite" and any(actual.startswith(v) for v in values): return True # a truncated md5 (Batocera) compares by its prefix if digest == DIGESTS["md5"] and path.suffix.lower() == ".zip": # Recalbox: members in name order with zipfile.ZipFile(path) as archive: names = sorted(name for name in archive.namelist() if not name.endswith("/")) - return hashlib.md5(b"".join(map(archive.read, names))).hexdigest() in declared + return hashlib.md5(b"".join(map(archive.read, names))).hexdigest() in values except (OSError, zipfile.BadZipFile): return False return False def check(platform: str, root: Path, api: str = API) -> list[tuple[str, str]]: - """(destination, ok | wrong | missing) for each entry of the list.""" + """(destination, ok | wrong | missing) for each entry. Entries with no destination + (EmuDeck) name no file: their system passes when any file in root has their hash.""" with urllib.request.urlopen(f"{api}{platform}.json") as response: contract = json.load(response)["items"][0]["contract"] - mode = contract["verification_mode"] - results = [] - for system in contract["systems"].values(): - for entry in system.get("files") or []: - destination = entry.get("destination") or entry["name"] - path = root / destination + mode, results = contract["verification_mode"], [] + loose = [path for path in root.iterdir() if path.is_file()] if root.is_dir() else [] + for system_id, system in contract["systems"].items(): + listed = [e for e in system.get("files") or [] if not e.get("destination")] + for entry in (e for e in system.get("files") or [] if e.get("destination")): + path = root / entry["destination"] verdict = "ok" if accepts(entry, path, mode) else "wrong" if path.is_file() else "missing" - results.append((destination, verdict)) + results.append((entry["destination"], verdict)) + if listed: + held = any(accepts(e, path, mode) for e in listed for path in loose) + results.append((f"{system_id}: any file", "ok" if held else "missing")) return results