diff --git a/scripts/common.py b/scripts/common.py index cb4149de5..cb3e294d1 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -2760,27 +2760,49 @@ def expand_directory_entries( if not any(entry.get("type") == "directory" for entry in files): return files + # A file the profile names itself is left to that entry, in the same + # build and below the same root: MAME's libretro floppy samples share + # their path with the standalone tree RetroPie reads below roms/. + other_build = "libretro" if standalone else "standalone" named = { - destination(entry) for entry in files if entry.get("type") != "directory" + (entry.get("root") or "", destination(entry)) for entry in files + if entry.get("type") != "directory" and entry.get("mode") != other_build } expanded: list[dict] = [] for entry in files: if entry.get("type") != "directory": expanded.append(entry) continue - members = directory_members(db, destination(entry)) + target = destination(entry) + # A directory a platform moved (platform_layouts) is the profile's + # tree at the platform's place: the collection holds it under the + # layout path, and the platform's folder proves nothing (RetroDECK + # hands MAME samplepath $bios_path/mame-sa/samples). + layout = str(entry.get("layout_path") or "") if standalone else "" + members = directory_members(db, layout) if layout else {} + if not members: + # A layout tail two trees answer (MAME's hash/) leaves the + # platform's own tail. + layout = "" + members = directory_members(db, target) + if layout and layout.strip("/") != target.strip("/"): + source, place = layout.strip("/") + "/", target.strip("/") + "/" + members = { + place + dest[len(source):]: sha1 + for dest, sha1 in members.items() if dest.startswith(source) + } if not members: expanded.append(entry) continue inherited = { key: value for key, value in entry.items() - if key not in ("type", "standalone_path") + if key not in ("type", "standalone_path", "layout_path") } expanded.extend( {**inherited, "name": dest, "path": dest, "sha1": sha1} for dest, sha1 in members.items() - if dest not in named + if (entry.get("root") or "", dest) not in named ) return expanded @@ -3274,6 +3296,20 @@ def platform_layouts( def bindings_of(entry: dict) -> dict[str, tuple[str, str]]: return bindings_for(launch_systems(entry)) + # The layout folders a search setting stands for (standalone_pools): + # bound, the launcher passes the setting and the default is not read. + pool_folders = { + str(v).strip("/") for v in (profile.get("standalone_pools") or {}).values() + } - {""} + + def passed_setting(entry: dict) -> bool: + layout = str(standalone_layout_path(entry, profile) or "") + bindings = bindings_of(entry) + return any( + folder in bindings and (layout == folder or layout.startswith(folder + "/")) + for folder in pool_folders + ) + def destinations(entry: dict) -> list[tuple[str, str] | None]: """Each place the entry's launches read it from, in launch order. @@ -3316,7 +3352,14 @@ def platform_layouts( if not places: files.append({**f, "unplaced": OUTSIDE_BIOS_FOLDER}) continue - files.extend(_placed_entry(f, profile, placed) for placed in places) + moved = f + if f.get("bundled") and passed_setting(f): + # The copy installed with the emulator sits where the setting + # pointed before the launcher moved it: RetroBat passes MAME + # -hash bios/mame/hash (Mame64.Generator.cs:172, 177-178), and + # the hash/ beside mame.exe is no longer read. + moved = {k: v for k, v in f.items() if k != "bundled"} + files.extend(_placed_entry(moved, profile, placed) for placed in places) standalone = {**profile, "files": files} dual = {c.removeprefix(DUAL_MODE_PREFIX) for c in standalone_cores if c.startswith(DUAL_MODE_PREFIX)} diff --git a/tests/test_launcher_placement.py b/tests/test_launcher_placement.py index 6e4d1558a..605776250 100644 --- a/tests/test_launcher_placement.py +++ b/tests/test_launcher_placement.py @@ -851,6 +851,40 @@ class MameSearchPaths(unittest.TestCase): e.get("unplaced") == "outside_bios_folder" for e in self.sets(view) )) + def test_a_passed_search_setting_leaves_the_bundled_tree_unread(self) -> None: + # RetroBat passes -hash bios/mame/hash and -sp bios/mame/samples + # (Mame64.Generator.cs:30, 101, 106-107, 172, 177-178), RetroDECK + # writes samplepath $bios_path/mame-sa/samples (component_prepare.sh:94): + # the trees beside the executable are not read, the pack brings them. + for platform, tree, place in ( + ("retrobat", "hash/", "mame/hash/"), + ("retrobat", "floppy/", "mame/samples/floppy/"), + ("knulli", "floppy/", "mame/samples/floppy/"), + ("retrodeck", "floppy/", "mame-sa/samples/floppy/"), + ): + view = standalone_view("mame", platform) + (entry,) = [e for e in view["files"] + if e["name"] == tree and e.get("mode") == "standalone"] + self.assertEqual(entry["standalone_path"], place, platform) + self.assertNotIn("bundled", entry, platform) + # Where no launcher passes the setting, the bundled copy is read. + view = standalone_view("mame", "emudeck") + (entry,) = [e for e in view["files"] + if e["name"] == "hash/" and e.get("mode") == "standalone"] + self.assertTrue(entry.get("bundled")) + + def test_a_moved_tree_is_found_by_its_layout_path(self) -> None: + from common import expand_directory_entries, load_database + + db = load_database(str(REPO_ROOT / "database.json")) + view = standalone_view("mame", "retrodeck") + (tree,) = [e for e in view["files"] + if e["name"] == "floppy/" and e.get("mode") == "standalone"] + members = expand_directory_entries([tree], db, standalone=True) + self.assertEqual(len(members), 19) + for member in members: + self.assertTrue(member["path"].startswith("mame-sa/samples/floppy/")) + def test_psxmame_reads_bios_psxmame_bios(self) -> None: # -rompath bios/psxmame/bios; (PSXMame.Generator.cs:38-48). view = standalone_view("psxmame", "retrobat")