feat: read pico-8 launchers of rocknix, retrobat

ROCKNIX's start_pico8.sh runs pico8_64 or pico8_dyn from
/storage/roms/pico-8 and RetroBat's Pico8 generator runs pico8.exe from
its pico8 emulator folder. Both are recorded as exec records, read from
the scripts at the revisions the platform files pin.
This commit is contained in:
Abdessamad Derraz committed 2026-11-09 18:22:26 +01:00
1 parent 6a9700489d
commit b5ee4ab09e
7 files changed
+216

No files matched your search

+10
View File
@@ -4953,6 +4953,16 @@ launchers:
fails_when_missing: ApplicationException 'No valid PS2 BIOS file found in '<ps2BiosFolder>'.'
depends_on_option: lrps2_bios unset
source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3662-3683, 3708-3713"
- emulator: pico8
build: standalone
kind: fixed_name
names:
- pico8.exe
case_insensitive: true
search_dir: "<AppConfig.GetFullPath(\"pico8\")>"
passes_to: exec
fails_when_missing: returns no process when the binary is absent
source_ref: "emulatorLauncher/Generators/Pico8.Generator.cs:14-18, 56-57"
- emulator: bizhawk
build: standalone
kind: names_searched
+13
View File
@@ -736,6 +736,19 @@ launchers:
source_ref: "projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/config/S922X/ARMSX2/inis/PCSX2.ini:21,\
\ 475, projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/config/inputplumber/ARMSX2/inis/PCSX2.ini:21,\
\ 473, projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/package.mk:117-118"
- emulator: pico-8
build: standalone
kind: fixed_name
names:
- pico8_64
- pico8_dyn
search_dir: /storage/roms/pico-8
passes_to: exec
fails_when_missing: false
depends_on_option: "HW_ARCH aarch64 runs pico8_64, any other pico8_dyn, from /storage/roms/pico-8/${HW_ARCH}\
\ when that folder exists"
source_ref: "projects/ROCKNIX/packages/emulators/standalone/pico-8/scripts/start_pico8.sh:8-15,\
\ 35-40, 48"
- emulator: flycast
build: standalone
kind: passes_path
+40
View File
@@ -51,6 +51,7 @@ SOURCE_FILES = (
f"{GENERATORS}/OpenMSX.Generator.cs",
f"{GENERATORS}/PSXMame.Generator.cs",
f"{GENERATORS}/Pcsx2.Generator.cs",
f"{GENERATORS}/Pico8.Generator.cs",
f"{GENERATORS}/PortsLauncher.Configuration.cs",
f"{GENERATORS}/Project64.Generator.cs",
f"{GENERATORS}/Raine.Generator.cs",
@@ -2968,6 +2969,44 @@ def xroar(sources: dict[str, Source]) -> list[Record]:
return _covered(src, records)
def pico8(sources: dict[str, Source]) -> list[Record]:
"""The PICO-8 binary the generator runs, from an emulator folder.
AppConfig.GetFullPath names a folder of the RetroBat install
(emulatorLauncher.cfg, else emulators/<key>): the binary is never
looked up in the BIOS tree.
"""
src = sources[f"{GENERATORS}/Pico8.Generator.cs"]
folder = src.find(r'string path = AppConfig\.GetFullPath\("(\w+)"\);', "pico8 folder")
exe = src.find(
r'string exe = Path\.Combine\(path, "([^"]+)"\);', "binary path", folder.end()
)
check = src.find(r"if \(!File\.Exists\(exe\)\)\s*return null;", "binary check", exe.end())
run = src.find(r"FileName = exe,\s*WorkingDirectory = path,", "binary run", check.end())
return [
_record(
"pico8",
"standalone",
"fixed_name",
[exe.group(1)],
source_ref(
(
src.path,
[
src.lines(folder.start(), folder.end()),
src.lines(exe.start(), check.end()),
src.lines(run.start(), run.end()),
],
)
),
case_insensitive=True,
search_dir=f"<AppConfig.GetFullPath(\"{folder.group(1)}\")>",
passes_to="exec",
fails_when_missing="returns no process when the binary is absent",
)
]
def snes9x(sources: dict[str, Source]) -> list[Record]:
src = sources[f"{GENERATORS}/Snes9x.Generator.cs"]
entry = src.find(rf'\{{ "([^"]+)", (Path\.Combine\({_BIOS_ROOT}\)) \}}', "bios folder entry")
@@ -3236,6 +3275,7 @@ EXTRACTORS: tuple[Callable[[dict[str, Source]], list[Record]], ...] = (
option_paths,
pcsx2,
pcsx2_libretro,
pico8,
psx_cascades,
psxmame,
raine,
+42
View File
@@ -575,6 +575,47 @@ def armsx2(tree: RepoTree) -> list[Launcher]:
# Start scripts whose ln -s commands put a BIOS path where the emulator reads.
def pico8(tree: RepoTree) -> list[Launcher]:
"""The PICO-8 binary start_pico8.sh executes, outside /storage/roms/bios.
The script picks the binary by HW_ARCH, pico8_64 on aarch64 and
pico8_dyn on any other, and runs it from GAME_DIR, or from
GAME_DIR/${HW_ARCH} when that folder exists.
"""
path = f"{STANDALONE}/pico-8/scripts/start_pico8.sh"
script = Script(path, tree.read(path))
game_line, game = script.find(r'^\s*GAME_DIR="([^"$]+)"', "GAME_DIR assignment")
case_line, _ = script.find(r"^\s*case \$\{HW_ARCH\} in", "case on HW_ARCH")
arch_line, arch = script.find(r"^\s*(\w+)\)", "HW_ARCH branch", case_line + 1)
first_line, first = script.find(r'STATIC_BIN="([^"$]+)"', "binary of the first branch", arch_line)
other_line, _ = script.find(r"^\s*\*\)", "default HW_ARCH branch", first_line)
rest_line, rest = script.find(r'STATIC_BIN="([^"$]+)"', "binary of the default branch", other_line)
sub_line, _ = script.find(
r'if \[ -d "\$\{GAME_DIR\}/\$\{HW_ARCH\}" \]', "per-architecture launch folder"
)
end_line, _ = script.find(r"^\s*fi\b", "end of the launch folder test", sub_line)
run_line, _ = script.find(r"^\s*\$\{LAUNCH_DIR\}/\$\{STATIC_BIN\} ", "binary run")
return [
Launcher(
emulator="pico-8",
build="standalone",
kind="fixed_name",
names=[first.group(1), rest.group(1)],
source_ref=source_ref([(path, {
game_line, case_line, arch_line, first_line, other_line, rest_line,
*range(sub_line, end_line + 1), run_line,
})]),
search_dir=game.group(1).rstrip("/"),
passes_to="exec",
fails_when_missing=False,
depends_on_option=(
f"HW_ARCH {arch.group(1)} runs {first.group(1)}, any other {rest.group(1)}, "
f"from {game.group(1).rstrip('/')}/${{HW_ARCH}} when that folder exists"
),
)
]
LINK_SCRIPTS = (
("drastic", f"{STANDALONE}/drastic-sa/scripts/start_drastic.sh"),
("dolphin", f"{STANDALONE}/dolphin-sa/scripts/start_dolphin_gc.sh"),
@@ -679,6 +720,7 @@ EXTRACTORS: tuple[Callable[[RepoTree], list[Launcher]], ...] = (
yabasanshiro,
duckstation,
armsx2,
pico8,
patched_directories,
links,
)
+30
View File
@@ -1141,6 +1141,36 @@ class FolderArguments(unittest.TestCase):
)
PICO8 = """
public override System.Diagnostics.ProcessStartInfo Generate(string system, string emulator, string core, string rom, string playersControllers, ScreenResolution resolution)
{
string path = AppConfig.GetFullPath("pico8");
string exe = Path.Combine(path, "pico8.exe");
if (!File.Exists(exe))
return null;
return new ProcessStartInfo()
{
FileName = exe,
WorkingDirectory = path,
Arguments = args,
};
}
"""
class ProgramsRunFromEmulatorFolders(unittest.TestCase):
def test_pico8_runs_its_binary_from_its_emulator_folder(self):
(record,) = rl.pico8(_sources(**{f"{G}/Pico8.Generator.cs": PICO8}))
self.assertEqual(
(record["emulator"], record["kind"], record["names"], record["search_dir"],
record["passes_to"], record["source_ref"]),
("pico8", "fixed_name", ["pico8.exe"], '<AppConfig.GetFullPath("pico8")>', "exec",
f"{G}/Pico8.Generator.cs:3-7, 11-12"),
)
class EveryBiosReferenceIsRead(unittest.TestCase):
def test_unread_reference_stops_the_scrape(self):
extra = EKA2L1 + (
+34
View File
@@ -293,6 +293,28 @@ DRASTIC = module("drastic", """
""", 36)
UAE4ALL = module("uae4all", """
function configure_uae4all() {
mkRomDir "amiga"
moveConfigDir "$md_inst/kickstarts" "$biosdir/amiga"
rm -f "$romdir/amiga/+Start UAE4All.sh"
if [[ "$md_mode" == "install" ]]; then
cat > "$romdir/amiga/+Start UAE4All.sh" << _EOF_
#!/bin/bash
pushd "$md_inst"
./uae4all
popd
_EOF_
fi
addEmulator 1 "$md_id" "amiga" "bash $romdir/amiga/+Start\\ UAE4All.sh"
addSystem "amiga"
}
""", 60)
class WorkingDirectory(unittest.TestCase):
def test_drastic_reads_below_the_folder_its_script_enters(self):
found = records({f"{EMULATORS}/drastic.sh": DRASTIC}, "drastic")
@@ -306,6 +328,18 @@ class WorkingDirectory(unittest.TestCase):
)],
)
def test_uae4all_runs_from_its_install_directory(self):
found = records({f"{EMULATORS}/uae4all.sh": UAE4ALL}, "uae4all")
entered = [r for r in found if r["kind"] == "passes_path"]
self.assertEqual(
[(r["search_dir"], r["passes_to"], r["source_ref"]) for r in entered],
[(
"$md_inst",
"working directory of $romdir/amiga/+Start UAE4All.sh",
f"{EMULATORS}/uae4all.sh:69",
)],
)
def test_a_launch_script_that_no_longer_enters_a_folder_raises(self):
files = {f"{EMULATORS}/drastic.sh": DRASTIC.replace('pushd "', 'echo "')}
with self.assertRaises(rl.LauncherPatternMissing):
+47
View File
@@ -551,6 +551,53 @@ class Links(unittest.TestCase):
rl.links(tree)
START_PICO8 = text(
"""
#!/bin/bash
. /etc/profile
GAME_DIR="/storage/roms/pico-8/"
case ${HW_ARCH} in
aarch64)
STATIC_BIN="pico8_64"
;;
*)
STATIC_BIN="pico8_dyn"
;;
esac
if [ -d "${GAME_DIR}/${HW_ARCH}" ]
then
LAUNCH_DIR="${GAME_DIR}/${HW_ARCH}"
else
LAUNCH_DIR="${GAME_DIR}"
fi
${LAUNCH_DIR}/${STATIC_BIN} -home -root_path ${GAME_DIR} -joystick 0 ${OPTIONS} "${CART}"
"""
)
class ProgramsRunOutsideTheBiosFolder(unittest.TestCase):
PATH = f"{SA}/pico-8/scripts/start_pico8.sh"
def test_pico8_runs_its_binary_from_roms(self):
(record,) = [r.entry() for r in rl.pico8(RepoTree.from_files({self.PATH: START_PICO8}))]
self.assertEqual(
(record["emulator"], record["kind"], record["names"], record["search_dir"],
record["passes_to"]),
("pico-8", "fixed_name", ["pico8_64", "pico8_dyn"], "/storage/roms/pico-8", "exec"),
)
self.assertIn("HW_ARCH aarch64 runs pico8_64", record["depends_on_option"])
self.assertEqual(record["source_ref"], f"{self.PATH}:4-11, 15-22")
def test_a_script_without_its_binary_choice_raises(self):
broken = START_PICO8.replace('STATIC_BIN="pico8_dyn"', "STATIC_BIN=$OTHER")
with self.assertRaises(LauncherPatternMissing):
rl.pico8(RepoTree.from_files({self.PATH: broken}))
class Revision(unittest.TestCase):
def test_the_revision_is_the_one_the_bios_list_is_read_at(self):
url = (