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).
348 lines
13 KiB
Python
348 lines
13 KiB
Python
"""AmberELEC's launcher layer, read from runemu.py and its emulator scripts.
|
|
|
|
The fixtures are cut-down copies of what each extractor reads at AmberELEC
|
|
a21975bf (prerelease-20261019_1835). Records must name the files in the
|
|
order the code writes them, cite the fixture's own lines, and a construct
|
|
that is gone must stop the scrape.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
|
|
|
|
import sys
|
|
import textwrap
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
|
|
from scraper import amberelec_launchers as al # noqa: E402
|
|
from scraper.configgen_launchers import LauncherShapeError # noqa: E402
|
|
|
|
|
|
def text(block: str) -> str:
|
|
return textwrap.dedent(block).lstrip("\n")
|
|
|
|
|
|
RUNEMU = text(
|
|
"""
|
|
standalone_emulators = {
|
|
'AMIBERRY': StandaloneEmulator('amiberry', ['/usr/bin/amiberry.sh', '<path>']),
|
|
'HATARISA': StandaloneEmulator('hatari', ['/usr/bin/hatari.sh', '<path>']),
|
|
'pico8': StandaloneEmulator('pico8_64', ['/usr/bin/pico-8.sh', '<path>']),
|
|
'piemu': StandaloneEmulator('piemu', ['/usr/bin/bash', '-l', '/usr/bin/piemu.sh', '<path>']),
|
|
'yabasanshiroSA': StandaloneEmulator('yabasanshiro', ['/usr/bin/yabasanshiro.sh', '<path>']),
|
|
}
|
|
|
|
class EmuRunner():
|
|
def get_retroarch_command(self, shader_arg):
|
|
if self.emulator == 'mame':
|
|
if rom_path:
|
|
cmd = f'{mame_system} -{mame_slot} "{rom_path}" -rp /roms/bios/mame;/roms/bios -cfg_directory /x'
|
|
else:
|
|
cmd = f'{mame_system} -rp /roms/bios/mame;/roms/bios -cfg_directory /x'
|
|
"""
|
|
)
|
|
|
|
YABASANSHIRO = text(
|
|
"""
|
|
#!/bin/bash
|
|
get_setting "use_hlebios"
|
|
HLEBIOS='0'
|
|
BIOS=""
|
|
if [ "${HLEBIOS}" != 1 ]; then
|
|
if [ -e "/storage/roms/bios/saturn_bios.bin" ]; then
|
|
BIOS="-b /storage/roms/bios/saturn_bios.bin"
|
|
else
|
|
text_viewer -e -w -t "Notice! Yabasanshiro BIOS Missing!" -m "not found"
|
|
fi
|
|
fi
|
|
{ yabasanshiro -r 2 -i "${1}" ${BIOS}; } > /tmp/logs/exec.log 2>&1
|
|
"""
|
|
)
|
|
|
|
PICO8 = text(
|
|
"""
|
|
#!/bin/bash
|
|
mkdir -p "/storage/roms/pico-8"
|
|
if [ ! -f "/storage/roms/pico-8/pico8_64" ] || [ ! -f "/storage/roms/pico-8/pico8.dat" ]; then
|
|
text_viewer -e -w -t "Missing Pico-8 binaries!" -m "Extract your purchased pico8_64."
|
|
else
|
|
/storage/roms/pico-8/pico8_64 -home -root_path /storage/roms/pico-8 ${OPTIONS} "${CART}"
|
|
fi
|
|
"""
|
|
)
|
|
|
|
PIEMU = text(
|
|
"""
|
|
#!/bin/bash
|
|
ROMS_DIR="/storage/roms"
|
|
BIOS_DIR="$ROMS_DIR/bios"
|
|
if [[ "$EXTENSION" == "pfs" ]]; then
|
|
/usr/bin/mkpfi "$BIOS_DIR/all.bin"
|
|
fi
|
|
"""
|
|
)
|
|
|
|
HATARI_SH = text(
|
|
"""
|
|
#!/bin/bash
|
|
HATARI_CONFIG_DIR=/storage/.config/hatari/conf
|
|
cp $HATARI_CONFIG_DIR/Atari-ST-default.cfg "$HATARI_TMP_CONFIG"
|
|
nice -n -19 hatari --configfile "$HATARI_TMP_CONFIG" --disk-a "$1" >> $HATARI_LOG 2>&1
|
|
"""
|
|
)
|
|
|
|
HATARI_CFG = text(
|
|
"""
|
|
[ROM]
|
|
szTosImageFileName = /storage/roms/bios/tos.img
|
|
bPatchTos = TRUE
|
|
"""
|
|
)
|
|
|
|
AMIBERRY_MK = text(
|
|
"""
|
|
makeinstall_target() {
|
|
mkdir -p ${INSTALL}/usr/config/amiberry
|
|
ln -s /storage/roms/bios ${INSTALL}/usr/config/amiberry/kickstarts
|
|
}
|
|
"""
|
|
)
|
|
|
|
AMIBERRY_SH = text(
|
|
"""
|
|
#!/bin/bash
|
|
AMIBERRY_CONFIG_DIR=$AMIBERRY_DIR/conf
|
|
AMIBERRY_SET_CONF() {
|
|
if [ `echo $1 | egrep 'AGA|CD32' | wc -l` -eq 1 ]; then
|
|
cp $AMIBERRY_CONFIG_DIR/AmigaA1200-default.uae "$AMIBERRY_TMP_CONFIG"
|
|
else
|
|
cp $AMIBERRY_CONFIG_DIR/AmigaA500-default.uae "$AMIBERRY_TMP_CONFIG"
|
|
fi
|
|
}
|
|
amiberry -f "$AMIBERRY_TMP_CONFIG" --autoload "$1" >> $AMIBERRY_LOG 2>&1
|
|
"""
|
|
)
|
|
|
|
USERCONFIG = text(
|
|
"""
|
|
#!/bin/bash
|
|
if [ ! -e "/storage/.configured" ]
|
|
then
|
|
rsync -a --ignore-existing --exclude=es_systems.cfg /usr/config/* /storage/.config/ || exit 1
|
|
fi
|
|
"""
|
|
)
|
|
|
|
|
|
def uae(rom: str) -> str:
|
|
return f"amiberry.rom_path=/storage/.config/amiberry/kickstarts/\nkickstart_rom_file=$(FILE_PATH)/{rom}\n"
|
|
|
|
|
|
SCUMMVM_SH = text(
|
|
"""
|
|
#!/bin/bash
|
|
case $1 in
|
|
"libretro")
|
|
sed -i '2,2s/^/themepath=\\/roms\\/bios\\/scummvm\\/theme\\n/' ${BIOSPATH}/scummvm.ini
|
|
sed -i '2,2s/^/extrapath=\\/roms\\/bios\\/scummvm\\/extra\\n/' ${BIOSPATH}/scummvm.ini
|
|
sed -i '2,2s/^/browser_lastpath=\\/roms\\/bios\\/scummvm\\/extra\\n/' ${BIOSPATH}/scummvm.ini
|
|
;;
|
|
esac
|
|
"""
|
|
)
|
|
|
|
FREEJ2ME = text(
|
|
"""
|
|
#!/bin/bash
|
|
if [ ! -f "/storage/roms/bios/{jar}" ]; then
|
|
cp /usr/config/distribution/freej2me/{jar} /storage/roms/bios
|
|
fi
|
|
"""
|
|
)
|
|
|
|
EASYRPG = text(
|
|
"""
|
|
#!/bin/bash
|
|
install_bios() {
|
|
BIOS="$1"
|
|
curl -Lo "${BIOS}" "${PKG_URL}/${BIOS}"
|
|
}
|
|
INSTALLPATH="/storage/roms/bios"
|
|
PKG_URL="https://dl.degica.com/rpgmakerweb/run-time-packages"
|
|
install_bios rpg2000_rtp_installer.exe rtp/2000
|
|
"""
|
|
)
|
|
|
|
|
|
def tree(**overrides: str) -> dict[str, str]:
|
|
files = {
|
|
al.RUNEMU: RUNEMU,
|
|
al.RETROARCH_CFG: 'savefile_directory = "~/saves"\nsystem_directory = "~/roms/bios"\n',
|
|
al.RETRORUN: '/usr/bin/retrorun -c "${RRCONF}" -s /storage/roms/"$3" -d /roms/bios "$EMU" "$2"\n',
|
|
al.SCRIPTS["yabasanshiroSA"]: YABASANSHIRO,
|
|
al.SCRIPTS["pico8"]: PICO8,
|
|
al.SCRIPTS["piemu"]: PIEMU,
|
|
al.SCRIPTS["HATARISA"]: HATARI_SH,
|
|
al.HATARI_CONFIG: HATARI_CFG,
|
|
al.USERCONFIG: USERCONFIG,
|
|
al.AMIBERRY_PACKAGE: AMIBERRY_MK,
|
|
al.SCRIPTS["AMIBERRY"]: AMIBERRY_SH,
|
|
f"{al.AMIBERRY_CONFIGS}/AmigaA1200-default.uae": uae("kick40068.A1200.rom"),
|
|
f"{al.AMIBERRY_CONFIGS}/AmigaA500-default.uae": uae("kick34005.A500.rom"),
|
|
al.SCRIPTS["SCUMMVMSA"]: SCUMMVM_SH,
|
|
al.EASYRPG: EASYRPG,
|
|
**{path: FREEJ2ME.replace("{jar}", f"{core}-lr.jar") for core, path in al.FREEJ2ME},
|
|
}
|
|
files.update(overrides)
|
|
return files
|
|
|
|
|
|
def read(**overrides: str):
|
|
return tree(**overrides).__getitem__
|
|
|
|
|
|
class Frontends(unittest.TestCase):
|
|
def test_retroarch_system_directory(self):
|
|
(record,) = al.retroarch(read())
|
|
self.assertEqual(record.search_dir, "~/roms/bios")
|
|
self.assertEqual(record.source_ref, f"{al.RETROARCH_CFG}:2")
|
|
# The first boot copies /usr/config and leaves a file already there.
|
|
self.assertEqual(record.installs, {
|
|
"path": "/storage/.config/retroarch/retroarch.cfg",
|
|
"source_ref": f"{al.USERCONFIG}:4",
|
|
})
|
|
self.assertIs(record.kept_if_present, True)
|
|
self.assertEqual(record.overrides, [])
|
|
|
|
def test_retrorun_directory(self):
|
|
(record,) = al.retrorun(read())
|
|
self.assertEqual((record.search_dir, record.passes_to), ("/roms/bios", "command line -d"))
|
|
|
|
def test_mame_search_path_of_both_commands(self):
|
|
(record,) = al.mame(al.Runemu(read()))
|
|
self.assertEqual(record.search_dir, "/roms/bios/mame;/roms/bios")
|
|
self.assertEqual(record.depends_on_option, "self.emulator == 'mame'")
|
|
self.assertEqual(record.source_ref, f"{al.RUNEMU}:11, 13, 15")
|
|
|
|
def test_a_retroarch_config_without_the_key_raises(self):
|
|
with self.assertRaises(LauncherShapeError):
|
|
al.retroarch(read(**{al.RETROARCH_CFG: "x = 1\n"}))
|
|
|
|
|
|
class Standalone(unittest.TestCase):
|
|
def test_yabasanshiro_flag_after_the_presence_test(self):
|
|
(record,) = al.yabasanshiro(read(), al.Runemu(read()))
|
|
self.assertEqual(record.names, ["saturn_bios.bin"])
|
|
self.assertEqual(record.passes_to, "command line -b")
|
|
self.assertIs(record.fails_when_missing, False)
|
|
self.assertEqual(record.depends_on_option, "use_hlebios")
|
|
self.assertEqual(
|
|
record.source_ref,
|
|
f"{al.RUNEMU}:6, {al.SCRIPTS['yabasanshiroSA']}:2, 5-7, 12",
|
|
)
|
|
|
|
def test_pico8_binaries_outside_bios(self):
|
|
(record,) = al.pico8(read(), al.Runemu(read()))
|
|
self.assertEqual(record.names, ["pico8_64", "pico8.dat"])
|
|
self.assertEqual(record.search_dir, "/storage/roms/pico-8")
|
|
self.assertEqual(record.fails_when_missing, "Missing Pico-8 binaries!")
|
|
self.assertEqual(record.source_ref, f"{al.RUNEMU}:4, {al.SCRIPTS['pico8']}:3-6")
|
|
|
|
def test_piemu_flash_image_source(self):
|
|
(record,) = al.piemu(read(), al.Runemu(read()))
|
|
self.assertEqual(record.names, ["all.bin"])
|
|
self.assertEqual(record.source_ref, f"{al.RUNEMU}:5, {al.SCRIPTS['piemu']}:2-5")
|
|
|
|
def test_hatari_default_config(self):
|
|
(record,) = al.hatari(read(), al.Runemu(read()))
|
|
self.assertEqual(record.names, ["tos.img"])
|
|
self.assertEqual(record.passes_to, "Atari-ST-default.cfg [ROM] szTosImageFileName")
|
|
self.assertEqual(
|
|
record.source_ref,
|
|
f"{al.RUNEMU}:3, {al.SCRIPTS['HATARISA']}:3-4, {al.HATARI_CONFIG}:2",
|
|
)
|
|
# Copied over at each launch: nothing the user edits survives.
|
|
self.assertEqual(record.installs["source_ref"], f"{al.SCRIPTS['HATARISA']}:3")
|
|
self.assertIs(record.kept_if_present, False)
|
|
|
|
def test_amiberry_link_and_one_kickstart_per_config(self):
|
|
link, a1200, a500 = al.amiberry(read(), al.Runemu(read()))
|
|
self.assertEqual(link.kind, "links")
|
|
self.assertEqual(link.passes_to, "/usr/config/amiberry/kickstarts is a symlink to it")
|
|
self.assertEqual((a1200.slot, a1200.names), ("A1200", ["kick40068.A1200.rom"]))
|
|
self.assertEqual((a500.slot, a500.names), ("A500", ["kick34005.A500.rom"]))
|
|
self.assertTrue(a500.depends_on_option.startswith("not ("))
|
|
# A WHDLoad boot replaces the setup the config gives.
|
|
self.assertIn("--autoload", a1200.depends_on_option)
|
|
self.assertEqual(a1200.installs["source_ref"], f"{al.SCRIPTS['AMIBERRY']}:5")
|
|
self.assertEqual(a500.installs["source_ref"], f"{al.SCRIPTS['AMIBERRY']}:7")
|
|
|
|
def test_a_table_running_another_script_raises(self):
|
|
broken = RUNEMU.replace("/usr/bin/yabasanshiro.sh", "/usr/bin/yaba.sh")
|
|
with self.assertRaises(LauncherShapeError):
|
|
al.yabasanshiro(read(), al.Runemu(read(**{al.RUNEMU: broken})))
|
|
|
|
def test_a_bios_test_written_another_way_raises(self):
|
|
broken = YABASANSHIRO.replace('if [ -e "/storage', 'if test -e "/storage')
|
|
with self.assertRaises(LauncherShapeError):
|
|
al.yabasanshiro(read(**{al.SCRIPTS["yabasanshiroSA"]: broken}), al.Runemu(read()))
|
|
|
|
|
|
class Cores(unittest.TestCase):
|
|
def test_atari800_config_is_no_record(self):
|
|
# The core reads atari800.cfg only under an option that is off.
|
|
self.assertFalse(any(r["emulator"] == "atari800" for r in al.extract_launchers(read())))
|
|
|
|
def test_scummvm_paths_written_under_bios(self):
|
|
theme, extra = al.scummvm(read())
|
|
self.assertEqual(theme.search_dir, "/roms/bios/scummvm/theme")
|
|
self.assertEqual(extra.passes_to, "scummvm.ini extrapath")
|
|
|
|
def test_freej2me_copies_the_shipped_jar(self):
|
|
plain, plus = al.freej2me(read())
|
|
self.assertEqual((plain.kind, plain.names), ("copies", ["freej2me-lr.jar"]))
|
|
self.assertEqual(plus.names, ["freej2me-plus-lr.jar"])
|
|
self.assertEqual(plain.search_dir, "/usr/config/distribution/freej2me")
|
|
|
|
def test_easyrpg_download_per_runtime(self):
|
|
(record,) = al.easyrpg(read())
|
|
self.assertEqual(
|
|
record.search_dir,
|
|
"https://dl.degica.com/rpgmakerweb/run-time-packages/rpg2000_rtp_installer.exe",
|
|
)
|
|
self.assertEqual(record.passes_to, "/storage/roms/bios/rtp/2000")
|
|
|
|
def test_every_extractor_runs_on_the_fixture(self):
|
|
records = al.extract_launchers(read())
|
|
# One easyrpg runtime in the fixture, two at the pinned commit.
|
|
self.assertEqual(len(records), 15)
|
|
|
|
|
|
class PlatformFile(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.config = yaml.safe_load((REPO_ROOT / "platforms" / "amberelec.yml").read_text())
|
|
|
|
def test_the_layer_is_written(self):
|
|
self.assertTrue(self.config["launchers"])
|
|
|
|
def test_every_reference_is_in_the_repository(self):
|
|
for record in self.config["launchers"]:
|
|
self.assertTrue(record["source_ref"].startswith("packages/"), record)
|
|
|
|
def test_every_emulator_is_one_amberelec_names(self):
|
|
cores = {*self.config["cores"], *self.config.get("standalone_cores", [])}
|
|
named = {n for record in self.config["launchers"] for n in record["emulator"].split(", ")}
|
|
# RetroArch and retrorun themselves, the two runners of every core.
|
|
self.assertEqual(sorted(named - cores), ["retroarch", "retrorun"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|