feat: read ES-DE MAME search paths

ES-DE starts standalone MAME with -rompath %GAMEDIR%\;%ROMPATH%/<system>
in 78 live commands across 54 systems (resources/systems/linux/
es_systems.xml at retrodeck-main-20261103-162710, 15e1b236; two more
are commented out). The command line outranks mame.ini, so RetroDECK's
MAME reads its sets in the roms folder each command names and in no
BIOS folder. The ES-DE rom folders parser now records, per folder, the
folders a standalone command passes as its ROM search path and the
machines it starts, or that the game is the machine (%BASENAME%).
Rescraped at the pinned revisions: retrodeck, emudeck and
emudeck-windows gain these records and nothing else.

A command reads the sets its machines load: the machine's own set, the
BIOS sets on its romof chain and the device sets with ROMs it
instantiates, from games/arcade/mame.json (mame0289 -listxml). That
index now also lists the game and device sets of each driver source
file, so a %BASENAME% folder reads for its board: neogeo and stv by
their BIOS root, cps1-3 and model2 by source file, daphne and laserdisc
by the laserdisc player devices, gameandwatch and lcdgames by the
handheld sources, any other (arcade, mame, consolearcade) for every
machine. The published installer copy of the index leaves the sources
out.

Manifests: RetroDECK gains 1399 files, 378.2 MB, below roms/. Of the 219
sets named as read outside the BIOS folder, 58 reach 146 files (25.9 MB),
161 stay named, no command's machine reading them. 441 sets RetroDECK's
list also declares at bios/ reach 1253 files (352.3 MB), 429 each in
arcade and mame and 480 in consolearcade. No other platform changes.
This commit is contained in:
Abdessamad Derraz committed 2026-11-11 11:17:06 +01:00
1 parent a8cfd20f49
commit 7a3d91585d
14 files changed
+1452 -10

No files matched your search

@@ -222,6 +222,13 @@ def build_index(
for device in set(entry.devices) & device_roms:
devices.setdefault(device, set()).add(name)
with_bios = {g for members in bios.values() for g in members}
# The source file of each game and device set: what a launcher that
# starts whatever set a folder holds selects a folder's machines by
# (ES-DE runs MAME on any CPS-2 set of roms/cps2).
sources: dict[str, set[str]] = {}
for name, entry in sets.items():
if entry.source and (name in games or name in device_roms):
sources.setdefault(entry.source, set()).add(archive(name, name))
return {
"bios": {k: sorted(v) for k, v in sorted(bios.items())},
"devices": {k: sorted(v) for k, v in sorted(devices.items())},
@@ -229,6 +236,7 @@ def build_index(
"missing_parents": dict(sorted(missing.items())),
"no_bios": sorted(set(games) - with_bios - set(missing)),
"bios_sets": sorted(bios_names),
**({"sources": {k: sorted(v) for k, v in sorted(sources.items())}} if sources else {}),
"counts": {
"sets": len(sets),
"games": len(games),
+1 -1
View File
@@ -36,7 +36,7 @@ SETUP_KEYS = (
)
ROW_KEYS = (
"folder", "name", "aliases", "extensions", "files", "core", "emulator", "source_ref",
"board_defaults", "arcade", "note",
"board_defaults", "arcade", "searched_by", "note",
)
+69 -1
View File
@@ -24,6 +24,7 @@ Formats, by the shape of the source:
from __future__ import annotations
import html
import json
import re
import shlex
@@ -167,7 +168,7 @@ def _all_lines(text: str, pattern: str) -> dict[str, list[int]]:
for number, line in enumerate(text.splitlines(), 1):
match = re.search(pattern, line)
if match:
out.setdefault(match.group(1).strip(), []).append(number)
out.setdefault(html.unescape(match.group(1)).strip(), []).append(number)
return out
@@ -522,6 +523,64 @@ def _alternative_emulators(text: str, function: str) -> dict[str, tuple[str, int
return out
_ROMPATH_OPTIONS = ("-rompath", "-rp")
def _searched_by(commands: list[tuple[str, str, int | None]], key: str, path: str) -> list[dict]:
"""The ROM folders each standalone emulator's commands pass it, by emulator.
ES-DE starts standalone MAME with `-rompath %GAMEDIR%\\;%ROMPATH%/<system>`
and a machine, or %BASENAME% where the game is the machine. The command
line outranks mame.ini. %GAMEDIR% is the game's own folder, no folder a
pack fills.
"""
found: dict[str, dict] = {}
for _label, command, line in commands:
emulator = _EMULATOR.search(command)
if emulator is None or _RA_CORE.search(command):
continue
words = command.replace("\\;", ";").split()
at = next((i for i, w in enumerate(words) if w in _ROMPATH_OPTIONS), None)
if at is None or at + 1 >= len(words):
continue
reads = [
folder for part in words[at + 1].split(";")
if (folder := _strip_prefix(part, ["%ROMPATH%/", "%ROMPATH%\\"]))
]
if not reads:
continue
name = emulator.group(1).lower()
record = found.setdefault(name, {"emulator": name, "reads": [], "machines": [],
"any_machine": False, "lines": []})
record["reads"] += [r for r in reads if r not in record["reads"]]
machine = words[at + 2] if at + 2 < len(words) else ""
if not machine or machine.startswith("%"):
record["any_machine"] = True
elif machine not in record["machines"]:
record["machines"].append(machine)
record["lines"].append(line)
rows = []
for record in found.values():
row = {"emulator": record["emulator"], "reads": record["reads"]}
if record["machines"]:
row["machines"] = record["machines"]
if record["any_machine"]:
row["any_machine"] = True
row["source_ref"] = _ref(key, path, *record["lines"])
rows.append(row)
return rows
def _command_lines(text: str) -> dict[str, list[int]]:
"""Command text -> the lines it is written on, in file order."""
out: dict[str, list[int]] = {}
for number, line in enumerate(text.splitlines(), 1):
match = re.search(r"<command\b[^>]*>([^<]*)</command>", line)
if match:
out.setdefault(match.group(1).strip(), []).append(number)
return out
@register("esde")
def esde(spec: dict, fetcher: Fetcher) -> list[dict]:
"""Each system of ES-DE's list whose path is %ROMPATH%/<folder>.
@@ -537,13 +596,21 @@ def esde(spec: dict, fetcher: Fetcher) -> list[dict]:
key, path = item["source"], item["path"]
text = _lenient_xml(fetcher.text(key, path))
lines = _first_lines(text, r"<name>([^<]+)</name>")
command_lines = _command_lines(text)
for name, rom_path, extensions, commands in _esde_systems(text, path):
folder = _strip_prefix(rom_path, ["%ROMPATH%/", "%ROMPATH%\\"])
if not name or not folder:
continue
start = lines.get(name) or 0
placed = [
(label, command, next(
(n for n in command_lines.get(command, []) if n >= start), None))
for label, command in commands
]
systems[name] = {
"folder": folder, "extensions": extensions, "commands": commands,
"ref": _ref(key, path, lines.get(name)),
"searched_by": _searched_by(placed, key, path),
}
alternatives: dict[str, tuple[str, int]] = {}
alt = params.get("alternatives")
@@ -580,6 +647,7 @@ def esde(spec: dict, fetcher: Fetcher) -> list[dict]:
"emulator": emulator,
"source_ref": "; ".join(refs),
"note": note,
"searched_by": system["searched_by"] or None,
})
return rows