mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-21 03:09:08 -05:00
feat: read RetroDECK launchers from components
The scraper reads each component's prepare, launcher and functions scripts at the components commit the BIOS list comes from, and the presets they copy: the fixed paths set_setting_value writes (melonDS seven, xemu three, Dolphin GBA), the folders handed over, the dir_prep links (LRPS2 pcsx2/bios back to bios/, Dolphin and PrimeHack GC regions under saves/), package copies into bios/, RetroArch core options naming a file (pcsx2_bios, DuckStation per region), XRoar romlists, the PICO-8 exec and the RPCS3 and Vita3K firmware downloads. platforms/retrodeck.yml is regenerated at fcd0d729: only the launchers section is new.
This commit is contained in:
1 parent
15ffeddfe3
commit
74a9d7beea
4 files changed
+1813
No files matched your search
@@ -16040,3 +16040,952 @@ systems:
|
||||
native_system: coco
|
||||
component: xroar
|
||||
description: Generic disk ROM
|
||||
launchers:
|
||||
- emulator: cemu
|
||||
build: standalone
|
||||
kind: links
|
||||
names:
|
||||
- keys.txt
|
||||
case_insensitive: false
|
||||
search_dir: $bios_path/cemu
|
||||
passes_to: $XDG_DATA_HOME/Cemu/keys.txt is a symlink to it
|
||||
fails_when_missing: false
|
||||
depends_on_option: "[[ -e \"$bios_path/cemu/keys.txt\" ]]"
|
||||
source_ref: "cemu/component_prepare.sh:17"
|
||||
- emulator: cemu
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: $bios_path/cemu
|
||||
passes_to: settings.xml mlc_path
|
||||
fails_when_missing: false
|
||||
source_ref: "cemu/component_prepare.sh:14, 29"
|
||||
- emulator: dolphin
|
||||
build: standalone
|
||||
kind: links
|
||||
slot: EUR
|
||||
names: []
|
||||
search_dir: $saves_path/gc/dolphin/EU
|
||||
passes_to: $XDG_DATA_HOME/dolphin-emu/GC/EUR is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "dolphin/component_prepare.sh:19, 38"
|
||||
- emulator: dolphin
|
||||
build: standalone
|
||||
kind: links
|
||||
slot: USA
|
||||
names: []
|
||||
search_dir: $saves_path/gc/dolphin/US
|
||||
passes_to: $XDG_DATA_HOME/dolphin-emu/GC/USA is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "dolphin/component_prepare.sh:20, 39"
|
||||
- emulator: dolphin
|
||||
build: standalone
|
||||
kind: links
|
||||
slot: JAP
|
||||
names: []
|
||||
search_dir: $saves_path/gc/dolphin/JP
|
||||
passes_to: $XDG_DATA_HOME/dolphin-emu/GC/JAP is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "dolphin/component_prepare.sh:21, 40"
|
||||
- emulator: dolphin
|
||||
build: standalone
|
||||
kind: links
|
||||
names: []
|
||||
search_dir: $bios_path/Triforce
|
||||
passes_to: $XDG_DATA_HOME/dolphin-emu/Triforce is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "dolphin/component_prepare.sh:30, 49"
|
||||
- emulator: dolphin
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- gba_bios.bin
|
||||
case_insensitive: false
|
||||
search_dir: $bios_path
|
||||
passes_to: "Dolphin.ini [GBA] BIOS"
|
||||
fails_when_missing: false
|
||||
source_ref: "dolphin/component_prepare.sh:14, 50"
|
||||
- emulator: duckstation
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: $bios_path
|
||||
passes_to: "settings.ini [BIOS] SearchDirectory"
|
||||
fails_when_missing: false
|
||||
source_ref: "duckstation/component_prepare.sh:15, 33"
|
||||
- emulator: gzdoom
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: $bios_path/gzdoom
|
||||
passes_to: "gzdoom.ini [FileSearch.Directories] Path"
|
||||
fails_when_missing: false
|
||||
source_ref: "gzdoom/rd_assets/rd_config/gzdoom.ini:19, gzdoom/component_prepare.sh:23,\
|
||||
\ 28"
|
||||
- emulator: mame
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: $bios_path/mame-sa/samples
|
||||
passes_to: mame.ini samplepath
|
||||
fails_when_missing: false
|
||||
source_ref: "mame/component_prepare.sh:94, 117"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: links
|
||||
names: []
|
||||
search_dir: $bios_path
|
||||
passes_to: $XDG_CONFIG_HOME/melonDS/bios is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "melonds/component_prepare.sh:19, 41"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios9.bin
|
||||
- bios7.bin
|
||||
- firmware.bin
|
||||
- dsi_nand.bin
|
||||
- dsi_bios7.bin
|
||||
- dsi_bios9.bin
|
||||
- dsi_firmware.bin
|
||||
case_insensitive: false
|
||||
search_dir: $bios_path
|
||||
passes_to: "melonDS.toml [DS] BIOS9Path, [DS] BIOS7Path, [DS] FirmwarePath, [DSi]\
|
||||
\ NANDPath, [DSi] BIOS7Path, [DSi] BIOS9Path, [DSi] FirmwarePath"
|
||||
fails_when_missing: false
|
||||
source_ref: "melonds/component_prepare.sh:21-23, 25-28, 43-45, 47-50"
|
||||
- emulator: pcsx2
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: $bios_path
|
||||
passes_to: "PCSX2.ini [Folders] Bios"
|
||||
fails_when_missing: false
|
||||
source_ref: "pcsx2/component_prepare.sh:16, 80"
|
||||
- emulator: pico-8
|
||||
build: standalone
|
||||
kind: links
|
||||
names: []
|
||||
search_dir: $bios_path/pico-8
|
||||
passes_to: $HOME/.lexaloffle/pico-8 is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "pico-8/component_prepare.sh:15"
|
||||
- emulator: pico-8
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- pico8
|
||||
case_insensitive: false
|
||||
search_dir: $bios_path/pico-8
|
||||
passes_to: exec
|
||||
fails_when_missing: true
|
||||
source_ref: "pico-8/component_launcher.sh:3"
|
||||
- emulator: ppsspp
|
||||
build: standalone
|
||||
kind: copies
|
||||
names: []
|
||||
search_dir: $component_extras/ppsspp_foss_bios.tar.gz
|
||||
passes_to: $bios_path/PPSSPP
|
||||
fails_when_missing: false
|
||||
source_ref: "ppsspp/component_prepare.sh:38"
|
||||
- emulator: primehack
|
||||
build: standalone
|
||||
kind: links
|
||||
slot: EUR
|
||||
names: []
|
||||
search_dir: $saves_path/gc/primehack/EU
|
||||
passes_to: $XDG_DATA_HOME/primehack/GC/EUR is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "primehack/component_prepare.sh:15, 33"
|
||||
- emulator: primehack
|
||||
build: standalone
|
||||
kind: links
|
||||
slot: USA
|
||||
names: []
|
||||
search_dir: $saves_path/gc/primehack/US
|
||||
passes_to: $XDG_DATA_HOME/primehack/GC/USA is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "primehack/component_prepare.sh:16, 34"
|
||||
- emulator: primehack
|
||||
build: standalone
|
||||
kind: links
|
||||
slot: JAP
|
||||
names: []
|
||||
search_dir: $saves_path/gc/primehack/JP
|
||||
passes_to: $XDG_DATA_HOME/primehack/GC/JAP is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "primehack/component_prepare.sh:17, 35"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: copies
|
||||
names: []
|
||||
search_dir: $retroarch_extras_path/mame2003-plus.tar.gz
|
||||
passes_to: $bios_path/mame2003-plus
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:86"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: links
|
||||
names: []
|
||||
search_dir: $bios_path
|
||||
passes_to: $bios_path/pcsx2/bios is a symlink to it
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:96"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: $bios_path
|
||||
passes_to: retroarch.cfg system_directory
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:44, 216"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: copies
|
||||
names:
|
||||
- cannonball
|
||||
- dinothawr
|
||||
- DirkSimple
|
||||
- dolphin-emu
|
||||
- ecwolf.pk3
|
||||
- nxengine
|
||||
- PPSSPP
|
||||
- prboom.wad
|
||||
- qemu
|
||||
- xrick
|
||||
- mame2003
|
||||
- mame2003-plus
|
||||
- pcsx2
|
||||
- fbneo
|
||||
search_dir: $retroarch_extras_path
|
||||
passes_to: $bios_path/<name>
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:58-67, 70, 87, 95, 109"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: copies
|
||||
names:
|
||||
- Databases
|
||||
- Machines
|
||||
search_dir: $retroarch_extras_path/MSX
|
||||
passes_to: $bios_path/<name>
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:122, 124"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: copies
|
||||
names:
|
||||
- capsimg.so
|
||||
search_dir: $retroarch_extras_path/Amiga
|
||||
passes_to: $bios_path/<name>
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:131"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: copies
|
||||
names:
|
||||
- scummvm.ini
|
||||
search_dir: $component_config
|
||||
passes_to: $bios_path/<name>
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/component_prepare.sh:137"
|
||||
- emulator: rpcs3
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- PS3UPDAT.PUP
|
||||
case_insensitive: false
|
||||
search_dir: $roms_path/ps3/tmp
|
||||
passes_to: component_launcher.sh --installfw
|
||||
fails_when_missing: false
|
||||
depends_on_option: "update_rpcs3_firmware downloads http://dus01.ps3.update.playstation.net/update/ps3/image/us/2026_0318_a2b60b6ac1d2e49e230144345616927c/PS3UPDAT.PUP"
|
||||
source_ref: "rpcs3/component_functions.sh:11, 19-20"
|
||||
- emulator: vita3k
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- PSVUPDAT.PUP
|
||||
case_insensitive: false
|
||||
search_dir: /tmp
|
||||
passes_to: component_launcher.sh --firmware
|
||||
fails_when_missing: false
|
||||
depends_on_option: "update_vita3k_firmware downloads http://dus01.psv.update.playstation.net/update/psv/image/2022_0209/rel_f2c7b12fe85496ec88a0391b514d6e3b/PSVUPDAT.PUP"
|
||||
source_ref: "vita3k/component_functions.sh:5, 18, 20"
|
||||
- emulator: vita3k
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- PSP2UPDAT.PUP
|
||||
case_insensitive: false
|
||||
search_dir: /tmp
|
||||
passes_to: component_launcher.sh --firmware
|
||||
fails_when_missing: false
|
||||
depends_on_option: "update_vita3k_firmware downloads http://dus01.psp2.update.playstation.net/update/psp2/image/2019_0924/sd_8b5f60b56c3da8365b973dba570c53a5/PSP2UPDAT.PUP?dest=us"
|
||||
source_ref: "vita3k/component_functions.sh:6, 19, 21"
|
||||
- emulator: xemu
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- mcpx_1.0.bin
|
||||
- Complex.bin
|
||||
- xbox_hdd.qcow2
|
||||
case_insensitive: false
|
||||
search_dir: $bios_path
|
||||
passes_to: "xemu.toml [sys.files] bootrom_path, [sys.files] flashrom_path, [sys.files]\
|
||||
\ hdd_path"
|
||||
fails_when_missing: false
|
||||
source_ref: "xemu/component_prepare.sh:23-24, 27, 43-44, 46"
|
||||
- emulator: xemu
|
||||
build: standalone
|
||||
kind: copies
|
||||
names:
|
||||
- xbox_hdd.qcow2
|
||||
search_dir: $component_extras
|
||||
passes_to: $bios_path/<name>
|
||||
fails_when_missing: false
|
||||
depends_on_option: "[ ! -f \"$bios_path/xbox_hdd.qcow2\" ]"
|
||||
source_ref: "xemu/component_prepare.sh:32"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: xroar.conf rompath
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:290, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon
|
||||
names:
|
||||
- dragon
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:291, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: d64_1
|
||||
names:
|
||||
- d64_1
|
||||
- d64rom1
|
||||
- Dragon Data Ltd - Dragon 64 - IC17
|
||||
- Dragon Data Ltd - TANO IC18
|
||||
- Eurohard S.A. - Dragon 200 IC18
|
||||
- dragrom
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist d64_1
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:292, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: d64_2
|
||||
names:
|
||||
- d64_2
|
||||
- d64rom2
|
||||
- Dragon Data Ltd - Dragon 64 - IC18
|
||||
- Dragon Data Ltd - TANO IC17
|
||||
- Eurohard S.A. - Dragon 200 IC17
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist d64_2
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:293, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: d32
|
||||
names:
|
||||
- d32
|
||||
- dragon32
|
||||
- d32rom
|
||||
- Dragon Data Ltd - Dragon 32 - IC17
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist d32
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:294, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: d200e_1
|
||||
names:
|
||||
- d200e_1
|
||||
- d200e_rom1
|
||||
- ic18_v1.4e.ic34
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist d200e_1
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:295, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: d200e_2
|
||||
names:
|
||||
- d200e_2
|
||||
- d200e_rom2
|
||||
- ic17_v1.4e.ic37
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist d200e_2
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:296, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon64
|
||||
names:
|
||||
- d64_1
|
||||
- d64rom1
|
||||
- Dragon Data Ltd - Dragon 64 - IC17
|
||||
- Dragon Data Ltd - TANO IC18
|
||||
- Eurohard S.A. - Dragon 200 IC18
|
||||
- dragrom
|
||||
- dragon
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon64
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:297, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon64_alt
|
||||
names:
|
||||
- d64_2
|
||||
- d64rom2
|
||||
- Dragon Data Ltd - Dragon 64 - IC18
|
||||
- Dragon Data Ltd - TANO IC17
|
||||
- Eurohard S.A. - Dragon 200 IC17
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon64_alt
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:298, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon32
|
||||
names:
|
||||
- d32
|
||||
- dragon32
|
||||
- d32rom
|
||||
- Dragon Data Ltd - Dragon 32 - IC17
|
||||
- dragon
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon32
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:299, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon200e
|
||||
names:
|
||||
- d200e_1
|
||||
- d200e_rom1
|
||||
- ic18_v1.4e.ic34
|
||||
- d64_1
|
||||
- d64rom1
|
||||
- Dragon Data Ltd - Dragon 64 - IC17
|
||||
- Dragon Data Ltd - TANO IC18
|
||||
- Eurohard S.A. - Dragon 200 IC18
|
||||
- dragrom
|
||||
- dragon
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon200e
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:300, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon200e_alt
|
||||
names:
|
||||
- d200e_2
|
||||
- d200e_rom2
|
||||
- ic17_v1.4e.ic37
|
||||
- d64_2
|
||||
- d64rom2
|
||||
- Dragon Data Ltd - Dragon 64 - IC18
|
||||
- Dragon Data Ltd - TANO IC17
|
||||
- Eurohard S.A. - Dragon 200 IC17
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon200e_alt
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:301, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragon200e_charset
|
||||
names:
|
||||
- d200e_26
|
||||
- rom26.ic1
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragon200e_charset
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:302, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragonpro_boot
|
||||
names:
|
||||
- alpha-boot-v1.0
|
||||
- alpha_bt_10
|
||||
- Dragon Data Ltd - Dragon Alpha Boot v1.0 150584
|
||||
- alpha-boot-v0.4
|
||||
- alpha_bt_04
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragonpro_boot
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:303, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragonpro_basic
|
||||
names:
|
||||
- alpha-basic
|
||||
- alpha_ba
|
||||
- Dragon Data Ltd - Dragon Alpga Basic
|
||||
- d64_1
|
||||
- d64rom1
|
||||
- Dragon Data Ltd - Dragon 64 - IC17
|
||||
- Dragon Data Ltd - TANO IC18
|
||||
- Eurohard S.A. - Dragon 200 IC18
|
||||
- dragrom
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragonpro_basic
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:304, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco
|
||||
names:
|
||||
- bas13
|
||||
- bas12
|
||||
- "Color Basic v1.2 (1982)(Tandy)"
|
||||
- bas11
|
||||
- bas10
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:305, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco_ext
|
||||
names:
|
||||
- extbas11
|
||||
- extbas10
|
||||
- coco
|
||||
- COCO
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco_ext
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:306, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco1
|
||||
names:
|
||||
- bas10
|
||||
- bas13
|
||||
- bas12
|
||||
- "Color Basic v1.2 (1982)(Tandy)"
|
||||
- bas11
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco1
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:307, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco1e
|
||||
names:
|
||||
- bas11
|
||||
- bas13
|
||||
- bas12
|
||||
- "Color Basic v1.2 (1982)(Tandy)"
|
||||
- bas10
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco1e
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:308, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco1e_ext
|
||||
names:
|
||||
- extbas10
|
||||
- extbas11
|
||||
- coco
|
||||
- COCO
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco1e_ext
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:309, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco2
|
||||
names:
|
||||
- bas12
|
||||
- bas13
|
||||
- "Color Basic v1.2 (1982)(Tandy)"
|
||||
- bas11
|
||||
- bas10
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco2
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:310, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco2_ext
|
||||
names:
|
||||
- extbas11
|
||||
- extbas10
|
||||
- coco
|
||||
- COCO
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco2_ext
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:311, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco2b
|
||||
names:
|
||||
- bas13
|
||||
- bas12
|
||||
- "Color Basic v1.2 (1982)(Tandy)"
|
||||
- bas11
|
||||
- bas10
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco2b
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:312, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: deluxecoco
|
||||
names:
|
||||
- adv070_u24
|
||||
- deluxe
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist deluxecoco
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:313, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: deluxecoco1
|
||||
names:
|
||||
- adv071_u24
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist deluxecoco1
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:314, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: deluxecoco2
|
||||
names:
|
||||
- adv072_u24
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist deluxecoco2
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:315, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: deluxecoco3
|
||||
names:
|
||||
- adv073-2_u24
|
||||
- adv073_u24
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist deluxecoco3
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:316, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: mx1600
|
||||
names:
|
||||
- mx1600bas
|
||||
- mx1600bas_zephyr
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist mx1600
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:317, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: mx1600ext
|
||||
names:
|
||||
- mx1600extbas
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist mx1600ext
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:318, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco3
|
||||
names:
|
||||
- coco3
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco3
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:319, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: coco3p
|
||||
names:
|
||||
- coco3p
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist coco3p
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:320, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: glenside_ide
|
||||
names:
|
||||
- yados
|
||||
- hdblba
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist glenside_ide
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:321, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: mc10
|
||||
names:
|
||||
- mc10
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist mc10
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:322, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: alice
|
||||
names:
|
||||
- alice
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist alice
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:323, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragondos
|
||||
names:
|
||||
- ddos12a
|
||||
- ddos12
|
||||
- ddos40
|
||||
- ddos15
|
||||
- ddos10
|
||||
- Dragon Data Ltd - DragonDOS 1.0
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragondos
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:324, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dosplus
|
||||
names:
|
||||
- dplus49b
|
||||
- dplus48
|
||||
- dosplus-4.8
|
||||
- DOSPLUS
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dosplus
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:325, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: superdos
|
||||
names:
|
||||
- sdose6
|
||||
- PNP - SuperDOS E6
|
||||
- sdose5
|
||||
- sdose4
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist superdos
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:326, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: cumana
|
||||
names:
|
||||
- cdos20
|
||||
- CDOS20
|
||||
- CumanaDOSv2.0
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist cumana
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:327, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: dragondos_compat
|
||||
names:
|
||||
- dplus49b
|
||||
- dplus48
|
||||
- dosplus-4.8
|
||||
- DOSPLUS
|
||||
- sdose6
|
||||
- PNP - SuperDOS E6
|
||||
- sdose5
|
||||
- sdose4
|
||||
- ddos12a
|
||||
- ddos12
|
||||
- ddos40
|
||||
- ddos15
|
||||
- ddos10
|
||||
- Dragon Data Ltd - DragonDOS 1.0
|
||||
- cdos20
|
||||
- CDOS20
|
||||
- CumanaDOSv2.0
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist dragondos_compat
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:328, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: rsdos
|
||||
names:
|
||||
- disk11
|
||||
- disk10
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist rsdos
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:329, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: delta
|
||||
names:
|
||||
- delta2
|
||||
- delta1a
|
||||
- delta19
|
||||
- delta
|
||||
- deltados
|
||||
- Premier Micros - DeltaDOS
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist delta
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:330, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: rsdos_becker
|
||||
names:
|
||||
- hdbdw3bck
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist rsdos_becker
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:331, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: mcx128
|
||||
names:
|
||||
- mcx128
|
||||
- MCX_Basic_21
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist mcx128
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:332, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: xroar
|
||||
build: standalone
|
||||
kind: names_searched
|
||||
slot: mcx128_azerty
|
||||
names:
|
||||
- mcx128a
|
||||
- MCX_Basic_21_AZERTY
|
||||
search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path"
|
||||
passes_to: romlist mcx128_azerty
|
||||
fails_when_missing: false
|
||||
source_ref: "xroar/rd_assets/rd_config/xroar.conf:333, xroar/component_prepare.sh:13,\
|
||||
\ 15"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: core_option
|
||||
names:
|
||||
- scph5500.bin
|
||||
passes_to: core option duckstation_BIOS.PathNTSCJ
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:165, retroarch/component_prepare.sh:24"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: core_option
|
||||
names:
|
||||
- scph5501.bin
|
||||
passes_to: core option duckstation_BIOS.PathNTSCU
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:166, retroarch/component_prepare.sh:24"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: core_option
|
||||
names:
|
||||
- scph5502.bin
|
||||
passes_to: core option duckstation_BIOS.PathPAL
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:167, retroarch/component_prepare.sh:24"
|
||||
- emulator: retroarch
|
||||
build: libretro
|
||||
kind: core_option
|
||||
names:
|
||||
- scph10000.bin
|
||||
passes_to: core option pcsx2_bios
|
||||
fails_when_missing: false
|
||||
source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:515, retroarch/component_prepare.sh:24"
|
||||
@@ -0,0 +1,498 @@
|
||||
"""RetroDECK's launcher layer, read from each component's scripts.
|
||||
|
||||
A component's `component_prepare.sh` writes the emulator's config on reset:
|
||||
melonDS gets seven fixed paths under $bios_path, xemu three, DuckStation and
|
||||
PCSX2 a directory to search. It links folders the emulator reads into or out
|
||||
of bios/ (LRPS2's pcsx2/bios points back at bios/, Dolphin's GC/<region>
|
||||
folders live under saves/), and copies package-provided trees into bios/.
|
||||
The presets it copies carry the rest: RetroArch core options that name a
|
||||
BIOS file, XRoar's ROM search lists. RPCS3's firmware is downloaded by
|
||||
RetroDECK itself and PICO-8 is started from a binary the user drops in bios/.
|
||||
|
||||
The shell is read as text, never run. Every line of a prepare or launcher
|
||||
script that names $bios_path must match a known construct, else the scrape
|
||||
stops with LauncherPatternMissing: an unclassified use would vanish from the
|
||||
platform file without a word.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shlex
|
||||
from collections.abc import Callable
|
||||
from pathlib import PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from .retropie_launchers import Launcher, LauncherPatternMissing, merge_records, source_ref
|
||||
except ImportError:
|
||||
from retropie_launchers import Launcher, LauncherPatternMissing, merge_records, source_ref
|
||||
|
||||
BIOS = "$bios_path"
|
||||
PLACEHOLDER = "RETRODECKBIOSDIR"
|
||||
CORE_OPTIONS = "retroarch/rd_assets/rd_config/retroarch-core-options.cfg"
|
||||
# The component that runs libretro cores.
|
||||
LIBRETRO_COMPONENT = "retroarch"
|
||||
# Values of a core option that name a file rather than a setting.
|
||||
_FILE_VALUE = re.compile(r"\.(bin|rom|img|zip|bios)$", re.IGNORECASE)
|
||||
_EXTENSION = re.compile(r"\.[A-Za-z0-9]{1,6}$")
|
||||
_ASSIGN = re.compile(r'^([A-Za-z_]\w*)="([^"]*)"$')
|
||||
_GC_REGION = re.compile(r"/GC/(EUR|USA|JAP)$")
|
||||
|
||||
# Commands whose $bios_path argument creates or backs up a folder, logs, or
|
||||
# only tests a path.
|
||||
HOUSEKEEPING = frozenset({"create_dir", "log", "mkdir", "chmod", "rm", "unlink"})
|
||||
|
||||
Read = Callable[[str], "str | None"]
|
||||
|
||||
|
||||
def _words(text: str) -> list[str]:
|
||||
try:
|
||||
return shlex.split(text, comments=True)
|
||||
except ValueError:
|
||||
return text.split()
|
||||
|
||||
|
||||
def _unquote(value: str) -> str:
|
||||
return value.strip().strip("'\"")
|
||||
|
||||
|
||||
def _split_path(path: str) -> tuple[str, str]:
|
||||
pure = PurePosixPath(path.rstrip("/"))
|
||||
return str(pure.parent), pure.name
|
||||
|
||||
|
||||
def _is_file(path: str) -> bool:
|
||||
return bool(_EXTENSION.search(PurePosixPath(path).name))
|
||||
|
||||
|
||||
def _mentions_bios(text: str) -> bool:
|
||||
return BIOS in text or "${bios_path}" in text
|
||||
|
||||
|
||||
class Script:
|
||||
"""One component shell script, line by line, with its enclosing tests."""
|
||||
|
||||
def __init__(self, path: str, text: str) -> None:
|
||||
self.path = path
|
||||
self.lines = text.splitlines()
|
||||
|
||||
def statements(self) -> list[tuple[int, list[str], str, list[str]]]:
|
||||
"""(line, words, raw, enclosing conditions) for each command, `&&` chains split."""
|
||||
found = []
|
||||
branches: list[str] = []
|
||||
for number, line in enumerate(self.lines, 1):
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
opened = re.match(r"^(if|elif)\s+(.*?)\s*;\s*then\s*$", stripped)
|
||||
if opened is not None:
|
||||
condition = opened.group(2)
|
||||
if opened.group(1) == "if":
|
||||
branches.append(condition)
|
||||
elif branches:
|
||||
branches[-1] = condition
|
||||
continue
|
||||
if stripped == "else" and branches:
|
||||
branches[-1] = f"not ({branches[-1]})"
|
||||
continue
|
||||
if stripped == "fi":
|
||||
if branches:
|
||||
branches.pop()
|
||||
continue
|
||||
shown = [c for c in branches if "$action" not in c]
|
||||
for part in re.split(r"\s+&&\s+|\s+\|\|\s+", stripped):
|
||||
words = _words(part)
|
||||
if words:
|
||||
found.append((number, words, part, shown))
|
||||
return found
|
||||
|
||||
|
||||
class Component:
|
||||
"""What one component's scripts and presets hand its emulator."""
|
||||
|
||||
def __init__(self, name: str, read: Read) -> None:
|
||||
self.name = name
|
||||
self.read = read
|
||||
self.build = "libretro" if name == LIBRETRO_COMPONENT else "standalone"
|
||||
self.records: list[Launcher] = []
|
||||
self.variables = self._variables()
|
||||
|
||||
def _variables(self) -> dict[str, str]:
|
||||
text = self.read(f"{self.name}/component_functions.sh") or ""
|
||||
return {
|
||||
m.group(1): m.group(2)
|
||||
for m in (_ASSIGN.match(line.strip()) for line in text.splitlines())
|
||||
if m is not None
|
||||
}
|
||||
|
||||
def resolve(self, word: str) -> str:
|
||||
"""A word with the component's own bios/ paths filled in (`$retroarch_config_scummvm`)."""
|
||||
def fill(found: re.Match[str]) -> str:
|
||||
value = self.variables.get(found.group(1) or found.group(2), "")
|
||||
return value if _mentions_bios(value) else found.group(0)
|
||||
|
||||
return re.sub(r"\$\{(\w+)\}|\$(\w+)", fill, word)
|
||||
|
||||
def config_name(self, value: str) -> str:
|
||||
"""`melonDS.toml` for `$melonds_config`, as component_functions.sh defines it."""
|
||||
variable = re.fullmatch(r"\$\{?(\w+)\}?", value)
|
||||
if variable is None or variable.group(1) not in self.variables:
|
||||
return value
|
||||
return PurePosixPath(self.variables[variable.group(1)]).name
|
||||
|
||||
def record(
|
||||
self, kind: str, names: list[str], lines: list[tuple[str, int]], **fields: Any
|
||||
) -> None:
|
||||
self.records.append(Launcher(
|
||||
emulator=fields.pop("emulator", self.name), build=fields.pop("build", self.build),
|
||||
kind=kind, names=names, source_ref=source_ref(lines), lines=list(lines), **fields,
|
||||
))
|
||||
|
||||
def read_prepare(self) -> None:
|
||||
path = f"{self.name}/component_prepare.sh"
|
||||
text = self.read(path)
|
||||
if text is None:
|
||||
return
|
||||
script = Script(path, text)
|
||||
settings: dict[tuple[str, ...], list[tuple[int, str, str]]] = {}
|
||||
copies: dict[tuple[str, str | None], list[tuple[int, str, str]]] = {}
|
||||
for number, words, raw, conditions in script.statements():
|
||||
depends = "; ".join(conditions) or None
|
||||
command = words[0]
|
||||
if command != "set_setting_value":
|
||||
words = [self.resolve(w) for w in words]
|
||||
raw = " ".join(words)
|
||||
if command == "set_setting_value" and len(words) >= 4:
|
||||
value = _unquote(words[3])
|
||||
if _mentions_bios(value):
|
||||
section = words[5] if len(words) > 5 else None
|
||||
where = f"[{section}] {words[2]}" if section else words[2]
|
||||
target = self.config_name(words[1])
|
||||
if _is_file(value):
|
||||
settings.setdefault(("fixed_name", target, depends or ""), []).append(
|
||||
(number, value, where)
|
||||
)
|
||||
else:
|
||||
key = ("passes_path", target, where, depends or "")
|
||||
settings.setdefault(key, []).append((number, value, where))
|
||||
continue
|
||||
if command == "dir_prep" and len(words) == 3:
|
||||
self._dir_prep(path, number, words[1], words[2], depends)
|
||||
continue
|
||||
if command == "ln" and _mentions_bios(raw):
|
||||
self._link(path, number, words, depends)
|
||||
continue
|
||||
if command == "cp" and len(words) >= 3 and _mentions_bios(words[-1]):
|
||||
source, target = words[-2], words[-1]
|
||||
folder, name = _split_path(source)
|
||||
copies.setdefault((folder, depends), []).append((number, name, target))
|
||||
continue
|
||||
if command == "tar" and "-C" in words and _mentions_bios(words[words.index("-C") + 1]):
|
||||
if "-czf" in words:
|
||||
continue
|
||||
archive = next((w for w in words if w.endswith((".tar.gz", ".tgz", ".tar"))), None)
|
||||
if archive is None:
|
||||
raise LauncherPatternMissing(
|
||||
f"{path}:{number}", f"tar into bios/ of no archive: {raw}"
|
||||
)
|
||||
self.record(
|
||||
"copies", [], [(path, number)], search_dir=archive,
|
||||
passes_to=words[words.index("-C") + 1], fails_when_missing=False,
|
||||
depends_on_option=depends,
|
||||
)
|
||||
continue
|
||||
if command == "sed" and PLACEHOLDER in raw:
|
||||
self._preset(script, number, words)
|
||||
continue
|
||||
if not _mentions_bios(raw):
|
||||
continue
|
||||
if command in HOUSEKEEPING or command in ("[[", "[", "if") or command.startswith("[["):
|
||||
continue
|
||||
raise LauncherPatternMissing(
|
||||
f"{path}:{number}", f"unrecognised use of $bios_path: {raw}"
|
||||
)
|
||||
for key, hits in settings.items():
|
||||
self._settings(path, key, hits)
|
||||
for (folder, depends), hits in copies.items():
|
||||
names = [name for _n, name, _t in hits]
|
||||
targets = [target for _n, _name, target in hits]
|
||||
self.record(
|
||||
"copies", names, [(path, n) for n, _name, _t in hits], search_dir=folder,
|
||||
passes_to=_render_targets(names, targets), fails_when_missing=False,
|
||||
depends_on_option=depends or None,
|
||||
)
|
||||
|
||||
def _settings(self, path: str, key: tuple[str, ...], hits: list[tuple[int, str, str]]) -> None:
|
||||
kind, target = key[0], key[1]
|
||||
depends = key[-1] or None
|
||||
lines = [(path, n) for n, _v, _w in hits]
|
||||
if kind == "fixed_name":
|
||||
folders = {_split_path(value)[0] for _n, value, _w in hits}
|
||||
if len(folders) != 1:
|
||||
raise LauncherPatternMissing(
|
||||
f"{path}:{hits[0][0]}", "fixed names in several folders"
|
||||
)
|
||||
names = list(dict.fromkeys(_split_path(value)[1] for _n, value, _w in hits))
|
||||
wheres = list(dict.fromkeys(w for _n, _v, w in hits))
|
||||
self.record(
|
||||
"fixed_name", names, lines,
|
||||
case_insensitive=False, search_dir=folders.pop(),
|
||||
passes_to=f"{target} " + ", ".join(wheres),
|
||||
fails_when_missing=False, depends_on_option=depends,
|
||||
)
|
||||
return
|
||||
values = {v for _n, v, _w in hits}
|
||||
if len(values) != 1:
|
||||
raise LauncherPatternMissing(f"{path}:{hits[0][0]}", f"{key[2]} set to several folders")
|
||||
self.record(
|
||||
"passes_path", [], lines, search_dir=values.pop(),
|
||||
passes_to=f"{target} {key[2]}", fails_when_missing=False, depends_on_option=depends,
|
||||
)
|
||||
|
||||
def _dir_prep(self, path: str, number: int, real: str, link: str, depends: str | None) -> None:
|
||||
"""`dir_prep REAL LINK` makes LINK a symlink to REAL (functions/other_functions.sh)."""
|
||||
region = _GC_REGION.search(link)
|
||||
if region is not None:
|
||||
self.record(
|
||||
"links", [], [(path, number)], slot=region.group(1), search_dir=real,
|
||||
passes_to=f"{link} is a symlink to it", fails_when_missing=False,
|
||||
depends_on_option=depends,
|
||||
)
|
||||
return
|
||||
if real.startswith(BIOS):
|
||||
self.record(
|
||||
"links", [], [(path, number)], search_dir=real,
|
||||
passes_to=f"{link} is a symlink to it", fails_when_missing=False,
|
||||
depends_on_option=depends,
|
||||
)
|
||||
# A user folder (saves, textures, cheats) linked into bios/ hands no BIOS over.
|
||||
|
||||
def _link(self, path: str, number: int, words: list[str], depends: str | None) -> None:
|
||||
paths = [w for w in words[1:] if not w.startswith("-")]
|
||||
if len(paths) != 2 or not _mentions_bios(paths[0]):
|
||||
raise LauncherPatternMissing(
|
||||
f"{path}:{number}", f"link without a bios/ source: {words}"
|
||||
)
|
||||
folder, name = _split_path(paths[0])
|
||||
self.record(
|
||||
"links", [name], [(path, number)], case_insensitive=False, search_dir=folder,
|
||||
passes_to=f"{paths[1]} is a symlink to it", fails_when_missing=False,
|
||||
depends_on_option=depends,
|
||||
)
|
||||
|
||||
def _preset(self, script: Script, number: int, words: list[str]) -> None:
|
||||
"""`sed -i "s#RETRODECKBIOSDIR#${bios_path}#g" "$cfg"` on a preset copied just above."""
|
||||
target = words[-1]
|
||||
substitutions = self._substitutions(script, target)
|
||||
copied = None
|
||||
for line_no, line in enumerate(script.lines[:number], 1):
|
||||
cp = _words(line.strip().split("&&")[0])
|
||||
if cp and cp[0] == "cp" and cp[-1] == target and "$component_config/" in cp[-2]:
|
||||
copied = (line_no, cp[-2].split("$component_config/", 1)[1])
|
||||
if copied is None:
|
||||
raise LauncherPatternMissing(f"{script.path}:{number}", f"no preset copied to {target}")
|
||||
preset = f"{self.name}/rd_assets/rd_config/{copied[1]}"
|
||||
text = self.read(preset)
|
||||
if text is None:
|
||||
raise LauncherPatternMissing(preset, "preset is missing")
|
||||
section = None
|
||||
found = 0
|
||||
for line_no, line in enumerate(text.splitlines(), 1):
|
||||
stripped = line.strip()
|
||||
if re.fullmatch(r"\[[^\]]+\]", stripped):
|
||||
section = stripped
|
||||
if PLACEHOLDER not in stripped or stripped.startswith("#"):
|
||||
continue
|
||||
setting = re.match(r"^([\w.$()-]+)\s*(?:=|\s)\s*(.+)$", stripped)
|
||||
if setting is None:
|
||||
raise LauncherPatternMissing(f"{preset}:{line_no}", f"placeholder in {stripped}")
|
||||
key, value = setting.group(1), _unquote(setting.group(2))
|
||||
for placeholder, variable in substitutions.items():
|
||||
value = value.replace(placeholder, variable)
|
||||
where = f"{section} {key}" if section else key
|
||||
found += 1
|
||||
applied = [(script.path, copied[0]), (script.path, number)]
|
||||
self.record(
|
||||
"passes_path", [], [(preset, line_no)] + applied,
|
||||
search_dir=value, passes_to=f"{self.config_name(target)} {where}",
|
||||
fails_when_missing=False,
|
||||
)
|
||||
if key == "rompath":
|
||||
self.romlists(preset, text, value, applied)
|
||||
if not found:
|
||||
raise LauncherPatternMissing(preset, f"no {PLACEHOLDER} line")
|
||||
|
||||
@staticmethod
|
||||
def _substitutions(script: Script, target: str) -> dict[str, str]:
|
||||
found = {}
|
||||
for line in script.lines:
|
||||
for m in re.finditer(r"s#(RETRODECK\w+)#\$\{?(\w+)\}?#g[\"']?\s+\"([^\"]+)\"", line):
|
||||
if m.group(3) == target:
|
||||
found[m.group(1)] = f"${m.group(2)}"
|
||||
return found
|
||||
|
||||
def romlists(
|
||||
self, preset: str, text: str, rompath: str, applied: list[tuple[str, int]]
|
||||
) -> None:
|
||||
"""XRoar's `romlist NAME=a,b,@other`: the names tried for each ROM, in order."""
|
||||
lists: dict[str, tuple[int, list[str]]] = {}
|
||||
for line_no, line in enumerate(text.splitlines(), 1):
|
||||
found = re.match(r"^romlist\s+([\w-]+)=(.*)$", line.strip())
|
||||
if found is not None:
|
||||
items = [_unquote(i) for i in re.findall(r'"[^"]*"|[^,]+', found.group(2))]
|
||||
lists[found.group(1)] = (line_no, items)
|
||||
if not lists:
|
||||
raise LauncherPatternMissing(preset, "rompath with no romlist")
|
||||
|
||||
def expand(name: str, seen: tuple[str, ...]) -> list[str]:
|
||||
names = []
|
||||
for item in lists[name][1]:
|
||||
if item.startswith("@"):
|
||||
ref = item[1:]
|
||||
if ref not in lists or ref in seen:
|
||||
raise LauncherPatternMissing(preset, f"romlist {name} refers to {item}")
|
||||
names += expand(ref, seen + (ref,))
|
||||
else:
|
||||
names.append(item)
|
||||
return list(dict.fromkeys(names))
|
||||
|
||||
for name, (line_no, _items) in lists.items():
|
||||
self.record(
|
||||
"names_searched", expand(name, (name,)), [(preset, line_no)] + applied,
|
||||
slot=name, search_dir=rompath, passes_to=f"romlist {name}",
|
||||
fails_when_missing=False,
|
||||
)
|
||||
|
||||
def read_launcher(self) -> None:
|
||||
path = f"{self.name}/component_launcher.sh"
|
||||
text = self.read(path)
|
||||
if text is None:
|
||||
return
|
||||
for number, words, raw, conditions in Script(path, text).statements():
|
||||
if not _mentions_bios(raw):
|
||||
continue
|
||||
if words[0] == "exec" and len(words) > 1 and _mentions_bios(words[1]):
|
||||
folder, name = _split_path(words[1])
|
||||
self.record(
|
||||
"fixed_name", [name], [(path, number)], case_insensitive=False,
|
||||
search_dir=folder, passes_to="exec", fails_when_missing=True,
|
||||
depends_on_option="; ".join(conditions) or None,
|
||||
)
|
||||
continue
|
||||
raise LauncherPatternMissing(
|
||||
f"{path}:{number}", f"unrecognised use of $bios_path: {raw}"
|
||||
)
|
||||
|
||||
def read_firmware_download(self) -> None:
|
||||
"""A firmware URL RetroDECK downloads and hands to the emulator's installer."""
|
||||
path = f"{self.name}/component_functions.sh"
|
||||
text = self.read(path)
|
||||
if text is None:
|
||||
return
|
||||
urls = {
|
||||
name: value for name, value in self.variables.items()
|
||||
if re.match(r"https?://", value)
|
||||
}
|
||||
script = Script(path, text)
|
||||
statements = script.statements()
|
||||
for number, words, _raw, _conditions in statements:
|
||||
if words[0] != "download_file" or len(words) < 3:
|
||||
continue
|
||||
source = re.fullmatch(r"\$\{?(\w+)\}?", words[1])
|
||||
if source is None or source.group(1) not in urls:
|
||||
continue
|
||||
destination = words[2]
|
||||
installs = [
|
||||
(n, w) for n, w, _r, _c in statements
|
||||
if destination in w and w[0] != "download_file"
|
||||
and any(_installs(f) for f in w)
|
||||
]
|
||||
if not installs:
|
||||
raise LauncherPatternMissing(
|
||||
f"{path}:{number}", f"{destination} is downloaded but never installed"
|
||||
)
|
||||
install_line, install = installs[0]
|
||||
flag = next(f for f in install if _installs(f))
|
||||
function = self._function_at(script, number)
|
||||
assigned = next(
|
||||
n for n, line in enumerate(script.lines, 1)
|
||||
if line.strip().startswith(f"{source.group(1)}=")
|
||||
)
|
||||
folder, name = _split_path(destination)
|
||||
self.record(
|
||||
"fixed_name", [name], [(path, assigned), (path, number), (path, install_line)],
|
||||
case_insensitive=False, search_dir=folder,
|
||||
passes_to=f"component_launcher.sh {flag}", fails_when_missing=False,
|
||||
depends_on_option=f"{function} downloads {urls[source.group(1)]}",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _function_at(script: Script, number: int) -> str:
|
||||
for line in reversed(script.lines[:number]):
|
||||
found = re.match(r"^([\w-]+)\s*\(\)\s*\{", line)
|
||||
if found is not None:
|
||||
return found.group(1)
|
||||
raise LauncherPatternMissing(f"{script.path}:{number}", "download outside a function")
|
||||
|
||||
|
||||
def _installs(word: str) -> bool:
|
||||
"""A launcher flag that installs a firmware file: RPCS3 --installfw, Vita3K --firmware."""
|
||||
return word.startswith("--install") or word == "--firmware"
|
||||
|
||||
|
||||
def _render_targets(names: list[str], targets: list[str]) -> str:
|
||||
"""Where copied names land: one folder when each keeps its name, else each path."""
|
||||
folders = {_split_path(t)[0] for t in targets}
|
||||
kept = [_split_path(t)[1] for t in targets] == [PurePosixPath(n).name for n in names]
|
||||
if kept and len(folders) == 1:
|
||||
return f"{folders.pop()}/<name>"
|
||||
return ", ".join(targets)
|
||||
|
||||
|
||||
def core_options(read: Read) -> list[Launcher]:
|
||||
"""RetroArch core options in RetroDECK's preset whose value names a BIOS file."""
|
||||
text = read(CORE_OPTIONS)
|
||||
prepare_path = f"{LIBRETRO_COMPONENT}/component_prepare.sh"
|
||||
prepare = read(prepare_path) or ""
|
||||
if text is None:
|
||||
raise LauncherPatternMissing(CORE_OPTIONS, "preset is missing")
|
||||
copied = [
|
||||
n for n, line in enumerate(prepare.splitlines(), 1)
|
||||
if "retroarch-core-options.cfg" in line and line.strip().startswith("cp ")
|
||||
]
|
||||
if not copied:
|
||||
raise LauncherPatternMissing(prepare_path, "the core options preset is never copied")
|
||||
records = []
|
||||
for line_no, line in enumerate(text.splitlines(), 1):
|
||||
found = re.match(r'^([\w.-]+)\s*=\s*"([^"]*)"$', line.strip())
|
||||
if found is None or not _FILE_VALUE.search(found.group(2)):
|
||||
continue
|
||||
key, value = found.group(1), found.group(2)
|
||||
records.append(Launcher(
|
||||
emulator=LIBRETRO_COMPONENT, build="libretro", kind="core_option", names=[value],
|
||||
passes_to=f"core option {key}", fails_when_missing=False,
|
||||
source_ref=source_ref([(CORE_OPTIONS, line_no), (prepare_path, copied[0])]),
|
||||
))
|
||||
if not records:
|
||||
raise LauncherPatternMissing(CORE_OPTIONS, "no core option names a file")
|
||||
return records
|
||||
|
||||
|
||||
def extract_launchers(components: list[str], read: Read) -> list[dict[str, Any]]:
|
||||
"""Every launcher record of the given components, read through `read(repo path)`.
|
||||
|
||||
`read` returns None only for a file the component does not have.
|
||||
"""
|
||||
records: list[Launcher] = []
|
||||
for name in components:
|
||||
component = Component(name, read)
|
||||
component.read_prepare()
|
||||
component.read_launcher()
|
||||
component.read_firmware_download()
|
||||
records += merge_records(component.records)
|
||||
if LIBRETRO_COMPONENT in components:
|
||||
records += core_options(read)
|
||||
if not records:
|
||||
raise LauncherPatternMissing("components", "no component hands a file to its emulator")
|
||||
return [record.entry() for record in records]
|
||||
@@ -40,16 +40,20 @@ try:
|
||||
BaseScraper,
|
||||
BiosRequirement,
|
||||
fetch_branch_head,
|
||||
github_headers,
|
||||
requirement_entry,
|
||||
)
|
||||
from .retrodeck_launchers import extract_launchers
|
||||
except ImportError:
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from scraper.base_scraper import (
|
||||
BaseScraper,
|
||||
BiosRequirement,
|
||||
fetch_branch_head,
|
||||
github_headers,
|
||||
requirement_entry,
|
||||
)
|
||||
from scraper.retrodeck_launchers import extract_launchers
|
||||
|
||||
PLATFORM_NAME = "retrodeck"
|
||||
COMPONENTS_REPO = "RetroDECK/components"
|
||||
@@ -233,6 +237,7 @@ class Scraper(BaseScraper):
|
||||
super().__init__()
|
||||
self.manifests_dir = manifests_dir
|
||||
self._manifests: list[tuple[str, dict]] | None = None
|
||||
self._files: dict[str, str | None] = {}
|
||||
# The manifests are read from main: one commit for the tree and for
|
||||
# every file, and that commit is the one recorded.
|
||||
self.revision = (
|
||||
@@ -317,6 +322,32 @@ class Scraper(BaseScraper):
|
||||
print(f" WARNING: {mf}: {e}", file=sys.stderr)
|
||||
return manifests
|
||||
|
||||
def component_file(self, path: str) -> str | None:
|
||||
"""A file of the components repo at the scraped revision, None when it has none.
|
||||
|
||||
Only a 404 says the file is absent: any other failure stops the
|
||||
scrape, as for the manifests.
|
||||
"""
|
||||
if path in self._files:
|
||||
return self._files[path]
|
||||
if self.manifests_dir:
|
||||
local = Path(self.manifests_dir) / path
|
||||
text = local.read_text(encoding="utf-8") if local.is_file() else None
|
||||
else:
|
||||
url = f"{RAW_BASE.format(rev=self.revision)}/{path}"
|
||||
try:
|
||||
req = urllib.request.Request(url, headers=github_headers())
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
text = resp.read().decode("utf-8")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 404:
|
||||
raise ConnectionError(f"{url}: HTTP {e.code}") from e
|
||||
text = None
|
||||
except urllib.error.URLError as e:
|
||||
raise ConnectionError(f"{url}: {e.reason}") from e
|
||||
self._files[path] = text
|
||||
return text
|
||||
|
||||
def validate_format(self, raw_data: str) -> bool:
|
||||
try:
|
||||
return isinstance(json.loads(raw_data), dict)
|
||||
@@ -478,6 +509,7 @@ class Scraper(BaseScraper):
|
||||
# its own, and retroarch is the one that runs cores.
|
||||
"standalone_cores": [c for c in cores if c != "retroarch"],
|
||||
"systems": systems,
|
||||
"launchers": extract_launchers(cores, self.component_file),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
"""RetroDECK's launcher layer read from each component's scripts and presets.
|
||||
|
||||
Each fixture copies the lines an extractor reads from RetroDECK/components
|
||||
fcd0d72995d7aa2e741e2753ac6914025b2ff1ac, placed at the line numbers they
|
||||
have there, so the source_ref a record carries is the one platforms/retrodeck.yml
|
||||
cites. A construct that is gone or unknown must stop the scrape.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
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 retrodeck_launchers as rd # noqa: E402
|
||||
|
||||
|
||||
def at(first_line: int, text: str) -> str:
|
||||
"""Text placed so its first line is line `first_line` of the file."""
|
||||
return place((first_line, text))
|
||||
|
||||
|
||||
def place(*parts: tuple[int, str]) -> str:
|
||||
"""Excerpts each placed at the line it starts on in the real file."""
|
||||
lines: list[str] = []
|
||||
for first_line, text in parts:
|
||||
excerpt = textwrap.dedent(text).lstrip("\n").splitlines()
|
||||
if len(lines) >= first_line:
|
||||
raise ValueError(f"excerpt at line {first_line} overlaps the one before")
|
||||
lines += [""] * (first_line - 1 - len(lines)) + excerpt
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
MELONDS_FUNCTIONS = at(3, 'melonds_config="$XDG_CONFIG_HOME/melonDS/melonDS.toml"\n')
|
||||
|
||||
MELONDS_PREPARE = at(7, """
|
||||
if [[ "$action" == "reset" ]]; then # Run reset-only commands
|
||||
log i "----------------------"
|
||||
log i "Resetting $component_name"
|
||||
log i "----------------------"
|
||||
|
||||
create_dir -d "$XDG_CONFIG_HOME/melonDS/"
|
||||
cp -fv "$component_config/melonDS.toml" "$melonds_config"
|
||||
|
||||
create_dir "$saves_path/nds/melonds"
|
||||
create_dir "$states_path/nds/melonds"
|
||||
create_dir "$cheats_path/MelonDS"
|
||||
|
||||
dir_prep "$bios_path" "$XDG_CONFIG_HOME/melonDS/bios"
|
||||
|
||||
set_setting_value "$melonds_config" "BIOS9Path" "\\"$bios_path/bios9.bin\\"" "melonds" "DS"
|
||||
set_setting_value "$melonds_config" "BIOS7Path" "\\"$bios_path/bios7.bin\\"" "melonds" "DS"
|
||||
set_setting_value "$melonds_config" "FirmwarePath" "\\"$bios_path/firmware.bin\\"" "melonds" "DS"
|
||||
|
||||
set_setting_value "$melonds_config" "NANDPath" "\\"$bios_path/dsi_nand.bin\\"" "melonds" "DSi"
|
||||
set_setting_value "$melonds_config" "BIOS7Path" "\\"$bios_path/dsi_bios7.bin\\"" "melonds" "DSi"
|
||||
set_setting_value "$melonds_config" "BIOS9Path" "\\"$bios_path/dsi_bios9.bin\\"" "melonds" "DSi"
|
||||
set_setting_value "$melonds_config" "FirmwarePath" "\\"$bios_path/dsi_firmware.bin\\"" "melonds" "DSi"
|
||||
|
||||
set_setting_value "$melonds_config" "SaveFilePath" "\\"$saves_path/nds/melonds\\"" "melonds" "Instance0"
|
||||
fi
|
||||
""")
|
||||
|
||||
XEMU_FUNCTIONS = at(3, 'xemu_conf="$XDG_CONFIG_HOME/xemu/xemu.toml"\n')
|
||||
|
||||
XEMU_PREPARE = at(20, """
|
||||
cp -fv "$component_config/xemu.toml" "$xemu_conf"
|
||||
create_dir "$screenshots_path/xemu"
|
||||
set_setting_value "$xemu_conf" "screenshot_dir" "'$screenshots_path/xemu'" "xemu" "General"
|
||||
set_setting_value "$xemu_conf" "bootrom_path" "'$bios_path/mcpx_1.0.bin'" "xemu" "sys.files"
|
||||
set_setting_value "$xemu_conf" "flashrom_path" "'$bios_path/Complex.bin'" "xemu" "sys.files"
|
||||
create_dir "$saves_path/xbox/xemu"
|
||||
set_setting_value "$xemu_conf" "eeprom_path" "'$saves_path/xbox/xemu/xbox-eeprom.bin'" "xemu" "sys.files"
|
||||
set_setting_value "$xemu_conf" "hdd_path" "'$bios_path/xbox_hdd.qcow2'" "xemu" "sys.files"
|
||||
|
||||
|
||||
# Preparing HD dummy Image if the image is not found
|
||||
if [ ! -f "$bios_path/xbox_hdd.qcow2" ];then
|
||||
cp -f "$component_extras/xbox_hdd.qcow2" "$bios_path/xbox_hdd.qcow2"
|
||||
fi
|
||||
""")
|
||||
|
||||
RETROARCH_PREPARE = place((23, """
|
||||
cp -fv "$component_config/retroarch.cfg" "$retroarch_config"
|
||||
cp -fv "$component_config/retroarch-core-options.cfg" "$retroarch_config_core_options"
|
||||
"""), (44, """
|
||||
set_setting_value "$retroarch_config" "system_directory" "$bios_path" "retroarch"
|
||||
"""), (95, """
|
||||
cp -rf "$retroarch_extras_path/pcsx2" "$bios_path/pcsx2"
|
||||
dir_prep "$bios_path" "$bios_path/pcsx2/bios"
|
||||
dir_prep "$saves_path/ps2/retroarch-core/LRPS2/memcards" "$bios_path/pcsx2/memcards"
|
||||
"""))
|
||||
|
||||
CORE_OPTIONS = place((165, """
|
||||
duckstation_BIOS.PathNTSCJ = "scph5500.bin"
|
||||
duckstation_BIOS.PathNTSCU = "scph5501.bin"
|
||||
duckstation_BIOS.PathPAL = "scph5502.bin"
|
||||
"""), (515, """
|
||||
pcsx2_bios = "scph10000.bin"
|
||||
pcsx2_boot_bios = "disabled"
|
||||
"""))
|
||||
|
||||
DOLPHIN_PREPARE = at(19, """
|
||||
dir_prep "$saves_path/gc/dolphin/EU" "$XDG_DATA_HOME/dolphin-emu/GC/EUR" # TODO: Multi-user one-off
|
||||
dir_prep "$saves_path/gc/dolphin/US" "$XDG_DATA_HOME/dolphin-emu/GC/USA" # TODO: Multi-user one-off
|
||||
dir_prep "$saves_path/gc/dolphin/JP" "$XDG_DATA_HOME/dolphin-emu/GC/JAP" # TODO: Multi-user one-off
|
||||
dir_prep "$screenshots_path" "$XDG_DATA_HOME/dolphin-emu/ScreenShots"
|
||||
""")
|
||||
|
||||
RPCS3_FUNCTIONS = at(11, """
|
||||
rpcs3_firmware="http://dus01.ps3.update.playstation.net/update/ps3/image/us/2026_0318_a2b60b6ac1d2e49e230144345616927c/PS3UPDAT.PUP"
|
||||
|
||||
update_rpcs3_firmware() {
|
||||
if [[ $(check_network_connectivity) == "true" ]]; then
|
||||
configurator_generic_dialog "RetroDECK Configurator - Install: RPCS3 Firmware" "This tool will download the firmware."
|
||||
(
|
||||
create_dir "$roms_path/ps3/tmp"
|
||||
chmod 777 "$roms_path/ps3/tmp"
|
||||
download_file "$rpcs3_firmware" "$roms_path/ps3/tmp/PS3UPDAT.PUP" "RPCS3 Firmware"
|
||||
bash "$rpcs3_component_dir/component_launcher.sh" --installfw "$roms_path/ps3/tmp/PS3UPDAT.PUP"
|
||||
rm -rf "$roms_path/ps3/tmp"
|
||||
)
|
||||
fi
|
||||
}
|
||||
""")
|
||||
|
||||
PICO8_LAUNCHER = at(3, 'exec "$bios_path/pico-8/pico8" -desktop_path "$screenshots_path" "$@"\n')
|
||||
|
||||
XROAR_PREPARE = at(12, """
|
||||
create_dir -d "$XDG_CONFIG_HOME/xroar"
|
||||
cp -f "$component_config/xroar.conf" "$xroar_config" && log i "Copied default xroar.conf to $xroar_config"
|
||||
sed -i "s#RETRODECKROMSDIR#${roms_path}#g" "$xroar_config" && log i "Set ROMs directory in xroar.conf"
|
||||
sed -i "s#RETRODECKBIOSDIR#${bios_path}#g" "$xroar_config" && log i "Set BIOS directory in xroar.conf"
|
||||
""")
|
||||
|
||||
XROAR_CONF = place((290, """
|
||||
rompath RETRODECKROMSDIR/coco:RETRODECKROMSDIR/dragon32:RETRODECKROMSDIR/tanodragon:RETRODECKBIOSDIR
|
||||
romlist dragon=dragon
|
||||
romlist d64_1=d64_1,d64rom1,"Dragon Data Ltd - Dragon 64 - IC17","Dragon Data Ltd - TANO IC18","Eurohard S.A. - Dragon 200 IC18",dragrom
|
||||
"""), (297, """
|
||||
romlist dragon64=@d64_1,@dragon
|
||||
"""))
|
||||
|
||||
|
||||
def tree(**components: dict[str, str]) -> dict[str, str]:
|
||||
return {
|
||||
f"{name}/{path}": text
|
||||
for name, files in components.items()
|
||||
for path, text in files.items()
|
||||
}
|
||||
|
||||
|
||||
def melonds(prepare: str) -> dict[str, str]:
|
||||
return tree(melonds={
|
||||
"component_prepare.sh": prepare, "component_functions.sh": MELONDS_FUNCTIONS,
|
||||
})
|
||||
|
||||
|
||||
def launchers(files: dict[str, str], components: list[str]) -> list[dict]:
|
||||
return rd.extract_launchers(components, files.get)
|
||||
|
||||
|
||||
RETROARCH = {
|
||||
"component_prepare.sh": RETROARCH_PREPARE,
|
||||
"rd_assets/rd_config/retroarch-core-options.cfg": CORE_OPTIONS,
|
||||
}
|
||||
|
||||
|
||||
class FixedNames(unittest.TestCase):
|
||||
def test_melonds_gets_seven_paths_in_the_order_written(self):
|
||||
files = melonds(MELONDS_PREPARE)
|
||||
fixed = next(r for r in launchers(files, ["melonds"]) if r["kind"] == "fixed_name")
|
||||
self.assertEqual(fixed["names"], [
|
||||
"bios9.bin", "bios7.bin", "firmware.bin",
|
||||
"dsi_nand.bin", "dsi_bios7.bin", "dsi_bios9.bin", "dsi_firmware.bin",
|
||||
])
|
||||
self.assertEqual(fixed["search_dir"], "$bios_path")
|
||||
self.assertTrue(
|
||||
fixed["passes_to"].startswith("melonDS.toml [DS] BIOS9Path, [DS] BIOS7Path")
|
||||
)
|
||||
self.assertEqual(fixed["source_ref"], "melonds/component_prepare.sh:21-23, 25-28")
|
||||
|
||||
def test_melonds_reads_bios_through_a_link(self):
|
||||
files = melonds(MELONDS_PREPARE)
|
||||
link = next(r for r in launchers(files, ["melonds"]) if r["kind"] == "links")
|
||||
self.assertEqual(link["passes_to"], "$XDG_CONFIG_HOME/melonDS/bios is a symlink to it")
|
||||
self.assertEqual(link["source_ref"], "melonds/component_prepare.sh:19")
|
||||
|
||||
def test_xemu_writes_three_paths_and_copies_a_blank_disk(self):
|
||||
files = tree(xemu={
|
||||
"component_prepare.sh": XEMU_PREPARE, "component_functions.sh": XEMU_FUNCTIONS,
|
||||
})
|
||||
found = launchers(files, ["xemu"])
|
||||
fixed = next(r for r in found if r["kind"] == "fixed_name")
|
||||
self.assertEqual(fixed["names"], ["mcpx_1.0.bin", "Complex.bin", "xbox_hdd.qcow2"])
|
||||
self.assertEqual(fixed["source_ref"], "xemu/component_prepare.sh:23-24, 27")
|
||||
copy = next(r for r in found if r["kind"] == "copies")
|
||||
self.assertEqual(copy["depends_on_option"], '[ ! -f "$bios_path/xbox_hdd.qcow2" ]')
|
||||
self.assertEqual(copy["source_ref"], "xemu/component_prepare.sh:32")
|
||||
|
||||
def test_pico8_starts_the_binary_the_user_supplies(self):
|
||||
files = tree(**{"pico-8": {"component_launcher.sh": PICO8_LAUNCHER}})
|
||||
record = launchers(files, ["pico-8"])[0]
|
||||
self.assertEqual((record["names"], record["search_dir"]), (["pico8"], "$bios_path/pico-8"))
|
||||
self.assertIs(record["fails_when_missing"], True)
|
||||
self.assertEqual(record["source_ref"], "pico-8/component_launcher.sh:3")
|
||||
|
||||
def test_rpcs3_firmware_is_downloaded_then_installed(self):
|
||||
files = tree(rpcs3={"component_functions.sh": RPCS3_FUNCTIONS})
|
||||
record = launchers(files, ["rpcs3"])[0]
|
||||
self.assertEqual(record["names"], ["PS3UPDAT.PUP"])
|
||||
self.assertEqual(record["passes_to"], "component_launcher.sh --installfw")
|
||||
self.assertIn("2026_0318_a2b60b6ac1d2e49e230144345616927c", record["depends_on_option"])
|
||||
self.assertEqual(record["source_ref"], "rpcs3/component_functions.sh:11, 19-20")
|
||||
|
||||
def test_a_download_never_installed_raises(self):
|
||||
broken = RPCS3_FUNCTIONS.replace("--installfw", "--run")
|
||||
with self.assertRaises(rd.LauncherPatternMissing):
|
||||
launchers(tree(rpcs3={"component_functions.sh": broken}), ["rpcs3"])
|
||||
|
||||
|
||||
class RetroArch(unittest.TestCase):
|
||||
def test_core_options_that_name_a_file(self):
|
||||
found = launchers(tree(retroarch=RETROARCH), ["retroarch"])
|
||||
found = [r for r in found if r["kind"] == "core_option"]
|
||||
self.assertEqual(
|
||||
[(r["names"], r["passes_to"]) for r in found],
|
||||
[
|
||||
(["scph5500.bin"], "core option duckstation_BIOS.PathNTSCJ"),
|
||||
(["scph5501.bin"], "core option duckstation_BIOS.PathNTSCU"),
|
||||
(["scph5502.bin"], "core option duckstation_BIOS.PathPAL"),
|
||||
(["scph10000.bin"], "core option pcsx2_bios"),
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
found[3]["source_ref"],
|
||||
"retroarch/rd_assets/rd_config/retroarch-core-options.cfg:515, "
|
||||
"retroarch/component_prepare.sh:24",
|
||||
)
|
||||
|
||||
def test_lrps2_bios_folder_points_back_at_bios(self):
|
||||
found = launchers(tree(retroarch=RETROARCH), ["retroarch"])
|
||||
links = [r for r in found if r["kind"] == "links"]
|
||||
self.assertEqual([(r["search_dir"], r["passes_to"]) for r in links],
|
||||
[("$bios_path", "$bios_path/pcsx2/bios is a symlink to it")])
|
||||
self.assertEqual(links[0]["source_ref"], "retroarch/component_prepare.sh:96")
|
||||
|
||||
def test_a_preset_never_copied_raises(self):
|
||||
prepare = RETROARCH_PREPARE.replace("retroarch-core-options.cfg", "other.cfg")
|
||||
files = tree(retroarch={**RETROARCH, "component_prepare.sh": prepare})
|
||||
with self.assertRaises(rd.LauncherPatternMissing):
|
||||
launchers(files, ["retroarch"])
|
||||
|
||||
|
||||
class Links(unittest.TestCase):
|
||||
def test_dolphin_reads_ipl_from_save_folders(self):
|
||||
found = launchers(tree(dolphin={"component_prepare.sh": DOLPHIN_PREPARE}), ["dolphin"])
|
||||
self.assertEqual(
|
||||
[(r["slot"], r["search_dir"]) for r in found],
|
||||
[("EUR", "$saves_path/gc/dolphin/EU"), ("USA", "$saves_path/gc/dolphin/US"),
|
||||
("JAP", "$saves_path/gc/dolphin/JP")],
|
||||
)
|
||||
self.assertEqual(found[1]["source_ref"], "dolphin/component_prepare.sh:20")
|
||||
|
||||
|
||||
class Presets(unittest.TestCase):
|
||||
def files(self, conf: str = XROAR_CONF) -> dict[str, str]:
|
||||
return tree(xroar={
|
||||
"component_prepare.sh": XROAR_PREPARE, "rd_assets/rd_config/xroar.conf": conf,
|
||||
})
|
||||
|
||||
def test_xroar_romlists_expand_their_references_in_order(self):
|
||||
found = launchers(self.files(), ["xroar"])
|
||||
search = next(r for r in found if r["kind"] == "passes_path")
|
||||
self.assertEqual(
|
||||
search["search_dir"],
|
||||
"$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path",
|
||||
)
|
||||
lists = {r["slot"]: r for r in found if r["kind"] == "names_searched"}
|
||||
self.assertEqual(lists["dragon64"]["names"][:2], ["d64_1", "d64rom1"])
|
||||
self.assertEqual(lists["dragon64"]["names"][-1], "dragon")
|
||||
self.assertEqual(
|
||||
lists["d64_1"]["source_ref"],
|
||||
"xroar/rd_assets/rd_config/xroar.conf:292, xroar/component_prepare.sh:13, 15",
|
||||
)
|
||||
|
||||
def test_a_reference_to_an_unknown_romlist_raises(self):
|
||||
with self.assertRaises(rd.LauncherPatternMissing):
|
||||
launchers(self.files(XROAR_CONF.replace("@dragon", "@dragonx")), ["xroar"])
|
||||
|
||||
def test_a_preset_without_the_placeholder_raises(self):
|
||||
with self.assertRaises(rd.LauncherPatternMissing):
|
||||
launchers(self.files(XROAR_CONF.replace("RETRODECKBIOSDIR", "/bios")), ["xroar"])
|
||||
|
||||
|
||||
class UnknownConstructs(unittest.TestCase):
|
||||
def test_an_unclassified_use_of_bios_path_raises(self):
|
||||
broken = MELONDS_PREPARE.replace(
|
||||
'create_dir "$cheats_path/MelonDS"', 'rsync -a "$bios_path/" "$XDG_DATA_HOME/melonDS/"'
|
||||
)
|
||||
files = melonds(broken)
|
||||
with self.assertRaises(rd.LauncherPatternMissing):
|
||||
launchers(files, ["melonds"])
|
||||
|
||||
def test_no_record_at_all_raises(self):
|
||||
with self.assertRaises(rd.LauncherPatternMissing):
|
||||
launchers({}, ["azahar"])
|
||||
|
||||
|
||||
class PlatformRecords(unittest.TestCase):
|
||||
"""The launchers platforms/retrodeck.yml carries, as the scraper wrote them."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.config = yaml.safe_load((REPO_ROOT / "platforms" / "retrodeck.yml").read_text())
|
||||
|
||||
def test_every_emulator_is_a_component_retrodeck_ships(self):
|
||||
named = {r["emulator"] for r in self.config["launchers"]}
|
||||
self.assertEqual(sorted(named - set(self.config["cores"])), [])
|
||||
|
||||
def test_every_reference_is_in_a_component(self):
|
||||
for record in self.config["launchers"]:
|
||||
first = record["source_ref"].split("/", 1)[0]
|
||||
self.assertIn(first, self.config["cores"], record)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user