fix: deliver MAME trees a search setting moves

A tree installed with MAME sits where its search setting points by
default. When a launcher passes the setting, MAME reads the folder it is
given and never the installed copy: RetroBat passes -hash bios/mame/hash
and -sp bios/mame/samples (Mame64.Generator.cs:30, 101, 106-107, 172,
177-178), KNULLI -samplepath /userdata/bios/mame/samples
(mameGenerator.py:129), RetroDECK samplepath $bios_path/mame-sa/samples
(mame/component_prepare.sh:94, 117). A bundled entry whose layout folder a
passed search setting (standalone_pools) binds is now delivered there.

A moved directory entry is found by its layout path and laid out below
the platform's folder, the folder proving nothing about content. A layout
tail two trees answer (MAME's hash/) falls back on the platform's tail. A
file the profile names itself only shadows a tree member of the same build
below the same root.

Manifests: RetroDECK gains the 19 floppy samples in bios/mame-sa/samples/
floppy. RetroBat's hash/ and samples/floppy and KNULLI's samples/floppy
reach the folders the libretro layout already fills, with the same files.
This commit is contained in:
Abdessamad Derraz committed 2026-11-10 14:04:54 +01:00
1 parent 6c41ff95ea
commit bd8cf8ed2c
2 files changed
+82 -5

No files matched your search

+48 -5
View File
@@ -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)}
+34
View File
@@ -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;<rom directory> (PSXMame.Generator.cs:38-48).
view = standalone_view("psxmame", "retrobat")