mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-13 05:23:51 -06:00
chore: merge knulli and amberelec launchers
This commit is contained in:
commit
5073e4ea85
15 files changed
+3579
-76
No files matched your search
@@ -130,7 +130,7 @@ files:
|
||||
md5: 70ab37eab4e7d162d36d9b2d91b84867
|
||||
sha1: 4ed23b6578a2d93d67aa856791dc70e95d68a3f4
|
||||
description: "PICO-8 0.2.7 aarch64 executable, system libSDL2"
|
||||
note: "Only in pico-8_0.2.7_raspi.zip, the build for 64-bit Raspberry Pi OS, linked against the system SDL2. AmberELEC and ROCKNIX run it from roms/pico-8/, not from bios/. AmberELEC stops with a message when it is missing. ROCKNIX picks it when HW_ARCH is aarch64. Recalbox renames bios/pico-8/pico8_64 to pico8 before the first run, and a missing file fails the rename."
|
||||
note: "pico8_64 is only in pico-8_0.2.7_raspi.zip, the build for 64-bit Raspberry Pi OS, linked against the system SDL2. AmberELEC and ROCKNIX run it from roms/pico-8/, not from bios/. AmberELEC stops with a message when it is missing. ROCKNIX picks it when HW_ARCH is aarch64. Recalbox renames bios/pico-8/pico8_64 to pico8 before the first run, and a missing file fails the rename."
|
||||
source_ref: "pico-8 0.2.7 raspi readme_raspi.txt:3-11, amberelec packages/amberelec/sources/pico-8.sh:51-54, amberelec packages/amberelec/sources/scripts/runemu.py:123, rocknix projects/ROCKNIX/packages/emulators/standalone/pico-8/scripts/start_pico8.sh:8-17, 35-40, 46-47, recalbox projects/configgen/configgen/generators/pico8/pico8Generator.py:14-19, dArkOS pico8/pico8.sh:47-60, 128-135"
|
||||
|
||||
- name: pico8_dyn
|
||||
|
||||
@@ -3172,3 +3172,184 @@ systems:
|
||||
cores:
|
||||
- fuse
|
||||
- ep128emu_core
|
||||
launchers:
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: ~/roms/bios
|
||||
passes_to: retroarch.cfg system_directory
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/tools/retroarch/sources/retroarch.cfg:611"
|
||||
- emulator: retrorun
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /roms/bios
|
||||
passes_to: command line -d
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/tools/retrorun/retrorun.sh:583"
|
||||
- emulator: mame
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /roms/bios/mame;/roms/bios;/roms/arcade;/roms/mame
|
||||
passes_to: .cmd -rp
|
||||
fails_when_missing: false
|
||||
depends_on_option: self.emulator == 'mame'
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:233, 252, 255"
|
||||
- emulator: atari800
|
||||
build: libretro
|
||||
kind: fixed_name
|
||||
names:
|
||||
- ATARIOSA.ROM
|
||||
- ATARIXL.ROM
|
||||
- 5200.rom
|
||||
- 5200a.rom
|
||||
- BASIC_Rev_A.rom
|
||||
- BASIC_Rev_B.rom
|
||||
- ATARIBAS.ROM
|
||||
- ATARIOSB.ROM
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: atari800.cfg ROM_OS_A_PAL, ROM_OS_BB01R2, ROM_5200, ROM_5200_A, ROM_BASIC_A,
|
||||
ROM_BASIC_B, ROM_BASIC_C, ROM_400/800_CUSTOM
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/libretro/atari800/patches/atari800-01-add-emuelec-platform.patch:27,\
|
||||
\ packages/amberelec/config/distribution/configs/atari800.cfg:3, 8, 16-22"
|
||||
- emulator: scummvm
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /roms/bios/scummvm/theme
|
||||
passes_to: scummvm.ini themepath
|
||||
fails_when_missing: false
|
||||
depends_on_option: scummvm.sh libretro
|
||||
source_ref: "packages/games/emulators/scummvmsa/scummvm.sh:34, 42"
|
||||
- emulator: scummvm
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /roms/bios/scummvm/extra
|
||||
passes_to: scummvm.ini extrapath
|
||||
fails_when_missing: false
|
||||
depends_on_option: scummvm.sh libretro
|
||||
source_ref: "packages/games/emulators/scummvmsa/scummvm.sh:34, 43"
|
||||
- emulator: freej2me
|
||||
build: libretro
|
||||
kind: copies
|
||||
names:
|
||||
- freej2me-lr.jar
|
||||
case_insensitive: false
|
||||
search_dir: /usr/config/distribution/freej2me
|
||||
passes_to: /storage/roms/bios
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/libretro/freej2me/freej2me.sh:11-12, 44"
|
||||
- emulator: freej2me-plus
|
||||
build: libretro
|
||||
kind: copies
|
||||
names:
|
||||
- freej2me-plus-lr.jar
|
||||
case_insensitive: false
|
||||
search_dir: /usr/config/distribution/freej2me
|
||||
passes_to: /storage/roms/bios
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/libretro/freej2me-plus/freej2me-plus.sh:11-12, 44"
|
||||
- emulator: easyrpg
|
||||
build: libretro
|
||||
kind: copies
|
||||
names: []
|
||||
search_dir: "https://dl.degica.com/rpgmakerweb/run-time-packages/rpg2000_rtp_installer.exe"
|
||||
passes_to: /storage/roms/bios/rtp/2000
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/libretro/easyrpg/easyrpg.sh:23, 42, 46, 60"
|
||||
- emulator: easyrpg
|
||||
build: libretro
|
||||
kind: copies
|
||||
names: []
|
||||
search_dir: "https://dl.degica.com/rpgmakerweb/run-time-packages/rpg2003_rtp_installer.zip"
|
||||
passes_to: /storage/roms/bios/rtp/2003
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/libretro/easyrpg/easyrpg.sh:23, 42, 46, 61"
|
||||
- emulator: AMIBERRY
|
||||
build: standalone
|
||||
kind: links
|
||||
names: []
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: /usr/config/amiberry/kickstarts is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/games/emulators/amiberry/package.mk:35"
|
||||
- emulator: AMIBERRY
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
slot: A1200
|
||||
names:
|
||||
- kick40068.A1200.rom
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: "AmigaA1200-default.uae kickstart_rom_file, $(FILE_PATH) = /storage/.config/amiberry/kickstarts/"
|
||||
fails_when_missing: false
|
||||
depends_on_option: "[ `echo $1 | egrep 'AGA|CD32' | wc -l` -eq 1 -o `echo \"$AMIBERRY_TMP_DIR\"\
|
||||
/* | egrep 'AGA|CD32' | wc -l` -eq 1 ]"
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:111, packages/games/emulators/amiberry/scripts/amiberry.sh:57-59,\
|
||||
\ packages/games/emulators/amiberry/config/conf/AmigaA1200-default.uae:35, 85"
|
||||
- emulator: AMIBERRY
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
slot: A500
|
||||
names:
|
||||
- kick34005.A500.rom
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: "AmigaA500-default.uae kickstart_rom_file, $(FILE_PATH) = /storage/.config/amiberry/kickstarts/"
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not ([ `echo $1 | egrep 'AGA|CD32' | wc -l` -eq 1 -o `echo \"\
|
||||
$AMIBERRY_TMP_DIR\"/* | egrep 'AGA|CD32' | wc -l` -eq 1 ])"
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:111, packages/games/emulators/amiberry/scripts/amiberry.sh:60-62,\
|
||||
\ packages/games/emulators/amiberry/config/conf/AmigaA500-default.uae:35, 85"
|
||||
- emulator: HATARISA
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- tos.img
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: "Atari-ST-default.cfg [ROM] szTosImageFileName"
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:113, packages/games/emulators/hatarisa/scripts/hatari.sh:36,\
|
||||
\ 66, packages/games/emulators/hatarisa/config/conf/Atari-ST-default.cfg:200"
|
||||
- emulator: pico8
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- pico8_64
|
||||
- pico8.dat
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/pico-8
|
||||
passes_to: exec
|
||||
fails_when_missing: Missing Pico-8 binaries!
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:123, packages/amberelec/sources/pico-8.sh:51-54"
|
||||
- emulator: piemu
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- all.bin
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: mkpfi argument
|
||||
fails_when_missing: false
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:124, packages/games/emulators/piemu/bin/piemu.sh:8-10,\
|
||||
\ 33, 37, 50"
|
||||
- emulator: yabasanshiroSA
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- saturn_bios.bin
|
||||
case_insensitive: false
|
||||
search_dir: /storage/roms/bios
|
||||
passes_to: command line -b
|
||||
fails_when_missing: false
|
||||
depends_on_option: use_hlebios
|
||||
source_ref: "packages/amberelec/sources/scripts/runemu.py:127, packages/games/emulators/yabasanshiroSA/yabasanshiro.sh:36,\
|
||||
\ 63-65, 78"
|
||||
@@ -5394,3 +5394,744 @@ libretro_systems:
|
||||
- saturn
|
||||
zc210:
|
||||
- zc210
|
||||
launchers:
|
||||
- emulator: duckstation
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: NTSCU
|
||||
names:
|
||||
- scph101.bin
|
||||
- scph1001.bin
|
||||
- scph5501.bin
|
||||
- scph7001.bin
|
||||
- scph7501.bin
|
||||
case_insensitive: true
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathNTSCU"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
|
||||
\ 589-611"
|
||||
- emulator: duckstation
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: PAL
|
||||
names:
|
||||
- scph1002.bin
|
||||
- scph5502.bin
|
||||
- scph5552.bin
|
||||
- scph7002.bin
|
||||
- scph7502.bin
|
||||
- scph9002.bin
|
||||
- scph102a.bin
|
||||
- scph102b.bin
|
||||
case_insensitive: true
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathPAL"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
|
||||
\ 589-611"
|
||||
- emulator: duckstation
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: NTSCJ
|
||||
names:
|
||||
- scph100.bin
|
||||
- scph1000.bin
|
||||
- scph3000.bin
|
||||
- scph3500.bin
|
||||
- scph5500.bin
|
||||
- scph7000.bin
|
||||
- scph7003.bin
|
||||
case_insensitive: true
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathNTSCJ"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
|
||||
\ 589-611"
|
||||
- emulator: duckstation
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: Uni
|
||||
names:
|
||||
- psxonpsp660.bin
|
||||
- ps1_rom.bin
|
||||
case_insensitive: true
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathNTSCU, PathPAL, PathNTSCJ"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation/duckstationGenerator.py:119-143,\
|
||||
\ 589-611"
|
||||
- emulator: duckstation-legacy
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: NTSCU
|
||||
names:
|
||||
- scph101.bin
|
||||
- scph1001.bin
|
||||
- scph5501.bin
|
||||
- scph7001.bin
|
||||
- scph7501.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathNTSCU"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation_legacy/duckstationLegacyGenerator.py:116-153"
|
||||
- emulator: duckstation-legacy
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: PAL
|
||||
names:
|
||||
- scph1002.bin
|
||||
- scph5502.bin
|
||||
- scph5552.bin
|
||||
- scph7002.bin
|
||||
- scph7502.bin
|
||||
- scph9002.bin
|
||||
- scph102a.bin
|
||||
- scph102b.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathPAL"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation_legacy/duckstationLegacyGenerator.py:116-153"
|
||||
- emulator: duckstation-legacy
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: NTSCJ
|
||||
names:
|
||||
- scph100.bin
|
||||
- scph1000.bin
|
||||
- scph3000.bin
|
||||
- scph3500.bin
|
||||
- scph5500.bin
|
||||
- scph7000.bin
|
||||
- scph7003.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[BIOS] PathNTSCJ"
|
||||
fails_when_missing: No PSX1 BIOS found
|
||||
fails_scope: emulator
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/duckstation_legacy/duckstationLegacyGenerator.py:116-153"
|
||||
- emulator: hatari
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: st
|
||||
names:
|
||||
- "etos256{L}.img"
|
||||
- "tos104{L}.img"
|
||||
- "tos102{L}.img"
|
||||
- "tos100{L}.img"
|
||||
expands:
|
||||
L: &id001
|
||||
- us
|
||||
- uk
|
||||
- de
|
||||
- es
|
||||
- fr
|
||||
- it
|
||||
- nl
|
||||
- ru
|
||||
- se
|
||||
- ''
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: --machine st --tos <file>
|
||||
fails_when_missing: no bios found for machine st
|
||||
depends_on_option: model, language
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/hatari/hatariGenerator.py:33-55,\
|
||||
\ 67-78, 172-207"
|
||||
- emulator: hatari
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: ste
|
||||
names:
|
||||
- "etos256{L}.img"
|
||||
- "tos162{L}.img"
|
||||
- "tos106{L}.img"
|
||||
expands:
|
||||
L: *id001
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: --machine ste --tos <file>
|
||||
fails_when_missing: no bios found for machine ste
|
||||
depends_on_option: model, language
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/hatari/hatariGenerator.py:33-55,\
|
||||
\ 67-78, 172-207"
|
||||
- emulator: hatari
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: megaste
|
||||
names:
|
||||
- "etos256{L}.img"
|
||||
- "tos206{L}.img"
|
||||
- "tos205{L}.img"
|
||||
expands:
|
||||
L: *id001
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: --machine megaste --tos <file>
|
||||
fails_when_missing: no bios found for machine megaste
|
||||
depends_on_option: model, language
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/hatari/hatariGenerator.py:33-55,\
|
||||
\ 67-78, 172-207"
|
||||
- emulator: hatari
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: tt
|
||||
names:
|
||||
- "etos512{L}.img"
|
||||
- "tos306{L}.img"
|
||||
expands:
|
||||
L: *id001
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: --machine tt --tos <file>
|
||||
fails_when_missing: no bios found for machine tt
|
||||
depends_on_option: model, language
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/hatari/hatariGenerator.py:33-55,\
|
||||
\ 67-78, 172-207"
|
||||
- emulator: hatari
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: falcon
|
||||
names:
|
||||
- "etos512{L}.img"
|
||||
- "tos404{L}.img"
|
||||
- "tos402{L}.img"
|
||||
- "tos400{L}.img"
|
||||
expands:
|
||||
L: *id001
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: --machine falcon --tos <file>
|
||||
fails_when_missing: no bios found for machine falcon
|
||||
depends_on_option: model, language
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/hatari/hatariGenerator.py:33-55,\
|
||||
\ 67-78, 172-207"
|
||||
- emulator: xemu
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- cerbios.bin
|
||||
- Complex_4627.bin
|
||||
- mcpx_1.0.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[sys.files] flashrom_path, [sys.files] bootrom_path"
|
||||
fails_when_missing: false
|
||||
depends_on_option: system.name == 'chihiro'
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/xemu/xemuConfig.py:72-78"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- firmware.bin
|
||||
- bios7.bin
|
||||
- bios9.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: DS.FirmwarePath, DS.BIOS7Path, DS.BIOS9Path
|
||||
fails_when_missing: false
|
||||
depends_on_option: melonds_console
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/melonds/melondsGenerator.py:52-56,\
|
||||
\ 74-77, 173"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- dsi_firmware.bin
|
||||
- bios9.bin
|
||||
- bios7.bin
|
||||
- dsi_nand.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: DSi.FirmwarePath, DSi.BIOS9Path, DSi.BIOS7Path, DSi.NANDPath
|
||||
fails_when_missing: false
|
||||
depends_on_option: melonds_console
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/melonds/melondsGenerator.py:62-77,\
|
||||
\ 173"
|
||||
- emulator: mupen64plus
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- 64DD_IPL.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "[64DD] IPL-ROM"
|
||||
fails_when_missing: false
|
||||
depends_on_option: system.name == 'n64dd'
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mupen/mupenConfig.py:200-204"
|
||||
- emulator: x16emu
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- commanderx16/rom.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line -rom
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/x16emu/x16emuGenerator.py:31"
|
||||
- emulator: flycast
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/dc
|
||||
passes_to: environment FLYCAST_BIOS_PATH
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/flycast/flycastGenerator.py:239"
|
||||
- emulator: lexaloffle
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
slot: pico8
|
||||
names:
|
||||
- pico-8/pico8
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: exec
|
||||
fails_when_missing: Lexaloffle official binary not found at /userdata/bios/pico-8/pico8,
|
||||
File /userdata/bios/pico-8/pico8 is not set as executable
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/lexaloffle/lexaloffleGenerator.py:20,\
|
||||
\ 43-44, 54-59, 140-141"
|
||||
- emulator: lexaloffle
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
slot: voxatron
|
||||
names:
|
||||
- voxatron/vox
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: exec
|
||||
fails_when_missing: Lexaloffle official binary not found at /userdata/bios/voxatron/vox,
|
||||
File /userdata/bios/voxatron/vox is not set as executable
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/lexaloffle/lexaloffleGenerator.py:27,\
|
||||
\ 47-48, 54-59, 140-141"
|
||||
- emulator: o2em
|
||||
build: libretro
|
||||
kind: core_option
|
||||
names:
|
||||
- g7400.bin
|
||||
- o2rom.bin
|
||||
passes_to: core option o2em_bios
|
||||
fails_when_missing: false
|
||||
depends_on_option: "o2em_bios (default by system.name == 'videopacplus')"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroOptions.py:675-676,\
|
||||
\ 680-685"
|
||||
- emulator: neocd
|
||||
build: libretro
|
||||
kind: core_option
|
||||
names:
|
||||
- "neocd_z.rom (CDZ)"
|
||||
passes_to: core option neocd_bios
|
||||
fails_when_missing: false
|
||||
depends_on_option: neocd_bios
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroOptions.py:2558-2559,\
|
||||
\ 2566-2569"
|
||||
- emulator: pcsx2
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/ps2
|
||||
passes_to: "inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/configs/PCSX2"
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py:92,\
|
||||
\ 176, 213"
|
||||
- emulator: aethersx2
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/ps2
|
||||
passes_to: "inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/.config/aethersx2"
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/aethersx2/aethersx2Generator.py:71,\
|
||||
\ 182, 219"
|
||||
- emulator: lexaloffle
|
||||
build: standalone
|
||||
kind: moves
|
||||
names:
|
||||
- pico-8/pico8
|
||||
- pico-8/pico8_dyn
|
||||
- pico-8/pico8_gpio
|
||||
- pico-8/pico8.dat
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: moved from /userdata/roms/pico8/pico-8*raspi*.zip into /userdata/bios/pico-8
|
||||
fails_when_missing: Please add the Raspberry Pi version of Pico-8 to roms/pico8
|
||||
and try again.
|
||||
source_ref: "board/fsoverlay/usr/bin/knulli-install-pico8:3-11, 16-19, 66-69"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- "{bootdisk}.img"
|
||||
expands:
|
||||
bootdisk:
|
||||
- macos30
|
||||
- macos608
|
||||
- macos701
|
||||
- macos75
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line -flop1
|
||||
fails_when_missing: false
|
||||
depends_on_option: "bootdisk (not (messSysName[messMode] == '' or messMode == -1)\
|
||||
\ and softList == '' and system.name == 'macintosh' and system.isOptSet('bootdisk')\
|
||||
\ and system.config['bootdisk'] in ['macos30', 'macos608', 'macos701', 'macos75'])"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:303-305,\
|
||||
\ 375-379, 381, 385"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- "{bootdisk}.chd"
|
||||
expands:
|
||||
bootdisk:
|
||||
- macos3
|
||||
- mac608
|
||||
- mac701
|
||||
- mac755
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line -hard
|
||||
fails_when_missing: false
|
||||
depends_on_option: "bootdisk (not (messSysName[messMode] == '' or messMode == -1)\
|
||||
\ and softList == '' and system.name == 'macintosh' and system.isOptSet('bootdisk')\
|
||||
\ and not (system.config['bootdisk'] in ['macos30', 'macos608', 'macos701', 'macos75']))"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:303-305,\
|
||||
\ 375-379, 384-385, package/emulationstation/knulli-es-system/es_features.yml:11037-11043"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: fixed_name
|
||||
names:
|
||||
- "{bootdisk}.img"
|
||||
expands:
|
||||
bootdisk:
|
||||
- macos30
|
||||
- macos608
|
||||
- macos701
|
||||
- macos75
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line -flop1
|
||||
fails_when_missing: false
|
||||
depends_on_option: "bootdisk (not (system.name in ['mame', 'neogeo', 'lcdgames',\
|
||||
\ 'plugnplay', 'vis']) and not (messSysName[messMode] == '') and not (softList\
|
||||
\ != '') and system.name == 'macintosh' and system.isOptSet('bootdisk') and system.config['bootdisk']\
|
||||
\ in ['macos30', 'macos608', 'macos701', 'macos75'])"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:64,\
|
||||
\ 123, 197, 265-268, 270, 274"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: fixed_name
|
||||
names:
|
||||
- "{bootdisk}.chd"
|
||||
expands:
|
||||
bootdisk:
|
||||
- macos3
|
||||
- mac608
|
||||
- mac701
|
||||
- mac755
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line -hard
|
||||
fails_when_missing: false
|
||||
depends_on_option: "bootdisk (not (system.name in ['mame', 'neogeo', 'lcdgames',\
|
||||
\ 'plugnplay', 'vis']) and not (messSysName[messMode] == '') and not (softList\
|
||||
\ != '') and system.name == 'macintosh' and system.isOptSet('bootdisk') and not\
|
||||
\ (system.config['bootdisk'] in ['macos30', 'macos608', 'macos701', 'macos75']))"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:64,\
|
||||
\ 123, 197, 265-268, 273-274, package/emulationstation/knulli-es-system/es_features.yml:3727-3733"
|
||||
- emulator: dolphin
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- "GC/{region}/IPL.bin"
|
||||
expands:
|
||||
region:
|
||||
- USA
|
||||
- EUR
|
||||
- JAP
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: "presence sets [Core] SkipIPL = False"
|
||||
fails_when_missing: false
|
||||
depends_on_option: "system.isOptSet('dolphin_SkipIPL') and system.getOptBoolean('dolphin_SkipIPL')"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/dolphin/dolphinGenerator.py:192-193,\
|
||||
\ 196-197"
|
||||
- emulator: yabasanshiro
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- saturn_bios.bin
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line -b
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not (system.isOptSet('yaba_bios_hle') and system.config['yaba_bios_hle']\
|
||||
\ == '1')"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/yabasanshiro/yabasanshiroGenerator.py:309-311"
|
||||
- emulator: rpcs3
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- PS3UPDAT.PUP
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: command line --installfw
|
||||
fails_when_missing: false
|
||||
depends_on_option: "Rpcs3Generator.getFirmwareVersion() is None"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/rpcs3/rpcs3Generator.py:355-358"
|
||||
- emulator: ryujinx
|
||||
build: standalone
|
||||
kind: copies
|
||||
names:
|
||||
- switch/prod.keys
|
||||
case_insensitive: false
|
||||
search_dir: /userdata/bios
|
||||
passes_to: /userdata/system/configs/Ryujinx/system/prod.keys
|
||||
fails_when_missing: false
|
||||
depends_on_option: "ryujinxKeys.exists()"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/ryujinx/ryujinxGenerator.py:107-109"
|
||||
- emulator: pcsx2
|
||||
build: standalone
|
||||
kind: copies
|
||||
names:
|
||||
- patches.zip
|
||||
case_insensitive: false
|
||||
search_dir: /usr/share/knulli/datainit/bios/ps2
|
||||
passes_to: /userdata/bios/ps2/patches.zip
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not pcsx2Patches.exists()"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py:78,\
|
||||
\ 121-122"
|
||||
- emulator: aethersx2
|
||||
build: standalone
|
||||
kind: copies
|
||||
names:
|
||||
- patches.zip
|
||||
case_insensitive: false
|
||||
search_dir: /usr/share/knulli/datainit/bios/ps2
|
||||
passes_to: /userdata/bios/ps2/patches.zip
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not pcsx2Patches.exists()"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/aethersx2/aethersx2Generator.py:59,\
|
||||
\ 93-94"
|
||||
- emulator: gsplus
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios
|
||||
passes_to: g_cfg_rom_path
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/gsplus/gsplusGenerator.py:106"
|
||||
- emulator: tsugaru
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/fmtowns
|
||||
passes_to: command line argument 1
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/tsugaru/tsugaruGenerator.py:18-19"
|
||||
- emulator: scummvm
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/scummvm/extra
|
||||
passes_to: command line --extrapath
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/scummvm/scummvmGenerator.py:117-123"
|
||||
- emulator: amiberry
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/amiga/
|
||||
passes_to: rom_path
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/amiberry/amiberryGenerator.py:40"
|
||||
- emulator: fsuae
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/amiga
|
||||
passes_to: command line --kickstarts_dir
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/fsuae/fsuaeGenerator.py:63-69"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <romDirname>;/userdata/bios/mame;/userdata/bios
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: messMode == -1
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:116-117"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <romDirname>;/userdata/bios/mame;/userdata/bios;/userdata/roms/mame;<softDir>
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not (messMode == -1) and softList in subdirSoftList"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:116,\
|
||||
\ 119-120"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <romDirname>;/userdata/bios/mame;/userdata/bios;/userdata/roms/mame
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not (messMode == -1) and not (softList in subdirSoftList)"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:116,\
|
||||
\ 119, 122"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/mame/samples
|
||||
passes_to: command line -samplepath
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:129"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /var/run/mame_artwork/;/usr/bin/mame/artwork/;/userdata/bios/mame/artwork;/userdata/decorations
|
||||
passes_to: command line -artpath
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:130"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/mame/artwork/crosshairs
|
||||
passes_to: command line -crosshairpath
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:180"
|
||||
- emulator: openmsx
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/Machines
|
||||
passes_to: "written: filepool add -path /userdata/bios/Machines -types system_rom\
|
||||
\ -position 1"
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:95"
|
||||
- emulator: openmsx
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/openmsx
|
||||
passes_to: "written: filepool add -path /userdata/bios/openmsx -types system_rom\
|
||||
\ -position 2"
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:96"
|
||||
- emulator: fpinball
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios
|
||||
passes_to: environment W_CACHE
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not wsh57_done.exists()"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/fpinball/fpinballGenerator.py:37,\
|
||||
\ 39-43"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/
|
||||
passes_to: system_directory
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroRetroarchCustom.py:98"
|
||||
- emulator: puae, puae2021, uae4arm
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/amiga/
|
||||
passes_to: system_directory
|
||||
fails_when_missing: false
|
||||
depends_on_option: "system.config['core'] == 'puae' or system.config['core'] ==\
|
||||
\ 'puae2021' or system.config['core'] == 'uae4arm'"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroConfig.py:293-295"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <rom.parent>;/userdata/bios/mame/;/userdata/bios/
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "system.name in ['mame', 'neogeo', 'lcdgames', 'plugnplay', 'vis']"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:64,\
|
||||
\ 77"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <rom.parent>;/userdata/bios/
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not (system.name in ['mame', 'neogeo', 'lcdgames', 'plugnplay',\
|
||||
\ 'vis']) and messSysName[messMode] == ''"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:64,\
|
||||
\ 123, 132"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <softDir>;/userdata/bios/
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not (system.name in ['mame', 'neogeo', 'lcdgames', 'plugnplay',\
|
||||
\ 'vis']) and not (messSysName[messMode] == '') and softList != ''"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:64,\
|
||||
\ 123, 197, 204"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: <rom.parent>;/userdata/bios/
|
||||
passes_to: command line -rompath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "not (system.name in ['mame', 'neogeo', 'lcdgames', 'plugnplay',\
|
||||
\ 'vis']) and not (messSysName[messMode] == '') and not (softList != '')"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:64,\
|
||||
\ 123, 197, 263"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /var/run/mame_artwork/;/usr/bin/mame/artwork/;/userdata/bios/lr-mame/artwork;/userdata/bios/mame/artwork;/userdata/decorations
|
||||
passes_to: command line -artpath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "system.config['core'] != 'same_cdi' and not (system.isOptSet('sharemameart')\
|
||||
\ and (not system.getOptBoolean('sharemameart'))) and not system.name == 'ti99'"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:412-416,\
|
||||
\ 419-420"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /var/run/mame_artwork/;/usr/bin/mame/artwork/;/userdata/bios/lr-mame/artwork
|
||||
passes_to: command line -artpath
|
||||
fails_when_missing: false
|
||||
depends_on_option: "system.config['core'] != 'same_cdi' and not (not (system.isOptSet('sharemameart')\
|
||||
\ and (not system.getOptBoolean('sharemameart')))) and not system.name == 'ti99'"
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:412-415,\
|
||||
\ 418-420"
|
||||
- emulator: mame, mess, mamevirtual, same_cdi
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/mame/samples
|
||||
passes_to: command line -samplepath
|
||||
fails_when_missing: false
|
||||
depends_on_option: not system.name == 'ti99'
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroMAMEConfig.py:431-432,\
|
||||
\ 435"
|
||||
+27
-23
@@ -3880,9 +3880,10 @@ launchers:
|
||||
- capsimg.dll
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: copied to <retroarch>/<name>, only when the target does not exist
|
||||
passes_to: <retroarch>/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: core in capsimgCore, rom extension ".ipf"
|
||||
depends_on_option: core in capsimgCore, rom extension ".ipf", the target does not
|
||||
exist
|
||||
source_ref: "emulatorLauncher/Generators/LibRetro.Generator.cs:1018-1027, 2750"
|
||||
- emulator: demul
|
||||
build: standalone
|
||||
@@ -3943,11 +3944,11 @@ launchers:
|
||||
- segaboot.gcm
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: first found copied to <emulator>/User/Triforce/segaboot.gcm, skipped
|
||||
when the target is as large and not older
|
||||
passes_to: <emulator>/User/Triforce/segaboot.gcm
|
||||
fails_when_missing: "warning '[WARNING] Triforce: segaboot.gcm not found in bios\\\
|
||||
triforce, test menu will not be available.' when the target is absent too"
|
||||
depends_on_option: _triforce, !_crediar
|
||||
depends_on_option: _triforce, !_crediar, first name found, the target is absent,
|
||||
smaller or older
|
||||
source_ref: "emulatorLauncher/Generators/Dolphin.Generator.cs:782-783, 1048-1083"
|
||||
- emulator: dosbox-pure
|
||||
build: standalone
|
||||
@@ -3959,8 +3960,9 @@ launchers:
|
||||
- Roland_SC-88.sf2
|
||||
case_insensitive: true
|
||||
search_dir: bios/scummvm/extra
|
||||
passes_to: copied to <emulator>/system/<name>, only when the target does not exist
|
||||
passes_to: <emulator>/system/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/DosBoxPure.Generator.cs:125, 130-148"
|
||||
- emulator: dosbox-staging
|
||||
build: standalone
|
||||
@@ -3970,9 +3972,9 @@ launchers:
|
||||
- MT32_PCM.ROM
|
||||
case_insensitive: true
|
||||
search_dir: bios/scummvm/extra
|
||||
passes_to: copied to <emulator>/mt32-roms/<name>, only when the target does not
|
||||
exist
|
||||
passes_to: <emulator>/mt32-roms/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/DosBoxStaging.Generator.cs:321, 326-334"
|
||||
- emulator: dosbox-staging
|
||||
build: standalone
|
||||
@@ -3982,9 +3984,9 @@ launchers:
|
||||
- Roland_SC-88.sf2
|
||||
case_insensitive: true
|
||||
search_dir: bios/scummvm/extra
|
||||
passes_to: copied to <emulator>/soundfonts/<name>, only when the target does not
|
||||
exist
|
||||
passes_to: <emulator>/soundfonts/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/DosBoxStaging.Generator.cs:318, 336-344"
|
||||
- emulator: duckstation
|
||||
build: standalone
|
||||
@@ -4426,9 +4428,9 @@ launchers:
|
||||
names: *id001
|
||||
case_insensitive: true
|
||||
search_dir: bios/hatari/tos
|
||||
passes_to: copied to bios/hatarib/<value>, only when the target does not exist
|
||||
passes_to: bios/hatarib/<value>
|
||||
fails_when_missing: false
|
||||
depends_on_option: hatarib_tos
|
||||
depends_on_option: hatarib_tos, the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3434-3435, 3457-3460"
|
||||
- emulator: jzintv
|
||||
build: standalone
|
||||
@@ -4620,9 +4622,9 @@ launchers:
|
||||
- syscard3.pce
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "copied to <mesen>/Firmware/[BIOS] Super CD-ROM System (Japan) (v3.0).pce,\
|
||||
\ only when the target does not exist"
|
||||
passes_to: "<mesen>/Firmware/[BIOS] Super CD-ROM System (Japan) (v3.0).pce"
|
||||
fails_when_missing: "ApplicationException 'PCE CD firmware is missing (syscard3.pce)'"
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/Mesen.Generator.cs:254-269"
|
||||
- emulator: mesen
|
||||
build: standalone
|
||||
@@ -4632,10 +4634,10 @@ launchers:
|
||||
- SGB2.sfc
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: copied to <mesen>/Firmware/SGB1.sfc, SGB2.sfc, only when the target does
|
||||
not exist
|
||||
passes_to: <mesen>/Firmware/SGB1.sfc, <mesen>/Firmware/SGB2.sfc
|
||||
fails_when_missing: "ApplicationException 'Super Gameboy firmware is missing (SGB1.sfc\
|
||||
\ and/or SGB2.sfc)'"
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/Mesen.Generator.cs:281-304"
|
||||
- emulator: mesen
|
||||
build: standalone
|
||||
@@ -4644,9 +4646,9 @@ launchers:
|
||||
- gba_bios.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: copied to <mesen>/Firmware/gba_bios.bin, only when the target does not
|
||||
exist
|
||||
passes_to: <mesen>/Firmware/gba_bios.bin
|
||||
fails_when_missing: false
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/Mesen.Generator.cs:307-324"
|
||||
- emulator: mesen
|
||||
build: standalone
|
||||
@@ -4655,8 +4657,9 @@ launchers:
|
||||
- colecovision.rom
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: copied to <mesen>/Firmware/bios.col, only when the target does not exist
|
||||
passes_to: <mesen>/Firmware/bios.col
|
||||
fails_when_missing: "ApplicationException 'Colecovision firmware is missing (colecovision.rom)'"
|
||||
depends_on_option: the target does not exist
|
||||
source_ref: "emulatorLauncher/Generators/Mesen.Generator.cs:326-346"
|
||||
- emulator: openmsx
|
||||
build: standalone
|
||||
@@ -5269,9 +5272,9 @@ launchers:
|
||||
- kick40068.A4000
|
||||
case_insensitive: true
|
||||
search_dir: bios, bios/amiga, bios/Amiga
|
||||
passes_to: copied to <WHDBoot>/Devs/Kickstarts/<name> from the system's Kickstart
|
||||
candidates
|
||||
passes_to: <WHDBoot>/Devs/Kickstarts/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: the system's Kickstart candidates
|
||||
source_ref: "emulatorLauncher/Generators/Uae.Generator.cs:1002-1011, 1026-1032"
|
||||
- emulator: xemu
|
||||
build: standalone
|
||||
@@ -5343,9 +5346,10 @@ launchers:
|
||||
- IPLROM30.DAT
|
||||
case_insensitive: true
|
||||
search_dir: bios/keropi
|
||||
passes_to: copied to <emulator>/<name>, only when the target does not exist
|
||||
passes_to: <emulator>/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: "!string.IsNullOrEmpty(biosRoot), Directory.Exists(biosPath)"
|
||||
depends_on_option: "!string.IsNullOrEmpty(biosRoot), Directory.Exists(biosPath),\
|
||||
\ the target does not exist"
|
||||
source_ref: "emulatorLauncher/Generators/Xm6pro.Generator.cs:118-141"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
"scans_dir",
|
||||
"package"
|
||||
],
|
||||
"description": "names_searched: tries the names in order, first found wins. fixed_name: writes or opens one path per name. core_option: sets a core option whose value names the file. passes_path: hands over a directory. links, copies: links or copies a file to where the emulator reads it. moves: moves a file the user left in one folder to the folder the launcher links from. scans_dir: the emulator picks from a directory. package: names no file, records what a package builds (`repo`) and the systems it registers (`systems`)"
|
||||
"description": "names_searched: tries the names in order, first found wins. fixed_name: writes or opens one path per name. core_option: sets a core option whose value names the file. passes_path: hands over a directory. links, copies: links or copies a file to where the emulator reads it (a copy names its files under search_dir, the folder it copies from, passes_to is the target, and the case that skips it is in depends_on_option). moves: moves a file the user left in one folder to the folder the launcher links from. scans_dir: the emulator picks from a directory. package: names no file, records what a package builds (`repo`) and the systems it registers (`systems`)"
|
||||
},
|
||||
"package": {
|
||||
"type": "string",
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
"""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, the atari800 core is built to read a config naming its OS ROMs.
|
||||
|
||||
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,
|
||||
describe_keys,
|
||||
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,
|
||||
describe_keys,
|
||||
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"
|
||||
ATARI800_CONFIG = "packages/amberelec/config/distribution/configs/atari800.cfg"
|
||||
ATARI800_PATCH = "packages/games/libretro/atari800/patches/atari800-01-add-emuelec-platform.patch"
|
||||
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 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 [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])]),
|
||||
)]
|
||||
|
||||
|
||||
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, _ = script.find(rf"^\s*cp\s+\S*/{re.escape(name)}\s", f"cp of {name}")
|
||||
passed, _ = script.find(r"\shatari\s+--configfile\s", "hatari --configfile")
|
||||
files, lines = _bios_files(HATARI_CONFIG, read(HATARI_CONFIG))
|
||||
return [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),
|
||||
]),
|
||||
)]
|
||||
|
||||
|
||||
def atari800(read: Read) -> list[Launcher]:
|
||||
"""The ROM keys of the config the atari800 core is built to read."""
|
||||
patch = Script(ATARI800_PATCH, read(ATARI800_PATCH))
|
||||
line, found = patch.find(r'DEFAULT_CFG_NAME="\\"([^"\\]+)\\""', "DEFAULT_CFG_NAME")
|
||||
built = found.group(1)
|
||||
if not built.endswith("/" + ATARI800_CONFIG.split("/config/", 1)[1]):
|
||||
raise LauncherPatternMissing(ATARI800_PATCH, f"DEFAULT_CFG_NAME naming {ATARI800_CONFIG}")
|
||||
files, lines = _bios_files(ATARI800_CONFIG, read(ATARI800_CONFIG))
|
||||
return [Launcher(
|
||||
emulator="atari800", build="libretro", kind="fixed_name",
|
||||
names=[n for _k, n in files], case_insensitive=False, search_dir=BIOS,
|
||||
passes_to=describe_keys(posixpath.basename(ATARI800_CONFIG), tuple(k for k, _n in files)),
|
||||
fails_when_missing=False,
|
||||
source_ref=source_ref([(ATARI800_PATCH, [line]), (ATARI800_CONFIG, lines)]),
|
||||
)]
|
||||
|
||||
|
||||
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))
|
||||
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
|
||||
launchers.append(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=condition,
|
||||
source_ref=source_ref([
|
||||
(RUNEMU, [table_line]), (path, [condition_line, number]),
|
||||
(config, [rom_path[0].line, found[0].line]),
|
||||
]),
|
||||
))
|
||||
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)
|
||||
launchers = [
|
||||
*retroarch(read), *retrorun(read), *mame(runemu),
|
||||
*atari800(read), *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]
|
||||
@@ -23,6 +23,9 @@ Verification (batocera-systems at that commit):
|
||||
(packages/amberelec/profile.d/99-distribution.conf:320) passes
|
||||
`--strictfilter`, which the script's argument switch (lines 665-687) does
|
||||
not handle: that call prints nothing and exits 0.
|
||||
|
||||
The launcher layer is read at the same commit (amberelec_launchers): runemu.py,
|
||||
the scripts its standalone table runs and the configs their packages install.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -32,6 +35,7 @@ import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from .amberelec_launchers import extract_launchers
|
||||
from .base_scraper import BaseScraper, BiosRequirement, github_headers
|
||||
from .batocera_scraper import (
|
||||
SYSTEM_SLUG_MAP as BATOCERA_SLUGS,
|
||||
@@ -155,6 +159,9 @@ class Scraper(BaseScraper):
|
||||
"standalone_cores": standalone,
|
||||
"dual_mode_cores": dual,
|
||||
"systems": systems,
|
||||
"launchers": extract_launchers(
|
||||
lambda path: self._fetch_text(_RAW.format(ref=self.commit) + path)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -58,6 +58,29 @@ def _config_option(node: ast.AST) -> tuple[str, Any] | None:
|
||||
return node.args[0].value, default
|
||||
|
||||
|
||||
def _option_names(node: ast.AST) -> list[str]:
|
||||
"""Options a node reads: system.config.get*(o), system.config[o], system.isOptSet(o)."""
|
||||
found: list[str] = []
|
||||
for child in ast.walk(node):
|
||||
option = _config_option(child)
|
||||
if option is None and (
|
||||
isinstance(child, ast.Subscript)
|
||||
and ast.unparse(child.value) == "system.config"
|
||||
and isinstance(child.slice, ast.Constant)
|
||||
):
|
||||
option = (str(child.slice.value), None)
|
||||
if option is None and (
|
||||
isinstance(child, ast.Call)
|
||||
and ast.unparse(child.func) == "system.isOptSet"
|
||||
and child.args
|
||||
and isinstance(child.args[0], ast.Constant)
|
||||
):
|
||||
option = (str(child.args[0].value), None)
|
||||
if option is not None and option[0] not in found:
|
||||
found.append(option[0])
|
||||
return found
|
||||
|
||||
|
||||
def path_constants(source: Source) -> dict[str, str]:
|
||||
"""batoceraPaths' module constants that are plain paths."""
|
||||
constants: dict[str, str] = {}
|
||||
@@ -92,12 +115,18 @@ def _path_value(node: ast.AST | None, constants: dict[str, str]) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def generator_names(source: Source) -> dict[str, list[str]]:
|
||||
"""Generator module -> the emulator or core names configgen loads it for."""
|
||||
def generator_names(source: Source, legacy_map: bool = True) -> dict[str, list[str]]:
|
||||
"""Generator module -> the emulator or core names configgen loads it for.
|
||||
|
||||
legacy_map is false for a configgen written before _LEGACY_GENERATOR_MAP
|
||||
(KNULLI's), whose importer has the plain map only.
|
||||
"""
|
||||
names: dict[str, list[str]] = {}
|
||||
plain = source.literal(source.assignment("_GENERATOR_MAP").value, "_GENERATOR_MAP")
|
||||
for emulator, (module, _cls) in plain.items():
|
||||
names.setdefault(module, []).append(emulator)
|
||||
if not legacy_map:
|
||||
return names
|
||||
legacy = source.literal(
|
||||
source.assignment("_LEGACY_GENERATOR_MAP").value, "_LEGACY_GENERATOR_MAP"
|
||||
)
|
||||
@@ -108,14 +137,23 @@ def generator_names(source: Source) -> dict[str, list[str]]:
|
||||
|
||||
|
||||
class Configgen:
|
||||
"""The configgen tree at one revision, read through `read(path)`."""
|
||||
"""The configgen tree at one revision, read through `read(path)`.
|
||||
|
||||
def __init__(self, read: Callable[[str], str]) -> None:
|
||||
root is the configgen package in the repository: Batocera's by default,
|
||||
KNULLI keeps the same package under its own path.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, read: Callable[[str], str], root: str = CONFIGGEN, legacy_map: bool = True
|
||||
) -> None:
|
||||
self._read = read
|
||||
self._sources: dict[str, Source] = {}
|
||||
self.paths = path_constants(self.source(PATHS_MODULE))
|
||||
self.generators = f"{root}/generators"
|
||||
self.importer = f"{self.generators}/importer.py"
|
||||
self.libretro_options = f"{self.generators}/libretro/libretroOptions.py"
|
||||
self.paths = path_constants(self.source(f"{root}/batoceraPaths.py"))
|
||||
self.bios = self.paths["BIOS"]
|
||||
self._generators = generator_names(self.source(IMPORTER))
|
||||
self._generators = generator_names(self.source(self.importer), legacy_map)
|
||||
|
||||
def source(self, path: str) -> Source:
|
||||
if path not in self._sources:
|
||||
@@ -123,7 +161,7 @@ class Configgen:
|
||||
return self._sources[path]
|
||||
|
||||
def generator(self, module: str) -> Source:
|
||||
return self.source(f"{GENERATORS}/{module.replace('.', '/')}.py")
|
||||
return self.source(f"{self.generators}/{module.replace('.', '/')}.py")
|
||||
|
||||
def helper(self, module: str, helper: str) -> Source:
|
||||
"""A module the generator imports with `from . import helper`."""
|
||||
@@ -137,7 +175,7 @@ class Configgen:
|
||||
)
|
||||
if not imported:
|
||||
generator.fail(f"does not import {helper}")
|
||||
return self.source(f"{GENERATORS}/{module.split('.')[0]}/{helper}.py")
|
||||
return self.source(f"{self.generators}/{module.split('.')[0]}/{helper}.py")
|
||||
|
||||
def emulator(self, module: str) -> str:
|
||||
"""The name configgen loads `module` for (importer.get_generator)."""
|
||||
@@ -146,7 +184,7 @@ class Configgen:
|
||||
package, _, leaf = module.partition(".")
|
||||
if leaf == f"{package}Generator":
|
||||
return package
|
||||
raise LauncherShapeError(f"{IMPORTER}: no emulator loads {module}")
|
||||
raise LauncherShapeError(f"{self.importer}: no emulator loads {module}")
|
||||
|
||||
def bios_name(self, node: ast.AST) -> str | None:
|
||||
"""The name under /userdata/bios that an expression spells, if any."""
|
||||
@@ -386,15 +424,28 @@ def _loaded(stmt: ast.stmt) -> set[str]:
|
||||
return {n.id for n in ast.walk(value) if isinstance(n, ast.Name)} if value else set()
|
||||
|
||||
|
||||
def _branch_assigns(stmt: ast.stmt, needed: set[str]) -> bool:
|
||||
"""Whether an if statement assigns one of the names in any branch."""
|
||||
return isinstance(stmt, ast.If) and any(
|
||||
_assigned_name(node) in needed for node in ast.walk(stmt)
|
||||
)
|
||||
|
||||
|
||||
def _trace(body: list[ast.stmt], index: int) -> list[ast.stmt]:
|
||||
"""body[index], the assignments before it that bind what it reads, and
|
||||
the command-line additions that read any of those names."""
|
||||
the command-line additions that read any of those names.
|
||||
|
||||
An if statement that assigns a name read is kept with what its test
|
||||
reads (KNULLI's `if system.isOptSet("model") ...: machine = ...`)."""
|
||||
kept = [body[index]]
|
||||
needed = _loaded(body[index])
|
||||
for stmt in reversed(body[:index]):
|
||||
if _assigned_name(stmt) in needed:
|
||||
kept.append(stmt)
|
||||
needed |= _loaded(stmt)
|
||||
elif _branch_assigns(stmt, needed):
|
||||
kept.append(stmt)
|
||||
needed |= {n.id for n in ast.walk(stmt) if isinstance(n, ast.Name)}
|
||||
produced = {name for stmt in kept if (name := _assigned_name(stmt)) is not None}
|
||||
kept += [
|
||||
stmt for stmt in body
|
||||
@@ -435,15 +486,23 @@ def hatari(cg: Configgen) -> list[Launcher]:
|
||||
and isinstance(parts[1], ast.FormattedValue)
|
||||
):
|
||||
marker, prefix = node.test.left.value, parts[0].value
|
||||
tos_path = src.assignment("tos_path", finder).value
|
||||
if isinstance(tos_path, ast.BinOp) and isinstance(tos_path.right, ast.JoinedStr):
|
||||
parts = tos_path.right.values
|
||||
if (
|
||||
len(parts) == 3
|
||||
and all(isinstance(p, ast.FormattedValue) for p in parts[:2])
|
||||
and isinstance(parts[2], ast.Constant)
|
||||
):
|
||||
suffix = parts[2].value
|
||||
# Batocera: tos_path = biosdir / f"{biosversion}{v_language}.img".
|
||||
# KNULLI: filename = f"{biosversion}{v_language}.img".
|
||||
names_built = []
|
||||
for node in ast.walk(finder):
|
||||
value = node.value if isinstance(node, ast.Assign) else None
|
||||
if isinstance(value, ast.BinOp) and isinstance(value.op, ast.Div):
|
||||
value = value.right
|
||||
if isinstance(value, ast.JoinedStr):
|
||||
parts = value.values
|
||||
if (
|
||||
len(parts) == 3
|
||||
and all(isinstance(p, ast.FormattedValue) for p in parts[:2])
|
||||
and isinstance(parts[2], ast.Constant)
|
||||
):
|
||||
names_built.append(parts[2].value)
|
||||
if len(names_built) == 1:
|
||||
suffix = names_built[0]
|
||||
if marker is None or prefix is None or suffix is None:
|
||||
src.fail("findBestTos no longer builds <tos{version} or etos*><language>.img")
|
||||
raised = [node for node in ast.walk(finder) if isinstance(node, ast.Raise)]
|
||||
@@ -466,10 +525,7 @@ def hatari(cg: Configgen) -> list[Launcher]:
|
||||
src.fail("findBestTos is not given a batoceraPaths directory")
|
||||
search_dir = cg.paths[call.args[0].id]
|
||||
kept = _trace(body, index)
|
||||
options = [
|
||||
option[0] for stmt in kept for node in ast.walk(stmt)
|
||||
if (option := _config_option(node)) is not None
|
||||
]
|
||||
options = list(dict.fromkeys(name for stmt in kept for name in _option_names(stmt)))
|
||||
spans = [src.span(stmt) for stmt in kept] + [(finder.lineno, finder.end_lineno)]
|
||||
args: list[str] = []
|
||||
second = call.args[1] if len(call.args) > 1 else None
|
||||
@@ -484,6 +540,14 @@ def hatari(cg: Configgen) -> list[Launcher]:
|
||||
args.append("{machine}")
|
||||
elif isinstance(element, ast.Name) and element.id == tos_var:
|
||||
args.append("<file>")
|
||||
elif (
|
||||
isinstance(element, ast.BinOp)
|
||||
and isinstance(element.right, ast.Name)
|
||||
and element.right.id == tos_var
|
||||
and _path_value(element.left, cg.paths) == search_dir
|
||||
):
|
||||
# KNULLI returns the bare name and joins it: BIOS / tos.
|
||||
args.append("<file>")
|
||||
if "<file>" not in args:
|
||||
src.fail("the TOS findBestTos returns is not put on the command line")
|
||||
|
||||
@@ -563,7 +627,7 @@ def melonds(cg: Configgen) -> list[Launcher]:
|
||||
node for node in ast.walk(generate)
|
||||
if isinstance(node, ast.Assign)
|
||||
and ast.unparse(node.targets[0]).startswith("base_config['Emu']['ConsoleType']")
|
||||
and _config_option(node.value) is not None
|
||||
and _option_names(node.value)
|
||||
]
|
||||
enabled = [
|
||||
(key, value) for key, value in zip(base.value.keys, base.value.values)
|
||||
@@ -574,7 +638,7 @@ def melonds(cg: Configgen) -> list[Launcher]:
|
||||
]
|
||||
if len(console) != 1 or len(enabled) != 1:
|
||||
src.fail("generate no longer sets Emu.ConsoleType from an option with external BIOS on")
|
||||
option = _config_option(console[0].value)[0] # type: ignore[index]
|
||||
option = _option_names(console[0].value)[0] # type: ignore[attr-defined]
|
||||
launchers = []
|
||||
for key, value in zip(base.value.keys, base.value.values):
|
||||
if not isinstance(value, ast.Dict) or not isinstance(key, ast.Constant):
|
||||
@@ -805,7 +869,7 @@ def environment_directories(cg: Configgen) -> list[Launcher]:
|
||||
for module, paths_module in ENVIRONMENT_GENERATORS:
|
||||
src = cg.generator(module)
|
||||
constants = dict(cg.paths)
|
||||
for node in cg.source(f"{GENERATORS}/{paths_module}").tree.body:
|
||||
for node in cg.source(f"{cg.generators}/{paths_module}").tree.body:
|
||||
name = _assigned_name(node)
|
||||
value = _path_value(getattr(node, "value", None), constants)
|
||||
if name is not None and value is not None:
|
||||
@@ -840,7 +904,7 @@ FILE_OPTIONS = (("_o2em_options", "o2em_bios"), ("_neocd_options", "neocd_bios")
|
||||
|
||||
def core_file_options(cg: Configgen) -> list[Launcher]:
|
||||
"""_set_from_system(settings, option, system, [name], default=...)."""
|
||||
src = cg.source(LIBRETRO_OPTIONS)
|
||||
src = cg.source(cg.libretro_options)
|
||||
table = src.assignment("_option_functions").value
|
||||
if not isinstance(table, ast.Dict):
|
||||
src.fail("_option_functions is not a dict display")
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -19,9 +19,10 @@ Verification (knulli-systems at the release tag):
|
||||
10834d00, es-app/src/ApiSystem.cpp:764-774) and by knulli-support:69.
|
||||
|
||||
es_systems.yml and configgen-defaults.yml have Batocera's shape, so the cores
|
||||
are read with Batocera's code. The configgen generators do not: the hatari,
|
||||
melonds and gsplus generators and libretroOptions.py are older forms than the
|
||||
ones batocera_launchers reads, so no launcher layer is written.
|
||||
are read with Batocera's code. The launcher layer comes from KNULLI's own
|
||||
configgen package and board scripts at the same tag (knulli_launchers):
|
||||
Batocera's extractors where a generator is still Batocera's code, KNULLI's
|
||||
where it is older or its own.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -44,6 +45,7 @@ from .batocera_scraper import (
|
||||
requirements_from_systems,
|
||||
)
|
||||
from .es_emulators import attach_system_cores
|
||||
from .knulli_launchers import extract_launchers
|
||||
|
||||
PLATFORM_NAME = "knulli"
|
||||
|
||||
@@ -124,6 +126,9 @@ class Scraper(BaseScraper):
|
||||
# configgen hands a libretro core /userdata/saves/<system>, as
|
||||
# Batocera's does: the systems each core runs for are its save folders.
|
||||
result["libretro_systems"] = es_systems_libretro_systems(es_systems, dropped)
|
||||
result["launchers"] = extract_launchers(
|
||||
lambda path: self._fetch_text(_RAW.format(ref=self.tag) + path)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -320,6 +320,16 @@ def feature_choices(
|
||||
return values, src.lines(feature.start(), close + len("</feature>"))
|
||||
|
||||
|
||||
# A copy record reads `names` from `search_dir` and hands them to the path in
|
||||
# `passes_to`. When the copy is skipped is a condition of the record.
|
||||
TARGET_ABSENT = "the target does not exist"
|
||||
|
||||
|
||||
def _joined(*parts: str | None) -> str | None:
|
||||
"""Conditions of a record, joined, None when there are none."""
|
||||
return ", ".join(part for part in parts if part) or None
|
||||
|
||||
|
||||
def _record(
|
||||
emulator: str, build: str, kind: str, names: list[str], ref: str, **fields: object
|
||||
) -> Record:
|
||||
@@ -611,9 +621,9 @@ def hatari_libretro(sources: dict[str, Source]) -> list[Record]:
|
||||
source_ref((src.path, [head, src.lines(shared.start(), copy.end())])),
|
||||
case_insensitive=True,
|
||||
search_dir="/".join(["bios", *folders]),
|
||||
passes_to=f"copied to bios/{own.group(2)}/<value>, only when the target does not exist",
|
||||
passes_to=f"bios/{own.group(2)}/<value>",
|
||||
fails_when_missing=False,
|
||||
depends_on_option=key,
|
||||
depends_on_option=_joined(key, TARGET_ABSENT),
|
||||
)
|
||||
)
|
||||
return records
|
||||
@@ -661,10 +671,9 @@ def mesen(sources: dict[str, Source]) -> list[Record]:
|
||||
source_ref((src.path, [src.statement_span(block)])),
|
||||
case_insensitive=True,
|
||||
search_dir="bios",
|
||||
passes_to="copied to <mesen>/Firmware/"
|
||||
+ ", ".join(destinations)
|
||||
+ ", only when the target does not exist",
|
||||
passes_to=", ".join(f"<mesen>/Firmware/{d}" for d in destinations),
|
||||
fails_when_missing=fails,
|
||||
depends_on_option=TARGET_ABSENT,
|
||||
)
|
||||
)
|
||||
return records
|
||||
@@ -804,11 +813,9 @@ def winuae(sources: dict[str, Source]) -> list[Record]:
|
||||
),
|
||||
case_insensitive=True,
|
||||
search_dir=search,
|
||||
passes_to=(
|
||||
f"copied to <WHDBoot>/{'/'.join(_strings(folder.group(1)))}/<name>"
|
||||
" from the system's Kickstart candidates"
|
||||
),
|
||||
passes_to=f"<WHDBoot>/{'/'.join(_strings(folder.group(1)))}/<name>",
|
||||
fails_when_missing=False,
|
||||
depends_on_option="the system's Kickstart candidates",
|
||||
)
|
||||
)
|
||||
return records
|
||||
@@ -2480,10 +2487,12 @@ def dolphin(sources: dict[str, Source]) -> list[Record]:
|
||||
),
|
||||
case_insensitive=True,
|
||||
search_dir="bios",
|
||||
passes_to=f"first found copied to {destination[0] if destination else target.group(1)},"
|
||||
" skipped when the target is as large and not older",
|
||||
passes_to=destination[0] if destination else target.group(1),
|
||||
fails_when_missing=f"warning '{_unescape(warning.group(1))}' when the target is absent too",
|
||||
depends_on_option=", ".join(conditions) or None,
|
||||
depends_on_option=_joined(
|
||||
*conditions, "first name found",
|
||||
"the target is absent, smaller or older",
|
||||
),
|
||||
)
|
||||
)
|
||||
return _covered(src, records, skip=(r"DolphinTool\.exe",))
|
||||
@@ -2578,9 +2587,9 @@ def _copies_if_absent(
|
||||
_path_parts(src, *returns, *[s for c in copies for s in c[3]]),
|
||||
case_insensitive=True,
|
||||
search_dir=folders.pop(),
|
||||
passes_to=f"copied to {destination}/<name>, only when the target does not exist",
|
||||
passes_to=f"{destination}/<name>",
|
||||
fails_when_missing=False,
|
||||
depends_on_option=", ".join(conditions) or None,
|
||||
depends_on_option=_joined(*conditions, TARGET_ABSENT),
|
||||
)
|
||||
)
|
||||
if not records:
|
||||
@@ -2619,7 +2628,9 @@ def capsimg(sources: dict[str, Source]) -> list[Record]:
|
||||
record["source_ref"] = source_ref(
|
||||
(src.path, [(src.line(cores.start()), core_lines[1]), src.statement_span(block)])
|
||||
)
|
||||
record["depends_on_option"] = f'core in {cores.group(1)}, rom extension "{gate.group(1)}"'
|
||||
record["depends_on_option"] = _joined(
|
||||
f'core in {cores.group(1)}, rom extension "{gate.group(1)}"', TARGET_ABSENT
|
||||
)
|
||||
return [record]
|
||||
|
||||
|
||||
@@ -3108,9 +3119,9 @@ def xm6pro(sources: dict[str, Source]) -> list[Record]:
|
||||
_path_parts(src, (src.line(table.start()), lines[1]), src.lines(method.start(), body[1])),
|
||||
case_insensitive=True,
|
||||
search_dir=folder[0],
|
||||
passes_to="copied to <emulator>/<name>, only when the target does not exist",
|
||||
passes_to="<emulator>/<name>",
|
||||
fails_when_missing=False,
|
||||
depends_on_option=", ".join(conditions) or None,
|
||||
depends_on_option=_joined(*conditions, TARGET_ABSENT),
|
||||
)
|
||||
return _covered(src, [record])
|
||||
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
"""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
|
||||
"""
|
||||
)
|
||||
|
||||
ATARI800_CFG = text(
|
||||
"""
|
||||
Atari 800 Emulator, Version 3.1.0
|
||||
ROM_OS_A_NTSC=
|
||||
ROM_OS_A_PAL=/storage/roms/bios/ATARIOSA.ROM
|
||||
ROM_5200=/storage/roms/bios/5200.rom
|
||||
"""
|
||||
)
|
||||
|
||||
ATARI800_PATCH = text(
|
||||
"""
|
||||
+ LDFLAGS += -static-libgcc
|
||||
+ CFLAGS += -DDEFAULT_CFG_NAME="\\"/storage/.config/distribution/configs/atari800.cfg\\""
|
||||
"""
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
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.ATARI800_CONFIG: ATARI800_CFG,
|
||||
al.ATARI800_PATCH: ATARI800_PATCH,
|
||||
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")
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
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.assertEqual(a500.depends_on_option, f"not ({a1200.depends_on_option})")
|
||||
|
||||
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_built_to_read_the_shipped_config(self):
|
||||
(record,) = al.atari800(read())
|
||||
self.assertEqual(record.names, ["ATARIOSA.ROM", "5200.rom"])
|
||||
self.assertEqual(record.source_ref, f"{al.ATARI800_PATCH}:2, {al.ATARI800_CONFIG}:3-4")
|
||||
|
||||
def test_atari800_built_for_another_config_raises(self):
|
||||
broken = ATARI800_PATCH.replace("atari800.cfg", "other.cfg")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
al.atari800(read(**{al.ATARI800_PATCH: broken}))
|
||||
|
||||
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), 16)
|
||||
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,639 @@
|
||||
"""KNULLI's launcher layer, read from its configgen and board scripts.
|
||||
|
||||
The fixtures are cut-down copies of the blocks each extractor reads at
|
||||
knulli-linux 20260511. Records must carry the names in the order the code
|
||||
writes or tries them, cite the fixture's own lines, and a construct that is
|
||||
gone must stop the scrape instead of returning nothing.
|
||||
"""
|
||||
|
||||
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 knulli_launchers as kl # noqa: E402
|
||||
from scraper.batocera_launchers import hatari, melonds # noqa: E402
|
||||
from scraper.configgen_launchers import LauncherShapeError # noqa: E402
|
||||
|
||||
G = kl.GENERATORS
|
||||
|
||||
|
||||
def text(block: str) -> str:
|
||||
return textwrap.dedent(block).lstrip("\n")
|
||||
|
||||
|
||||
PATHS = text(
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
KNULLI_SHARE_DIR: Final = Path('/usr/share/knulli')
|
||||
DATAINIT_DIR: Final = KNULLI_SHARE_DIR / 'datainit'
|
||||
USERDATA: Final = Path('/userdata')
|
||||
HOME: Final = USERDATA / 'system'
|
||||
CONFIGS: Final = HOME / 'configs'
|
||||
BIOS: Final = USERDATA / 'bios'
|
||||
ROMS: Final = USERDATA / 'roms'
|
||||
"""
|
||||
)
|
||||
|
||||
IMPORTER = text(
|
||||
"""
|
||||
from typing import Final
|
||||
|
||||
_GENERATOR_MAP: Final[dict[str, tuple[str, str]]] = {
|
||||
'lexaloffle': ('lexaloffle.lexaloffleGenerator', 'LexaloffleGenerator'),
|
||||
'yabasanshiro': ('yabasanshiro.yabasanshiroGenerator', 'YabasanshiroGenerator'),
|
||||
'ryujinx': ('ryujinx.ryujinxGenerator', 'RyujinxGenerator'),
|
||||
'dolphin': ('dolphin.dolphinGenerator', 'DolphinGenerator'),
|
||||
'scummvm': ('scummvm.scummvmGenerator', 'ScummVMGenerator'),
|
||||
'openmsx': ('openmsx.openmsxGenerator', 'OpenmsxGenerator'),
|
||||
'hatari': ('hatari.hatariGenerator', 'HatariGenerator'),
|
||||
'melonds': ('melonds.melondsGenerator', 'MelonDSGenerator'),
|
||||
'pcsx2': ('pcsx2.pcsx2Generator', 'Pcsx2Generator'),
|
||||
'mame': ('mame.mameGenerator', 'MameGenerator'),
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# lexaloffleGenerator.py: the binary constants, the system branches, the two
|
||||
# checks that log and return, and the command.
|
||||
LEXALOFFLE = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS, HOME, ROMS
|
||||
|
||||
PICO8_BIN_PATH: Final = BIOS / "pico-8" / "pico8"
|
||||
VOX_BIN_PATH: Final = BIOS / "voxatron" / "vox"
|
||||
|
||||
class LexaloffleGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
if (system.name == "pico8"):
|
||||
BIN_PATH=PICO8_BIN_PATH
|
||||
elif (system.name == "voxatron"):
|
||||
BIN_PATH=VOX_BIN_PATH
|
||||
else:
|
||||
return -1
|
||||
if not BIN_PATH.exists():
|
||||
eslog.error(f"Lexaloffle official binary not found at {BIN_PATH}")
|
||||
return -1
|
||||
if not os.access(BIN_PATH, os.X_OK):
|
||||
eslog.error(f"File {BIN_PATH} is not set as executable")
|
||||
return -1
|
||||
commandArray = [BIN_PATH]
|
||||
return Command.Command(array=commandArray, env={})
|
||||
"""
|
||||
)
|
||||
|
||||
# libretroOptions.py: one function of core blocks, the option or a literal.
|
||||
OPTIONS = text(
|
||||
"""
|
||||
def generateCoreSettings(coreSettings, system, rom, guns, wheels):
|
||||
if (system.config['core'] == 'o2em'):
|
||||
coreSettings.save('o2em_vkbd_transparency', '"25"')
|
||||
if system.isOptSet('o2em_bios'):
|
||||
coreSettings.save('o2em_bios', '"' + system.config['o2em_bios'] + '"')
|
||||
elif system.name == 'videopacplus':
|
||||
coreSettings.save('o2em_bios', '"g7400.bin"')
|
||||
else:
|
||||
coreSettings.save('o2em_bios', '"o2rom.bin"')
|
||||
if (system.config['core'] == 'neocd'):
|
||||
if system.isOptSet('neocd_bios'):
|
||||
coreSettings.save('neocd_bios', '"' + system.config['neocd_bios'] + '"')
|
||||
else:
|
||||
coreSettings.save('neocd_bios', '"neocd_z.rom (CDZ)"')
|
||||
"""
|
||||
)
|
||||
|
||||
PICO8 = text(
|
||||
"""
|
||||
#!/bin/sh
|
||||
|
||||
TARGET_DIR="/userdata/bios"
|
||||
PICO_DIR="$TARGET_DIR/pico-8"
|
||||
ROMS_DIR="/userdata/roms/pico8"
|
||||
TEMP_INSTALL_DIR="$ROMS_DIR/pico8-install"
|
||||
PICO_8_64_FILE="$TEMP_INSTALL_DIR/pico-8/pico8_64"
|
||||
PICO_8_DAT_FILE="$TEMP_INSTALL_DIR/pico-8/pico8.dat"
|
||||
PICO_8_ZIPFILE=$(ls -d /userdata/roms/pico8/pico-8*raspi*.zip 2>/dev/null | head -1)
|
||||
|
||||
if [ -z $PICO_8_ZIPFILE ] || [ ! -f "$PICO_8_ZIPFILE" ]; then
|
||||
echo "Please add the Raspberry Pi version of Pico-8 to roms/pico8 and try again."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mv $PICO_8_64_FILE "$PICO_DIR/pico8"
|
||||
mv $PICO_8_DAT_FILE "$PICO_DIR/"
|
||||
"""
|
||||
)
|
||||
|
||||
YABASANSHIRO = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS
|
||||
|
||||
YABA_BIOS: Final = BIOS / "saturn_bios.bin"
|
||||
|
||||
class YabasanshiroGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
commandArray = ["yabasanshiro", "-i", rom]
|
||||
|
||||
if not (system.isOptSet('yaba_bios_hle') and system.config['yaba_bios_hle'] == '1'):
|
||||
if YABA_BIOS.exists():
|
||||
commandArray[1:1] = ["-b", str(YABA_BIOS)]
|
||||
return Command.Command(array=commandArray)
|
||||
"""
|
||||
)
|
||||
|
||||
RYUJINX = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS, CONFIGS
|
||||
|
||||
ryujinxConf: Final = CONFIGS / "Ryujinx"
|
||||
ryujinxKeys: Final = BIOS / "switch" / "prod.keys"
|
||||
|
||||
class RyujinxGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
if ryujinxKeys.exists():
|
||||
shutil.copyfile(ryujinxKeys, ryujinxConf / "system" / "prod.keys")
|
||||
"""
|
||||
)
|
||||
|
||||
DOLPHIN_PATHS = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS
|
||||
|
||||
DOLPHIN_BIOS: Final = BIOS / "GC"
|
||||
"""
|
||||
)
|
||||
|
||||
DOLPHIN = text(
|
||||
"""
|
||||
from .dolphinPaths import DOLPHIN_BIOS
|
||||
|
||||
class DolphinGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
if system.isOptSet("dolphin_SkipIPL") and system.getOptBoolean("dolphin_SkipIPL"):
|
||||
ipl_regions = ["USA", "EUR", "JAP"]
|
||||
if any((DOLPHIN_BIOS / region / "IPL.bin").exists() for region in ipl_regions):
|
||||
dolphinSettings.set("Core", "SkipIPL", "False")
|
||||
else:
|
||||
dolphinSettings.set("Core", "SkipIPL", "True")
|
||||
"""
|
||||
)
|
||||
|
||||
SCUMMVM = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS
|
||||
|
||||
scummExtra: Final = BIOS / "scummvm" / "extra"
|
||||
|
||||
class ScummVMGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
mkdir_if_not_exists(scummExtra)
|
||||
commandArray = ["/usr/bin/scummvm", "-f"]
|
||||
commandArray.extend(
|
||||
[f"--joystick={id}",
|
||||
f"--extrapath={scummExtra}",
|
||||
f"{romName}"]
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
OPENMSX = text(
|
||||
"""
|
||||
class OpenmsxGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
with settings_tcl.open("a") as file:
|
||||
file.write("filepool add -path /userdata/bios/Machines -types system_rom -position 1\\n")
|
||||
"""
|
||||
)
|
||||
|
||||
LIBRETRO_CONFIG = text(
|
||||
"""
|
||||
def writeLibretroConfigToFile(retroarchConfig, system, rom):
|
||||
if (system.config['core'] == 'puae') or (system.config['core'] == 'uae4arm'):
|
||||
retroarchConfig['system_directory'] = '"/userdata/bios/amiga/"'
|
||||
if system.config['core'] in [ 'mame', 'mess' ]:
|
||||
libretroMAMEConfig.generateMAMEConfigs(controllers, system, rom, guns)
|
||||
"""
|
||||
)
|
||||
|
||||
MAME_CONFIG = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS
|
||||
|
||||
def generateMAMEConfigs(playersControllers, system, rom, guns):
|
||||
if system.name in [ 'mame', 'neogeo' ]:
|
||||
commandLine += [ '-rompath', f'"{rom.parent};/userdata/bios/mame/;/userdata/bios/"' ]
|
||||
if not system.isOptSet("sharemameart"):
|
||||
artPath = f"/usr/bin/mame/artwork/;{BIOS / 'mame' / 'artwork'}"
|
||||
else:
|
||||
artPath = f"/usr/bin/mame/artwork/"
|
||||
commandLine += [ '-artpath', artPath ]
|
||||
mkdir_if_not_exists(BIOS / "mame" / "samples")
|
||||
"""
|
||||
)
|
||||
|
||||
# hatariGenerator.py in KNULLI's older form: the language and model read
|
||||
# with subscripts, the name built bare and joined to BIOS on the command line.
|
||||
HATARI = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS
|
||||
|
||||
class HatariGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
model_mapping = {"520st_auto": { "machine": "st", "tos": "auto" }}
|
||||
commandArray = ["hatari", "--fullscreen"]
|
||||
machine = "st"
|
||||
tosversion = "auto"
|
||||
if system.isOptSet("model") and system.config["model"] in model_mapping:
|
||||
machine = model_mapping[system.config["model"]]["machine"]
|
||||
tosversion = model_mapping[system.config["model"]]["tos"]
|
||||
toslang = "us"
|
||||
if system.isOptSet("language"):
|
||||
toslang = system.config["language"]
|
||||
commandArray += ["--machine", machine]
|
||||
tos = HatariGenerator.findBestTos(BIOS, machine, tosversion, toslang)
|
||||
commandArray += [ "--tos", BIOS / tos]
|
||||
|
||||
@staticmethod
|
||||
def findBestTos(biosdir, machine, tos_version, language) -> str:
|
||||
all_languages = ["us", "uk", ""]
|
||||
all_machines_bios = {
|
||||
"st": ["etos256", "104"],
|
||||
}
|
||||
if machine in all_machines_bios:
|
||||
for v_tos_version in l_tos:
|
||||
for v_language in l_lang:
|
||||
if "etos" in v_tos_version:
|
||||
biosversion = v_tos_version
|
||||
else:
|
||||
biosversion = f"tos{v_tos_version}"
|
||||
filename = f"{biosversion}{v_language}.img"
|
||||
if os.path.exists(f"{biosdir}/{filename}"):
|
||||
return filename
|
||||
raise Exception(f"no bios found for machine {machine}")
|
||||
"""
|
||||
)
|
||||
|
||||
MELONDS = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS
|
||||
|
||||
class MelonDSGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
base_config = {
|
||||
"DS": {
|
||||
"FirmwarePath": str(BIOS / "firmware.bin"),
|
||||
"BIOS7Path": str(BIOS / "bios7.bin"),
|
||||
},
|
||||
"Emu": {
|
||||
"ExternalBIOSEnable": True
|
||||
},
|
||||
}
|
||||
if system.isOptSet("melonds_console"):
|
||||
base_config["Emu"]["ConsoleType"] = int(system.config["melonds_console"])
|
||||
else:
|
||||
base_config["Emu"]["ConsoleType"] = 0
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
# pcsx2Generator.py: the default patches copied into BIOS when absent, and
|
||||
# the BIOS folder written relative to the config folder.
|
||||
PCSX2 = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS, CONFIGS, DATAINIT_DIR
|
||||
|
||||
_PCSX2_CONFIG: Final = CONFIGS / "PCSX2"
|
||||
_PCSX2_BIOS: Final = BIOS / "ps2"
|
||||
|
||||
class Pcsx2Generator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
pcsx2Patches = _PCSX2_BIOS / "patches.zip"
|
||||
configureINI(_PCSX2_CONFIG, _PCSX2_BIOS, system)
|
||||
if not pcsx2Patches.exists():
|
||||
shutil.copy(DATAINIT_DIR / "bios" / "ps2" / "patches.zip", pcsx2Patches)
|
||||
|
||||
def configureINI(config_directory, bios_directory, system):
|
||||
configFileName = config_directory / 'inis' / "PCSX2.ini"
|
||||
pcsx2INIConfig.set("Folders", "Bios", "../../../bios/ps2")
|
||||
pcsx2INIConfig.set("Folders", "Logs", "../../logs")
|
||||
"""
|
||||
)
|
||||
|
||||
# mameGenerator.py: the Macintosh boot disk named from the bootdisk option.
|
||||
MAME = text(
|
||||
"""
|
||||
class MameGenerator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
if system.name == "macintosh" and system.isOptSet("bootdisk"):
|
||||
if system.config["bootdisk"] in [ "macos30", "macos608" ]:
|
||||
bootType = "-flop1"
|
||||
bootDisk = "/userdata/bios/" + system.config["bootdisk"] + ".img"
|
||||
else:
|
||||
bootType = "-hard"
|
||||
bootDisk = "/userdata/bios/" + system.config["bootdisk"] + ".chd"
|
||||
commandArray += [ bootType, bootDisk ]
|
||||
"""
|
||||
)
|
||||
|
||||
FEATURES = text(
|
||||
"""
|
||||
mame:
|
||||
systems:
|
||||
macintosh:
|
||||
cfeatures:
|
||||
bootdisk:
|
||||
prompt: BOOT DISK
|
||||
choices:
|
||||
"System 3.0 (LD Floppy)": macos3
|
||||
"System 6.0.8 (LD Floppy)": macos608
|
||||
"System 7.5.5 (Hard Drive)": mac755
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def tree(**overrides: str) -> dict[str, str]:
|
||||
files = {
|
||||
f"{kl.CONFIGGEN}/batoceraPaths.py": PATHS,
|
||||
f"{G}/importer.py": IMPORTER,
|
||||
f"{G}/lexaloffle/lexaloffleGenerator.py": LEXALOFFLE,
|
||||
kl.LIBRETRO_OPTIONS: OPTIONS,
|
||||
kl.PICO8_INSTALLER: PICO8,
|
||||
f"{G}/yabasanshiro/yabasanshiroGenerator.py": YABASANSHIRO,
|
||||
f"{G}/ryujinx/ryujinxGenerator.py": RYUJINX,
|
||||
f"{G}/dolphin/dolphinPaths.py": DOLPHIN_PATHS,
|
||||
f"{G}/dolphin/dolphinGenerator.py": DOLPHIN,
|
||||
f"{G}/scummvm/scummvmGenerator.py": SCUMMVM,
|
||||
f"{G}/openmsx/openmsxGenerator.py": OPENMSX,
|
||||
f"{G}/libretro/libretroConfig.py": LIBRETRO_CONFIG,
|
||||
f"{G}/libretro/libretroMAMEConfig.py": MAME_CONFIG,
|
||||
f"{G}/hatari/hatariGenerator.py": HATARI,
|
||||
f"{G}/melonds/melondsGenerator.py": MELONDS,
|
||||
f"{G}/pcsx2/pcsx2Generator.py": PCSX2,
|
||||
f"{G}/mame/mameGenerator.py": MAME,
|
||||
kl.ES_FEATURES: FEATURES,
|
||||
}
|
||||
files.update(overrides)
|
||||
return files
|
||||
|
||||
|
||||
def configgen(**overrides: str) -> kl.Configgen:
|
||||
files = tree(**overrides)
|
||||
return kl.configgen(files.__getitem__)
|
||||
|
||||
|
||||
def sites(path: str, kind: str, **fields: object) -> list[dict]:
|
||||
spec = kl.GenericSites(path, kind, **fields) # type: ignore[arg-type]
|
||||
return [launcher.entry() for launcher in kl.bios_sites(configgen(), spec)]
|
||||
|
||||
|
||||
class SharedExtractors(unittest.TestCase):
|
||||
"""Batocera's extractors read KNULLI's older generator forms."""
|
||||
|
||||
def test_hatari_with_a_bare_name_joined_to_bios(self):
|
||||
(st,) = hatari(configgen())
|
||||
self.assertEqual(st.names, ["etos256{L}.img", "tos104{L}.img"])
|
||||
self.assertEqual(st.expands, {"L": ["us", "uk", ""]})
|
||||
self.assertEqual(st.passes_to, "--machine st --tos <file>")
|
||||
self.assertEqual(st.fails_when_missing, "no bios found for machine st")
|
||||
self.assertEqual(st.depends_on_option, "model, language")
|
||||
self.assertTrue(st.source_ref.startswith(f"{G}/hatari/hatariGenerator.py:"))
|
||||
|
||||
def test_hatari_reads_the_knulli_package(self):
|
||||
(st,) = hatari(configgen())
|
||||
self.assertNotIn("package/batocera", st.source_ref)
|
||||
|
||||
def test_hatari_name_built_another_way_raises(self):
|
||||
broken = HATARI.replace('filename = f"{biosversion}{v_language}.img"', "filename = x")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
hatari(configgen(**{f"{G}/hatari/hatariGenerator.py": broken}))
|
||||
|
||||
def test_melonds_console_read_by_subscript(self):
|
||||
(ds,) = melonds(configgen())
|
||||
self.assertEqual(ds.names, ["firmware.bin", "bios7.bin"])
|
||||
self.assertEqual(ds.depends_on_option, "melonds_console")
|
||||
self.assertEqual(ds.source_ref, f"{G}/melonds/melondsGenerator.py:6-12, 15")
|
||||
|
||||
def test_a_configgen_without_the_plain_map_raises(self):
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
configgen(**{f"{G}/importer.py": "X = 1\n"})
|
||||
|
||||
|
||||
class Lexaloffle(unittest.TestCase):
|
||||
def test_each_system_runs_its_binary(self):
|
||||
pico8, vox = kl.lexaloffle(configgen())
|
||||
self.assertEqual((pico8.slot, pico8.names), ("pico8", ["pico-8/pico8"]))
|
||||
self.assertEqual((vox.slot, vox.names), ("voxatron", ["voxatron/vox"]))
|
||||
self.assertEqual(pico8.search_dir, "/userdata/bios")
|
||||
self.assertEqual(pico8.passes_to, "exec")
|
||||
|
||||
def test_the_two_checks_are_the_failure(self):
|
||||
pico8, _ = kl.lexaloffle(configgen())
|
||||
self.assertEqual(
|
||||
pico8.fails_when_missing,
|
||||
"Lexaloffle official binary not found at /userdata/bios/pico-8/pico8, "
|
||||
"File /userdata/bios/pico-8/pico8 is not set as executable",
|
||||
)
|
||||
|
||||
def test_the_lines_cited(self):
|
||||
pico8, vox = kl.lexaloffle(configgen())
|
||||
path = f"{G}/lexaloffle/lexaloffleGenerator.py"
|
||||
self.assertEqual(pico8.source_ref, f"{path}:3, 8-9, 14-20")
|
||||
self.assertEqual(vox.source_ref, f"{path}:4, 10-11, 14-20")
|
||||
|
||||
def test_a_raise_in_place_of_the_log_raises(self):
|
||||
broken = LEXALOFFLE.replace(
|
||||
'eslog.error(f"Lexaloffle official binary not found at {BIN_PATH}")\n return -1',
|
||||
'raise BatoceraException(f"Lexaloffle official binary not found at {BIN_PATH}")',
|
||||
)
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
kl.lexaloffle(configgen(**{f"{G}/lexaloffle/lexaloffleGenerator.py": broken}))
|
||||
|
||||
|
||||
class PicoInstaller(unittest.TestCase):
|
||||
def test_the_files_moved_into_bios(self):
|
||||
(record,) = kl.pico8_installer(tree().__getitem__, "/userdata/bios")
|
||||
self.assertEqual(record.kind, "moves")
|
||||
self.assertEqual(record.names, ["pico-8/pico8", "pico-8/pico8.dat"])
|
||||
self.assertEqual(
|
||||
record.passes_to,
|
||||
"moved from /userdata/roms/pico8/pico-8*raspi*.zip into /userdata/bios/pico-8",
|
||||
)
|
||||
self.assertEqual(
|
||||
record.fails_when_missing,
|
||||
"Please add the Raspberry Pi version of Pico-8 to roms/pico8 and try again.",
|
||||
)
|
||||
self.assertEqual(record.source_ref, f"{kl.PICO8_INSTALLER}:3-12, 16-17")
|
||||
|
||||
def test_an_installer_that_moves_nothing_into_bios_raises(self):
|
||||
broken = PICO8.replace('"$PICO_DIR/', '"/tmp/')
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
kl.pico8_installer(tree(**{kl.PICO8_INSTALLER: broken}).__getitem__, "/userdata/bios")
|
||||
|
||||
|
||||
class CoreFileOptions(unittest.TestCase):
|
||||
def test_option_then_literal_defaults(self):
|
||||
o2em, neocd = kl.core_file_options(configgen())
|
||||
self.assertEqual(o2em.emulator, "o2em")
|
||||
self.assertEqual(o2em.names, ["g7400.bin", "o2rom.bin"])
|
||||
self.assertEqual(o2em.depends_on_option, "o2em_bios (default by system.name == 'videopacplus')")
|
||||
self.assertEqual(o2em.source_ref, f"{kl.LIBRETRO_OPTIONS}:2, 4-9")
|
||||
self.assertEqual((neocd.names, neocd.depends_on_option), (["neocd_z.rom (CDZ)"], "neocd_bios"))
|
||||
|
||||
def test_a_default_that_is_no_literal_raises(self):
|
||||
broken = OPTIONS.replace("'\"o2rom.bin\"'", "default_bios")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
kl.core_file_options(configgen(**{kl.LIBRETRO_OPTIONS: broken}))
|
||||
|
||||
|
||||
class BiosSites(unittest.TestCase):
|
||||
def test_a_flag_on_the_command_line_under_a_presence_test(self):
|
||||
(record,) = sites("yabasanshiro/yabasanshiroGenerator.py", "fixed_name")
|
||||
self.assertEqual(record["names"], ["saturn_bios.bin"])
|
||||
self.assertEqual(record["passes_to"], "command line -b")
|
||||
self.assertEqual(
|
||||
record["depends_on_option"],
|
||||
"not (system.isOptSet('yaba_bios_hle') and system.config['yaba_bios_hle'] == '1')",
|
||||
)
|
||||
self.assertEqual(record["source_ref"], f"{G}/yabasanshiro/yabasanshiroGenerator.py:9-11")
|
||||
|
||||
def test_a_copy_out_of_bios(self):
|
||||
(record,) = sites("ryujinx/ryujinxGenerator.py", "fixed_name")
|
||||
self.assertEqual(record["kind"], "copies")
|
||||
self.assertEqual(record["names"], ["switch/prod.keys"])
|
||||
self.assertEqual(record["search_dir"], "/userdata/bios")
|
||||
self.assertEqual(record["passes_to"], "/userdata/system/configs/Ryujinx/system/prod.keys")
|
||||
self.assertEqual(record["depends_on_option"], "ryujinxKeys.exists()")
|
||||
|
||||
def test_a_presence_test_over_a_literal_list(self):
|
||||
(record,) = sites("dolphin/dolphinGenerator.py", "fixed_name")
|
||||
self.assertEqual(record["names"], ["GC/{region}/IPL.bin"])
|
||||
self.assertEqual(record["expands"], {"region": ["USA", "EUR", "JAP"]})
|
||||
self.assertEqual(record["passes_to"], "presence sets [Core] SkipIPL = False")
|
||||
self.assertEqual(record["source_ref"], f"{G}/dolphin/dolphinGenerator.py:5, 7-8")
|
||||
|
||||
def test_a_flag_written_into_the_argument(self):
|
||||
(record,) = sites("scummvm/scummvmGenerator.py", "passes_path")
|
||||
self.assertEqual(record["search_dir"], "/userdata/bios/scummvm/extra")
|
||||
self.assertEqual(record["passes_to"], "command line --extrapath")
|
||||
|
||||
def test_a_line_written_to_a_config(self):
|
||||
(record,) = sites("openmsx/openmsxGenerator.py", "passes_path")
|
||||
self.assertEqual(record["search_dir"], "/userdata/bios/Machines")
|
||||
self.assertTrue(record["passes_to"].startswith("written: filepool add -path"))
|
||||
|
||||
def test_a_core_test_names_the_emulator(self):
|
||||
(record,) = sites("libretro/libretroConfig.py", "passes_path", build="libretro")
|
||||
self.assertEqual(record["emulator"], "puae, uae4arm")
|
||||
self.assertEqual(record["passes_to"], "system_directory")
|
||||
|
||||
def test_a_helper_takes_the_cores_of_its_caller(self):
|
||||
records = sites(
|
||||
"libretro/libretroMAMEConfig.py", "passes_path", build="libretro",
|
||||
caller=("libretro/libretroConfig.py", "generateMAMEConfigs"),
|
||||
)
|
||||
self.assertEqual({r["emulator"] for r in records}, {"mame, mess"})
|
||||
# The else branch binds an art path outside /userdata/bios: no record.
|
||||
rompath, art = records
|
||||
self.assertEqual(rompath["search_dir"], "<rom.parent>;/userdata/bios/mame/;/userdata/bios/")
|
||||
self.assertEqual(rompath["passes_to"], "command line -rompath")
|
||||
self.assertEqual(art["search_dir"], "/usr/bin/mame/artwork/;/userdata/bios/mame/artwork")
|
||||
self.assertEqual(art["depends_on_option"], "not system.isOptSet('sharemameart')")
|
||||
self.assertEqual(art["source_ref"], f"{G}/libretro/libretroMAMEConfig.py:6-7, 10")
|
||||
|
||||
def test_a_generator_without_a_bios_path_raises(self):
|
||||
files = tree(**{f"{G}/openmsx/openmsxGenerator.py": "def generate():\n return 1\n"})
|
||||
spec = kl.GenericSites("openmsx/openmsxGenerator.py", "passes_path")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
kl.bios_sites(kl.configgen(files.__getitem__), spec)
|
||||
|
||||
|
||||
class CopiesAndFolders(unittest.TestCase):
|
||||
def test_a_copy_into_bios_reads_from_the_folder_it_comes_from(self):
|
||||
(record,) = sites("pcsx2/pcsx2Generator.py", "fixed_name")
|
||||
self.assertEqual(record["kind"], "copies")
|
||||
self.assertEqual(record["names"], ["patches.zip"])
|
||||
self.assertEqual(record["search_dir"], "/usr/share/knulli/datainit/bios/ps2")
|
||||
self.assertEqual(record["passes_to"], "/userdata/bios/ps2/patches.zip")
|
||||
self.assertEqual(record["depends_on_option"], "not pcsx2Patches.exists()")
|
||||
|
||||
def test_a_relative_folder_resolved_against_the_config_folder(self):
|
||||
(record,) = kl.relative_folders(configgen(), kl.RELATIVE_FOLDERS[:1])
|
||||
self.assertEqual(record.kind, "passes_path")
|
||||
self.assertEqual(record.search_dir, "/userdata/bios/ps2")
|
||||
self.assertEqual(
|
||||
record.passes_to,
|
||||
"inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/configs/PCSX2",
|
||||
)
|
||||
self.assertEqual(record.source_ref, f"{G}/pcsx2/pcsx2Generator.py:9, 14-15")
|
||||
|
||||
def test_a_folder_written_outside_bios_raises(self):
|
||||
broken = PCSX2.replace("../../../bios/ps2", "../../../saves/ps2")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
kl.relative_folders(
|
||||
configgen(**{f"{G}/pcsx2/pcsx2Generator.py": broken}), kl.RELATIVE_FOLDERS[:1]
|
||||
)
|
||||
|
||||
|
||||
class BootDisks(unittest.TestCase):
|
||||
def records(self, **overrides: str) -> list:
|
||||
files = tree(**overrides)
|
||||
return kl.boot_disks(kl.configgen(files.__getitem__), files.__getitem__, kl.BOOT_DISKS[:1])
|
||||
|
||||
def test_each_branch_is_a_template_over_the_values_that_reach_it(self):
|
||||
floppy, hard = self.records()
|
||||
self.assertEqual((floppy.names, floppy.passes_to), (["{bootdisk}.img"], "command line -flop1"))
|
||||
self.assertEqual(floppy.expands, {"bootdisk": ["macos30", "macos608"]})
|
||||
# The values the menu offers that the code's list does not take.
|
||||
self.assertEqual((hard.names, hard.passes_to), (["{bootdisk}.chd"], "command line -hard"))
|
||||
self.assertEqual(hard.expands, {"bootdisk": ["macos3", "mac755"]})
|
||||
self.assertTrue(hard.depends_on_option.startswith("bootdisk (system.name == 'macintosh'"))
|
||||
self.assertEqual(
|
||||
hard.source_ref,
|
||||
f"{G}/mame/mameGenerator.py:3-4, 9-10, {kl.ES_FEATURES}:8-10",
|
||||
)
|
||||
|
||||
def test_a_name_built_another_way_raises(self):
|
||||
broken = MAME.replace('"/userdata/bios/" + system.config["bootdisk"] + ".chd"', "disk")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
self.records(**{f"{G}/mame/mameGenerator.py": broken})
|
||||
|
||||
def test_an_option_the_menu_no_longer_offers_raises(self):
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
self.records(**{kl.ES_FEATURES: FEATURES.replace("bootdisk:", "other:")})
|
||||
|
||||
|
||||
class PlatformFile(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.config = yaml.safe_load((REPO_ROOT / "platforms" / "knulli.yml").read_text())
|
||||
|
||||
def test_the_layer_is_written(self):
|
||||
self.assertTrue(self.config["launchers"])
|
||||
|
||||
def test_every_reference_is_in_knulli_code(self):
|
||||
for record in self.config["launchers"]:
|
||||
self.assertTrue(
|
||||
record["source_ref"].startswith((f"{kl.CONFIGGEN}/", kl.PICO8_INSTALLER)), record
|
||||
)
|
||||
|
||||
def test_every_emulator_is_one_knulli_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 itself, and the MAME core names configgen tests that
|
||||
# KNULLI's es_systems.yml never offers.
|
||||
self.assertEqual(sorted(named - cores), ["mamevirtual", "mess", "retroarch"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1042,7 +1042,10 @@ class HelpersAndCopies(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(segaboot["kind"], "copies")
|
||||
self.assertEqual(segaboot["names"], ["triforce/segaboot.gcm", "segaboot.gcm"])
|
||||
self.assertEqual(segaboot["depends_on_option"], "_triforce, !_crediar")
|
||||
self.assertEqual(
|
||||
segaboot["depends_on_option"],
|
||||
"_triforce, !_crediar, first name found, the target is absent, smaller or older",
|
||||
)
|
||||
|
||||
def test_dosbox_copies_grouped_by_target_folder(self):
|
||||
sources = _sources(
|
||||
@@ -1055,16 +1058,17 @@ class HelpersAndCopies(unittest.TestCase):
|
||||
(
|
||||
"dosbox-pure",
|
||||
["MT32_CONTROL.ROM"],
|
||||
"copied to <emulator>/mt32-roms/<name>, only when the target does not exist",
|
||||
"<emulator>/mt32-roms/<name>",
|
||||
),
|
||||
(
|
||||
"dosbox-pure",
|
||||
["Roland_SC-55.sf2"],
|
||||
"copied to <emulator>/soundfonts/<name>, only when the target does not exist",
|
||||
"<emulator>/soundfonts/<name>",
|
||||
),
|
||||
],
|
||||
)
|
||||
self.assertEqual(records[0]["search_dir"], "bios/scummvm/extra")
|
||||
self.assertEqual(records[0]["depends_on_option"], "the target does not exist")
|
||||
|
||||
def test_tsugaru_folder_chain(self):
|
||||
[record] = rl.tsugaru(_sources(**{f"{G}/Tsugaru.Generator.cs": TSUGARU}))
|
||||
|
||||
@@ -493,7 +493,8 @@ the launcher's name, a file the core accepts: the file the collection knows
|
||||
under that name when the core takes its content, otherwise the copy held for
|
||||
the core entry read under that name. Records that only hand over a directory
|
||||
(`passes_path`, `scans_dir`, a `links` record with no names) or copy a file
|
||||
the platform already names (`copies`) place nothing, and a `search_dir` the
|
||||
the platform already names (`copies`, its `names` under the `search_dir` it
|
||||
copies from, the target in `passes_to`) place nothing, and a `search_dir` the
|
||||
code describes rather than names ("directory of the Chihiro BIOS found") is
|
||||
skipped. `$biosdir`, `${biosPath}` and `$bios_path` are the BIOS directory as
|
||||
RetroPie, EmuDeck and RetroDECK spell it, and a path from a root
|
||||
|
||||
Reference in new issue
Block a user