From e787efca22b80ed4f3c4cdbd57f4ec5178664774 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:20:42 +0200 Subject: [PATCH] fix: match a size-checked entry to its launcher EmuDeck passes Complex_4627v1.03.bin as xemu's flash ROM (emuDeckXemu.sh:60-61), and xemu's entry Complex_4627.bin states no hash, only the sizes it maps. The layer matched entries by name or content only, so without the alias the default name was still asked for. A hashless entry that checks a size now takes a launcher file of a size it accepts, when no other entry of that core accepts that size too. --- scripts/launchers.py | 22 ++++++++++++++++++++++ tests/test_launcher_layer.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/scripts/launchers.py b/scripts/launchers.py index 14aed5f2..e12b21f5 100644 --- a/scripts/launchers.py +++ b/scripts/launchers.py @@ -233,6 +233,8 @@ class _Candidate: entry: dict names: set[str] by_content: bool + # Matched by the size the code checks, the entry naming no hash. + by_shape: bool = False class LauncherLayer: @@ -314,6 +316,22 @@ class LauncherLayer: } if content: return _Candidate(entry, content, True) + # A hashless entry the code checks by size: EmuDeck passes + # Complex_4627v1.03.bin as xemu's flash ROM, and xemu's entry + # Complex_4627.bin states only the sizes it maps. + checks = entry.get("validation") or [] + sized = any(entry.get(k) for k in ("size", "min_size", "max_size")) + if not _hashes(entry) and sized and "size" in checks: + shaped = { + place.fold(path.rsplit("/", 1)[-1]) + for path in place.paths + if any( + name_match_size_ok(entry, record.get("size")) + for record in self.files_named(place, path.rsplit("/", 1)[-1]) + ) + } + if shaped: + return _Candidate(entry, shaped, False, by_shape=True) return None def platform_holds(self, place: LauncherPlace) -> bool: @@ -360,6 +378,10 @@ class LauncherLayer: for entry, report in members if (candidate := self.match(place, entry)) is not None ] + # A size names one entry only when no other of the core's + # entries takes that size too. + if sum(c.by_shape for c, _ in matched) > 1: + matched = [(c, r) for c, r in matched if not c.by_shape] if not matched: continue filled = self._fill(place, matched, reported_at, resolve_held, placed) diff --git a/tests/test_launcher_layer.py b/tests/test_launcher_layer.py index 3dd9e064..f17efeac 100644 --- a/tests/test_launcher_layer.py +++ b/tests/test_launcher_layer.py @@ -201,6 +201,36 @@ class LauncherLayerTest(unittest.TestCase): added = [u["path"] for u in self._report() if u.get("launcher")] self.assertNotIn("sub/fix.bin", added) + def test_a_hashless_entry_takes_a_launcher_file_of_a_size_it_checks(self) -> None: + """EmuDeck passes Complex_4627v1.03.bin as xemu's flash ROM + (emuDeckXemu.sh:60-61), and xemu's Complex_4627.bin entry checks only + a size: matched neither by name nor by content, the default name was + still asked for.""" + data = b"F" * 64 + self.db["files"]["f" * 40] = {"path": os.path.join(self.bios_dir, "v103.bin"), + "name": "v103.bin", "md5": _md5(data), "sha1": "f" * 40, + "size": len(data), "crc32": ""} + with open(os.path.join(self.bios_dir, "v103.bin"), "wb") as fh: + fh.write(data) + self.db["indexes"]["by_name"]["v103.bin"] = ["f" * 40] + profile = { + "emulator": "FixEmu", "type": "standalone", "systems": ["test-sys"], + "files": [{"name": "flash.bin", "required": True, "size": [32, 64], + "validation": ["size"]}, + {"name": "boot.bin", "required": True, "size": 8, + "validation": ["size"]}], + } + with open(os.path.join(self.emulators_dir, "fixemu.yml"), "w") as fh: + yaml.dump(profile, fh) + self.config["launchers"][2]["names"] = ["v103.bin"] + self.config["launchers"][2]["search_dir"] = "/userdata/bios" + _emulator_profiles_cache.clear() + self.profiles = load_emulator_profiles(self.emulators_dir) + report = {u["path"]: u for u in self._report()} + self.assertNotIn("flash.bin", report) + self.assertIn("v103.bin", report) + self.assertIn("boot.bin", report) + def test_no_place_is_filled_twice(self) -> None: added = [u["path"] for u in self._report() if u.get("launcher")] self.assertEqual(sorted(added), ["e.bin", "sub/fix.bin"])