mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
ROCKNIX, EmuELEC, AmberELEC, EmuDeck and RetroDECK records citing a config now carry the code installing it, whether that keeps a file already there, and the launch code rewriting its keys. Rescraped at the pinned revisions. Records the shipped code never reads are no longer written: EmuELEC's mame.ini folders (MAME.opt turns paths off), AmberELEC's atari800.cfg (read only under an option that is off), the six RetroDECK XRoar ROM lists no machine looks up, and the ROCKNIX melonDS DSi BIOS keys (DSiExternalBIOSEnable is set by no shipped ini). Conditions added: ROCKNIX and AmberELEC amiberry kickstarts hold only outside a WHDLoad --autoload boot, ROCKNIX NanoBoyAdvance exits without its BIOS, the EmuELEC scummvm.ini copy runs from an action no launch passes, and the EmuDeck melonDS.toml keys need ExternalBIOSEnable (DS) or ConsoleType 1 (DSi).
456 lines
20 KiB
Python
456 lines
20 KiB
Python
"""AmberELEC's launcher layer, read from runemu.py and its emulator scripts.
|
|
|
|
EmulationStation hands every launch to runemu.py. A libretro core runs in
|
|
RetroArch or retrorun with the system directory their configs name, MAME
|
|
machines get a .cmd file with a `-rp` search path, and every other emulator
|
|
runs the script its standalone_emulators table names (/usr/bin/<name>.sh,
|
|
installed from the package). Those scripts, and the configs their packages
|
|
install, say which file under /storage/roms/bios is opened: yabasanshiro.sh
|
|
tests saturn_bios.bin and passes it with -b, Hatari's default config names
|
|
tos.img.
|
|
|
|
runemu.py is read with ast, the scripts with shell_launchers' readers. An
|
|
extractor whose construct is gone raises instead of returning nothing.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import posixpath
|
|
import re
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
try:
|
|
from .configgen_launchers import Launcher, Source
|
|
from .shell_launchers import (
|
|
LauncherPatternMissing,
|
|
Script,
|
|
config_placement,
|
|
describe_keys,
|
|
evidence_fields,
|
|
settings,
|
|
source_ref,
|
|
)
|
|
except ImportError:
|
|
from configgen_launchers import Launcher, Source # type: ignore[no-redef]
|
|
from shell_launchers import ( # type: ignore[no-redef]
|
|
LauncherPatternMissing,
|
|
Script,
|
|
config_placement,
|
|
describe_keys,
|
|
evidence_fields,
|
|
settings,
|
|
source_ref,
|
|
)
|
|
|
|
BIOS = "/storage/roms/bios"
|
|
RUNEMU = "packages/amberelec/sources/scripts/runemu.py"
|
|
RETROARCH_CFG = "packages/games/tools/retroarch/sources/retroarch.cfg"
|
|
RETRORUN = "packages/games/tools/retrorun/retrorun.sh"
|
|
HATARI_CONFIG = "packages/games/emulators/hatarisa/config/conf/Atari-ST-default.cfg"
|
|
# The first-boot copy of every package's /usr/config into /storage/.config.
|
|
USERCONFIG = "packages/sysutils/systemd/scripts/userconfig-setup"
|
|
AMIBERRY_PACKAGE = "packages/games/emulators/amiberry/package.mk"
|
|
AMIBERRY_CONFIGS = "packages/games/emulators/amiberry/config/conf"
|
|
FREEJ2ME = (
|
|
("freej2me", "packages/games/libretro/freej2me/freej2me.sh"),
|
|
("freej2me-plus", "packages/games/libretro/freej2me-plus/freej2me-plus.sh"),
|
|
)
|
|
EASYRPG = "packages/games/libretro/easyrpg/easyrpg.sh"
|
|
|
|
# The script each standalone_emulators entry runs, as the package installs it
|
|
# to /usr/bin. scripts() checks each against runemu's table.
|
|
SCRIPTS = {
|
|
"AMIBERRY": "packages/games/emulators/amiberry/scripts/amiberry.sh",
|
|
"HATARISA": "packages/games/emulators/hatarisa/scripts/hatari.sh",
|
|
"SCUMMVMSA": "packages/games/emulators/scummvmsa/scummvm.sh",
|
|
"pico8": "packages/amberelec/sources/pico-8.sh",
|
|
"piemu": "packages/games/emulators/piemu/bin/piemu.sh",
|
|
"yabasanshiroSA": "packages/games/emulators/yabasanshiroSA/yabasanshiro.sh",
|
|
}
|
|
|
|
Read = Callable[[str], str]
|
|
|
|
|
|
class Runemu:
|
|
"""runemu.py, parsed: the standalone table and the RetroArch command."""
|
|
|
|
def __init__(self, read: Read) -> None:
|
|
self.src = Source(RUNEMU, read(RUNEMU))
|
|
node = self.src.assignment("standalone_emulators")
|
|
if not isinstance(node.value, ast.Dict):
|
|
self.src.fail("standalone_emulators is not a dict display")
|
|
self.table: dict[str, tuple[str, int]] = {}
|
|
for key, value in zip(node.value.keys, node.value.values):
|
|
if not (
|
|
isinstance(key, ast.Constant)
|
|
and isinstance(value, ast.Call)
|
|
and len(value.args) >= 2
|
|
and isinstance(value.args[1], ast.List)
|
|
):
|
|
self.src.fail(f"entry at line {getattr(key, 'lineno', '?')} is not StandaloneEmulator(name, [args])")
|
|
args = [a.value for a in value.args[1].elts if isinstance(a, ast.Constant)]
|
|
script = next((a for a in args if str(a).endswith(".sh")), None)
|
|
if script is None:
|
|
self.src.fail(f"{ast.unparse(key)} runs no .sh script")
|
|
self.table[str(key.value)] = (str(script), value.lineno)
|
|
|
|
def script(self, emulator: str) -> tuple[str, int]:
|
|
"""The repository script runemu runs for emulator, and the table line."""
|
|
if emulator not in self.table:
|
|
self.src.fail(f"standalone_emulators has no {emulator}")
|
|
installed, line = self.table[emulator]
|
|
path = SCRIPTS[emulator]
|
|
if posixpath.basename(installed) != posixpath.basename(path):
|
|
self.src.fail(f"{emulator} runs {installed}, not {posixpath.basename(path)}")
|
|
return path, line
|
|
|
|
|
|
def _evidence(
|
|
read: Read, record: Launcher, target: str, keys: list[str],
|
|
start: Script | None = None, source_name: str = "",
|
|
) -> Launcher:
|
|
"""A config record with the steps that put its file at ``target``: the
|
|
first-boot copy of /usr/config, then the start script's own copy."""
|
|
setup = Script(USERCONFIG, read(USERCONFIG))
|
|
placements = [config_placement(setup, target, required=False, sources=("/usr/config/",))]
|
|
if start is not None:
|
|
placements.append(
|
|
config_placement(start, target, keys, source_name=source_name, required=False)
|
|
)
|
|
fields = evidence_fields(placements)
|
|
record.installs = fields["installs"]
|
|
record.kept_if_present = fields["kept_if_present"]
|
|
record.overrides = fields["overrides"]
|
|
return record
|
|
|
|
|
|
def retroarch(read: Read) -> list[Launcher]:
|
|
"""system_directory of the retroarch.cfg the retroarch package installs."""
|
|
found = [s for s in settings(read(RETROARCH_CFG)) if s.key == "system_directory"]
|
|
if len(found) != 1:
|
|
raise LauncherPatternMissing(RETROARCH_CFG, "one system_directory")
|
|
return [_evidence(read, Launcher(
|
|
emulator="retroarch", build="libretro", kind="passes_path", names=[],
|
|
search_dir=found[0].value, passes_to="retroarch.cfg system_directory",
|
|
fails_when_missing=False, source_ref=source_ref([(RETROARCH_CFG, [found[0].line])]),
|
|
), "/storage/.config/retroarch/retroarch.cfg", ["system_directory"])]
|
|
|
|
|
|
def retrorun(read: Read) -> list[Launcher]:
|
|
"""The -d directory retrorun.sh passes to retrorun."""
|
|
script = Script(RETRORUN, read(RETRORUN))
|
|
line, found = script.find(r"^\s*/usr/bin/retrorun\s.*\s-d\s+(\S+)", "retrorun -d")
|
|
return [Launcher(
|
|
emulator="retrorun", build="libretro", kind="passes_path", names=[],
|
|
search_dir=found.group(1).strip('"'), passes_to="command line -d",
|
|
fails_when_missing=False, source_ref=source_ref([(RETRORUN, [line])]),
|
|
)]
|
|
|
|
|
|
def mame(runemu: Runemu) -> list[Launcher]:
|
|
"""The `-rp` search path of the .cmd file runemu writes for a MAME machine."""
|
|
src = runemu.src
|
|
commands = [
|
|
node for node in ast.walk(src.function("get_retroarch_command"))
|
|
if isinstance(node, ast.JoinedStr)
|
|
and any(isinstance(p, ast.Constant) and " -rp " in str(p.value) for p in node.values)
|
|
]
|
|
if not commands:
|
|
src.fail("get_retroarch_command writes no -rp search path")
|
|
paths = []
|
|
for node in commands:
|
|
text = "".join(str(p.value) for p in node.values if isinstance(p, ast.Constant))
|
|
paths.append(text.split(" -rp ", 1)[1].split()[0])
|
|
if len(set(paths)) != 1:
|
|
src.fail(f"the MAME commands differ in their search path: {sorted(set(paths))}")
|
|
guard = next(
|
|
node for node in ast.walk(src.function("get_retroarch_command"))
|
|
if isinstance(node, ast.If) and ast.unparse(node.test) == "self.emulator == 'mame'"
|
|
)
|
|
return [Launcher(
|
|
emulator="mame", build="libretro", kind="passes_path", names=[],
|
|
search_dir=paths[0], passes_to=".cmd -rp", fails_when_missing=False,
|
|
depends_on_option=ast.unparse(guard.test),
|
|
source_ref=src.ref([
|
|
src.span(guard.test), *((n.lineno, n.end_lineno or n.lineno) for n in commands),
|
|
]),
|
|
)]
|
|
|
|
|
|
def _bios_files(path: str, text: str) -> tuple[list[tuple[tuple[str, str], str]], set[int]]:
|
|
"""((section, key), name under BIOS) for each config key whose value is a BIOS file."""
|
|
prefix = BIOS + "/"
|
|
found = [s for s in settings(text) if s.value.startswith(prefix)]
|
|
if not found:
|
|
raise LauncherPatternMissing(path, f"key naming a file under {BIOS}")
|
|
return [((s.section, s.key), s.value[len(prefix):]) for s in found], {s.line for s in found}
|
|
|
|
|
|
def hatari(read: Read, runemu: Runemu) -> list[Launcher]:
|
|
"""szTosImageFileName of the default config hatari.sh copies and passes."""
|
|
path, table_line = runemu.script("HATARISA")
|
|
script = Script(path, read(path))
|
|
name = posixpath.basename(HATARI_CONFIG)
|
|
copied, copy = script.find(
|
|
rf"^\s*cp\s+\S*/{re.escape(name)}\s+\"?(\$\{{?\w+\}}?)", f"cp of {name}"
|
|
)
|
|
passed, _ = script.find(r"\shatari\s+--configfile\s", "hatari --configfile")
|
|
files, lines = _bios_files(HATARI_CONFIG, read(HATARI_CONFIG))
|
|
return [_evidence(read, Launcher(
|
|
emulator="HATARISA", build="standalone", kind="fixed_name",
|
|
names=[n for _k, n in files], case_insensitive=False, search_dir=BIOS,
|
|
passes_to=describe_keys(name, tuple(k for k, _n in files)),
|
|
fails_when_missing=False,
|
|
source_ref=source_ref([
|
|
(RUNEMU, [table_line]), (path, [copied, passed]), (HATARI_CONFIG, lines),
|
|
]),
|
|
), script.expand(copy.group(1), copied, set()),
|
|
[f"[{s}] {k}" if s else k for (s, k), _n in files], script, name)]
|
|
|
|
|
|
def yabasanshiro(read: Read, runemu: Runemu) -> list[Launcher]:
|
|
"""`if [ -e <bios> ]; then BIOS="-b <bios>"`, skipped when HLE is chosen."""
|
|
path, table_line = runemu.script("yabasanshiroSA")
|
|
script = Script(path, read(path))
|
|
tested, test = script.find(rf'if \[ -e "({re.escape(BIOS)}/[^"]+)" \]', "BIOS presence test")
|
|
flag_line, flag = script.find(
|
|
rf'BIOS="(-\w+) {re.escape(test.group(1))}"', "BIOS flag", tested
|
|
)
|
|
hle_line, hle = script.find(r'get_setting "(\w*hle\w*)"', "HLE setting")
|
|
guard_line, _ = script.find(r'if \[ "\$\{HLEBIOS\}" != 1 \]', "HLE test")
|
|
used_line, _ = script.find(r"\$\{BIOS\}", "BIOS on the command line", flag_line + 1)
|
|
return [Launcher(
|
|
emulator="yabasanshiroSA", build="standalone", kind="fixed_name",
|
|
names=[test.group(1)[len(BIOS) + 1:]], case_insensitive=False, search_dir=BIOS,
|
|
passes_to=f"command line {flag.group(1)}", fails_when_missing=False,
|
|
depends_on_option=hle.group(1),
|
|
source_ref=source_ref([
|
|
(RUNEMU, [table_line]),
|
|
(path, [hle_line, guard_line, tested, flag_line, used_line]),
|
|
]),
|
|
)]
|
|
|
|
|
|
def pico8(read: Read, runemu: Runemu) -> list[Launcher]:
|
|
"""`if [ ! -f A ] || [ ! -f B ]; then text_viewer ...; else A ...`."""
|
|
path, table_line = runemu.script("pico8")
|
|
script = Script(path, read(path))
|
|
line, test = script.find(r'if \[ ! -f "([^"]+)" \] \|\| \[ ! -f "([^"]+)" \]', "binary test")
|
|
message_line, message = script.find(r'text_viewer .*-t "([^"]+)"', "missing message", line)
|
|
binary = test.group(1)
|
|
run_line, _ = script.find(rf"^\s*{re.escape(binary)}\s", f"{binary} run", message_line)
|
|
folders = {posixpath.dirname(test.group(1)), posixpath.dirname(test.group(2))}
|
|
if len(folders) != 1:
|
|
raise script.missing("both binaries in one folder")
|
|
return [Launcher(
|
|
emulator="pico8", build="standalone", kind="fixed_name",
|
|
names=[posixpath.basename(test.group(1)), posixpath.basename(test.group(2))],
|
|
case_insensitive=False, search_dir=folders.pop(), passes_to="exec",
|
|
fails_when_missing=message.group(1),
|
|
source_ref=source_ref([(RUNEMU, [table_line]), (path, [line, message_line, run_line])]),
|
|
)]
|
|
|
|
|
|
def piemu(read: Read, runemu: Runemu) -> list[Launcher]:
|
|
"""The BIOS file piemu.sh hands to mkpfi to build the flash image."""
|
|
path, table_line = runemu.script("piemu")
|
|
script = Script(path, read(path))
|
|
names: list[str] = []
|
|
lines: set[int] = set()
|
|
for number, line in script.code():
|
|
found = re.search(r'/usr/bin/mkpfi\s+"([^"]+)"', line)
|
|
if found is None:
|
|
continue
|
|
used: set[int] = {number}
|
|
value = script.expand(found.group(1), number, used)
|
|
if value.startswith(BIOS + "/") and value[len(BIOS) + 1:] not in names:
|
|
names.append(value[len(BIOS) + 1:])
|
|
lines |= used
|
|
if not names:
|
|
raise script.missing(f"mkpfi of a file under {BIOS}")
|
|
return [Launcher(
|
|
emulator="piemu", build="standalone", kind="fixed_name", names=names,
|
|
case_insensitive=False, search_dir=BIOS, passes_to="mkpfi argument",
|
|
fails_when_missing=False,
|
|
source_ref=source_ref([(RUNEMU, [table_line]), (path, lines)]),
|
|
)]
|
|
|
|
|
|
def amiberry(read: Read, runemu: Runemu) -> list[Launcher]:
|
|
"""The kickstarts link of the package, and the ROM each default config names."""
|
|
package = Script(AMIBERRY_PACKAGE, read(AMIBERRY_PACKAGE))
|
|
links = [link for link in package.links(BIOS) if link.bios_is_target]
|
|
if len(links) != 1 or links[0].bios_side.rstrip("/") != BIOS:
|
|
raise package.missing(f"ln -s {BIOS} to the kickstarts folder")
|
|
link = links[0]
|
|
installed = re.sub(r"^\$\{INSTALL\}", "", link.link)
|
|
path, table_line = runemu.script("AMIBERRY")
|
|
script = Script(path, read(path))
|
|
# A WHDLoad archive is booted by --autoload, which builds its own setup
|
|
# over the config: the kickstart records hold for the other content.
|
|
autoload = [n for n, text in script.code() if re.search(r"amiberry .*--autoload", text)]
|
|
if not autoload:
|
|
raise script.missing("WHDLoad boot with --autoload")
|
|
content = (
|
|
"content not booted with --autoload, a WHDLoad .lha or .zip "
|
|
f"({posixpath.basename(path)}:{', '.join(map(str, autoload))})"
|
|
)
|
|
launchers = [Launcher(
|
|
emulator="AMIBERRY", build="standalone", kind="links", names=[],
|
|
case_insensitive=False, search_dir=BIOS,
|
|
passes_to=f"{installed} is a symlink to it", fails_when_missing=False,
|
|
source_ref=source_ref([(AMIBERRY_PACKAGE, link.lines)]),
|
|
)]
|
|
for number, line in script.code():
|
|
copied = re.search(r"cp\s+\$AMIBERRY_CONFIG_DIR/(\S+\.uae)\s", line)
|
|
if copied is None:
|
|
continue
|
|
config = f"{AMIBERRY_CONFIGS}/{copied.group(1)}"
|
|
found = [
|
|
s for s in settings(read(config))
|
|
if s.key == "kickstart_rom_file" and s.value.startswith("$(FILE_PATH)/")
|
|
]
|
|
rom_path = [s for s in settings(read(config)) if s.key == "amiberry.rom_path"]
|
|
if len(found) != 1 or len(rom_path) != 1:
|
|
raise LauncherPatternMissing(config, "kickstart_rom_file under $(FILE_PATH)")
|
|
if rom_path[0].value.rstrip("/").rsplit("/", 1)[-1] != installed.rstrip("/").rsplit("/", 1)[-1]:
|
|
raise LauncherPatternMissing(config, f"amiberry.rom_path naming {installed}")
|
|
condition_line = max(
|
|
n for n, text in script.code()
|
|
if n < number and re.match(r"\s*(if|else)\b", text)
|
|
)
|
|
branch = script.lines[condition_line - 1].strip()
|
|
if branch == "else":
|
|
opened = max(
|
|
n for n, text in script.code()
|
|
if n < condition_line and re.match(r"\s*if\b", text)
|
|
)
|
|
test = _shell_test(script, opened)
|
|
condition = f"not ({test})"
|
|
else:
|
|
test = _shell_test(script, condition_line)
|
|
condition = test
|
|
target = re.search(r'"?(\$\{?\w+\}?)"?\s*$', line)
|
|
if target is None:
|
|
raise script.missing(f"destination of the copy at line {number}")
|
|
launchers.append(_evidence(read, Launcher(
|
|
emulator="AMIBERRY", build="standalone", kind="fixed_name",
|
|
slot=copied.group(1).removesuffix("-default.uae").removeprefix("Amiga"),
|
|
names=[found[0].value.removeprefix("$(FILE_PATH)/")], case_insensitive=False,
|
|
search_dir=BIOS,
|
|
passes_to=f"{copied.group(1)} kickstart_rom_file, $(FILE_PATH) = {rom_path[0].value}",
|
|
fails_when_missing=False,
|
|
depends_on_option=f"{condition}, {content}",
|
|
source_ref=source_ref([
|
|
(RUNEMU, [table_line]), (path, [condition_line, number]),
|
|
(config, [rom_path[0].line, found[0].line]),
|
|
]),
|
|
), script.expand(target.group(1), number, set()), ["kickstart_rom_file"],
|
|
script, copied.group(1)))
|
|
if len(launchers) == 1:
|
|
raise script.missing("cp of a default .uae config")
|
|
return launchers
|
|
|
|
|
|
def _shell_test(script: Script, line: int) -> str:
|
|
"""The test of `if <test>; then` at a line."""
|
|
found = re.match(r"\s*if\s+(.*?);\s*then\s*$", script.lines[line - 1])
|
|
if found is None:
|
|
raise script.missing(f"if ...; then at line {line}")
|
|
return found.group(1)
|
|
|
|
|
|
def scummvm(read: Read) -> list[Launcher]:
|
|
"""The paths scummvm.sh writes into the libretro core's scummvm.ini."""
|
|
path = SCRIPTS["SCUMMVMSA"]
|
|
script = Script(path, read(path))
|
|
branch, _ = script.find(r'^\s*"libretro"\)', "libretro branch")
|
|
launchers = []
|
|
for number, line in script.code():
|
|
if number < branch:
|
|
continue
|
|
written = re.search(r"s/\^/(\w*path)=((?:\\/[^\\/n]+)+)\\n/", line)
|
|
if written is None:
|
|
continue
|
|
folder = written.group(2).replace("\\/", "/")
|
|
if "/roms/bios/" not in folder + "/" or written.group(1) == "browser_lastpath":
|
|
continue
|
|
launchers.append(Launcher(
|
|
emulator="scummvm", build="libretro", kind="passes_path", names=[],
|
|
search_dir=folder, passes_to=f"scummvm.ini {written.group(1)}",
|
|
fails_when_missing=False, depends_on_option="scummvm.sh libretro",
|
|
source_ref=source_ref([(path, [branch, number])]),
|
|
))
|
|
if not launchers:
|
|
raise script.missing("a scummvm.ini path written under /roms/bios")
|
|
return launchers
|
|
|
|
|
|
def freej2me(read: Read) -> list[Launcher]:
|
|
"""`if [ ! -f <bios>/<jar> ]; then cp <shipped jar> <bios>`."""
|
|
launchers = []
|
|
for core, path in FREEJ2ME:
|
|
script = Script(path, read(path))
|
|
tested, test = script.find(
|
|
rf'if \[ ! -f "{re.escape(BIOS)}/([^"]+)" \]', "jar presence test"
|
|
)
|
|
jar = test.group(1)
|
|
lines = [tested]
|
|
source = None
|
|
for number, line in script.code():
|
|
copied = re.match(rf"\s*cp\s+(?:-\S+\s+)*(\S+/{re.escape(jar)})\s+{re.escape(BIOS)}/?\s*$", line)
|
|
if copied:
|
|
lines.append(number)
|
|
source = copied.group(1)
|
|
if source is None:
|
|
raise script.missing(f"cp of {jar} into {BIOS}")
|
|
launchers.append(Launcher(
|
|
emulator=core, build="libretro", kind="copies", names=[jar],
|
|
case_insensitive=False, search_dir=posixpath.dirname(source), passes_to=BIOS,
|
|
fails_when_missing=False, source_ref=source_ref([(path, lines)]),
|
|
))
|
|
return launchers
|
|
|
|
|
|
def easyrpg(read: Read) -> list[Launcher]:
|
|
"""Each install_bios call: a runtime package downloaded and unpacked under BIOS."""
|
|
script = Script(EASYRPG, read(EASYRPG))
|
|
url_line, url = script.find(r'curl -Lo "\$\{BIOS\}" "([^"]+)"', "curl of the package")
|
|
launchers = []
|
|
for number, line in script.code():
|
|
call = re.match(r"\s*install_bios\s+(\S+)\s+(\S+)\s*$", line)
|
|
if call is None:
|
|
continue
|
|
used: set[int] = {number, url_line}
|
|
source = script.expand(url.group(1), number, used, {"BIOS": call.group(1)})
|
|
install = script.value("INSTALLPATH", number)
|
|
if install is None or install.value.rstrip("/") != BIOS:
|
|
raise script.missing(f"INSTALLPATH {BIOS}")
|
|
used.add(install.line)
|
|
launchers.append(Launcher(
|
|
emulator="easyrpg", build="libretro", kind="copies", names=[],
|
|
search_dir=source, passes_to=f"{BIOS}/{call.group(2)}",
|
|
fails_when_missing=False, source_ref=source_ref([(EASYRPG, used)]),
|
|
))
|
|
if not launchers:
|
|
raise script.missing("install_bios call")
|
|
return launchers
|
|
|
|
|
|
def extract_launchers(read: Read) -> list[dict[str, Any]]:
|
|
"""Every launcher record, read through `read(repo-relative path)`."""
|
|
runemu = Runemu(read)
|
|
# The atari800.cfg AmberELEC ships names the OS ROMs, but the core opens
|
|
# it only under its atari800_cfg option, off by default, and finds the
|
|
# ROMs by a scan of the content folder (libretro-atari800 atari.c:512-516,
|
|
# 537, libretro_core_options.h:1203): no record reads it.
|
|
launchers = [
|
|
*retroarch(read), *retrorun(read), *mame(runemu),
|
|
*scummvm(read), *freej2me(read), *easyrpg(read),
|
|
*amiberry(read, runemu), *hatari(read, runemu), *pico8(read, runemu),
|
|
*piemu(read, runemu), *yabasanshiro(read, runemu),
|
|
]
|
|
return [launcher.entry() for launcher in launchers]
|