From 9002a4a4bb9fb165095e710c5022fc831e2548e6 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sat, 10 Oct 2026 07:59:18 +0200 Subject: [PATCH] fix: lay a hash pack out as its emulator does --- scripts/generate_pack.py | 44 +++++++++++++++++++++++++++++----- tests/test_md5_pack_context.py | 12 ++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 2ba1d25c..283727db 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1826,6 +1826,33 @@ def generate_split_packs( return results +def _entry_for_file(candidates: list[dict], held: dict) -> dict: + """The declaration a held file answers when several share its name. + + BBKEmu declares 8.BIN once per model; the last one won, and the A4980 + dump went to the A4988 folder. The declared hash decides, then the + destination the collection's own path ends with, then the last one. + """ + def declared(fe: dict, key: str) -> set[str]: + value = fe.get(key) + values = value if isinstance(value, list) else str(value or "").split(",") + return {str(v).strip().lower() for v in values if str(v).strip()} + + for fe in candidates: + if any( + str(held.get(key, "")).lower() in declared(fe, key) + for key in ("sha1", "md5", "sha256", "crc32") + if held.get(key) + ): + return fe + held_paths = [str(held.get("path", ""))] + [str(p) for p in held.get("paths") or []] + for fe in candidates: + tail = str(fe.get("path") or fe.get("destination") or "").lower() + if tail and any(path.lower().endswith("/" + tail) for path in held_paths): + return fe + return candidates[-1] + + def generate_md5_pack( hashes: list[tuple[str, str]], db: dict, @@ -1845,7 +1872,7 @@ def generate_md5_pack( if zip_contents is None: zip_contents = {} - plat_file_index: dict[str, dict] = {} + plat_file_index: dict[str, list[dict]] = {} base_dest = "" plat_display = "Custom" if platform_name and platforms_dir: @@ -1854,7 +1881,7 @@ def generate_md5_pack( plat_display = config.get("platform", platform_name) for _sys_id, system in config.get("systems", {}).items(): for fe in system.get("files", []): - plat_file_index[fe.get("name", "").lower()] = fe + plat_file_index.setdefault(fe.get("name", "").lower(), []).append(fe) emu_pack_structure = None emu_display = "" @@ -1873,9 +1900,9 @@ def generate_md5_pack( emu_display = profile.get("emulator", emulator_name) emu_pack_structure = profile.get("pack_structure") for fe in profile.get("files", []): - plat_file_index[fe.get("name", "").lower()] = fe + plat_file_index.setdefault(fe.get("name", "").lower(), []).append(fe) for alias in fe.get("aliases", []): - plat_file_index[alias.lower()] = fe + plat_file_index.setdefault(alias.lower(), []).append(fe) context_name = plat_display if platform_name else (emu_display or "Custom") # --standalone changes the destination layout, so a run with it must not @@ -1920,11 +1947,16 @@ def generate_md5_pack( matched_fe = None for lookup_name in [name] + aliases: if lookup_name.lower() in plat_file_index: - matched_fe = plat_file_index[lookup_name.lower()] + matched_fe = _entry_for_file( + plat_file_index[lookup_name.lower()], entry + ) break if matched_fe: - if emulator_name and emu_pack_structure is not None: + if emulator_name: + # Laid out as the emulator pack lays it out: a profile + # without pack_structure still names its path and its + # standalone_path, and --standalone has to reach them. dest = _resolve_destination( matched_fe, emu_pack_structure, standalone ) diff --git a/tests/test_md5_pack_context.py b/tests/test_md5_pack_context.py index 4b9fa92a..65f1d9c5 100644 --- a/tests/test_md5_pack_context.py +++ b/tests/test_md5_pack_context.py @@ -50,5 +50,17 @@ class TheEmulatorContextIsChecked(unittest.TestCase): self.assertEqual(names, []) + def test_the_standalone_layout_reaches_a_profile_without_structure(self): + """BBKEmu has no pack_structure; its A4980 8.BIN goes to + system/BBKEmu/A4980/ standalone. The hash pack kept the bare name, + then, once laid out, sent it to the A4988 folder whose entry shares + the name.""" + md5 = "ddfc001a6859d63ed46368ea7fe9f20c" + if not any(record.get("md5") == md5 for record in self.db["files"].values()): + self.skipTest("the A4980 8.BIN is not collected") + proc, names = _pack("--from-md5", md5, "--emulator", "bbkemu", "--standalone") + self.assertEqual(proc.returncode, 0, proc.stderr) + self.assertEqual(names, ["system/BBKEmu/A4980/8.BIN"]) + if __name__ == "__main__": unittest.main()