diff --git a/emulators/amiberry-5.6.5.yml b/emulators/amiberry-5.6.5.yml index 8671ce5eb..5db40d55e 100644 --- a/emulators/amiberry-5.6.5.yml +++ b/emulators/amiberry-5.6.5.yml @@ -32,6 +32,13 @@ systems: standalone_directories: - "getcwd" - "rom_path = kickstarts" +config_reads: + - file: "*.uae" + source_ref: "src/main.cpp:926-934" + keys: + - key: "kickstart_rom_file" + source_ref: "src/cfgfile.cpp:6050, src/memory.cpp:1907-1916" + notes: | Amiberry 5.6.5 (src/osdep/target.h:27). Every folder it reads is below its start path: $EXTERNAL_FILES_DIR or $XDG_DATA_HOME when either holds a diff --git a/emulators/amiberry-7.1.1.yml b/emulators/amiberry-7.1.1.yml index c617c4df7..fc433e525 100644 --- a/emulators/amiberry-7.1.1.yml +++ b/emulators/amiberry-7.1.1.yml @@ -43,6 +43,15 @@ standalone_directories: - "AMIBERRY_HOME_DIR/roms" - "~/Amiberry/roms" - "whdboot_path = whdboot" +config_reads: + - file: "*.uae" + source_ref: "src/main.cpp:986-994" + keys: + - key: "kickstart_rom_file" + source_ref: "src/cfgfile.cpp:6122, src/memory.cpp:1974-1983" + - key: "$(FILE_PATH)" + source_ref: "src/cfgfile.cpp:389" + notes: | Amiberry 7.1.1 (CMakeLists.txt:3-4). The ROM path is rom_path in amiberry.conf, else roms/ below the home directory: $AMIBERRY_HOME_DIR, diff --git a/emulators/ares.yml b/emulators/ares.yml index 8f991ad13..35623cf00 100644 --- a/emulators/ares.yml +++ b/emulators/ares.yml @@ -56,6 +56,49 @@ systems: - zx-spectrum - zx-spectrum-128 +config_reads: + - file: "settings.bml" + source_ref: "desktop-ui/desktop-ui.cpp:9-35, 100-103, 114, desktop-ui/settings/settings.cpp:31-42, 263-286, nall/nall/path.cpp:123-142" + keys: + - key: "ColecoVision/Firmware/BIOS.World" + source_ref: "desktop-ui/emulator/colecovision.cpp:10-12, 42-50, mia/system/colecovision.cpp:7-9" + - key: "MSX/Firmware/BIOS.Japan" + source_ref: "desktop-ui/emulator/msx.cpp:16-19, 44-53" + - key: "FamicomDiskSystem/Firmware/BIOS.Japan" + source_ref: "desktop-ui/emulator/famicom-disk-system.cpp:17-19, 89-97" + - key: + - "Nintendo64DD/Firmware/BIOS.Japan" + - "Nintendo64DD/Firmware/BIOS.US" + - "Nintendo64DD/Firmware/BIOS.DEV" + source_ref: "desktop-ui/emulator/nintendo-64dd.cpp:15-19, 59-73" + condition: "the game's region" + - key: "GameBoyAdvance/Firmware/BIOS.World" + source_ref: "desktop-ui/emulator/game-boy-advance.cpp:12-14, 55-63" + - key: + - "MasterSystem/Firmware/BIOS.US" + - "MasterSystem/Firmware/BIOS.Japan" + - "MasterSystem/Firmware/BIOS.Europe" + source_ref: "desktop-ui/emulator/master-system.cpp:12-16, 93-107" + condition: "the game's region" + - key: "GameGear/Firmware/BIOS.World" + source_ref: "desktop-ui/emulator/game-gear.cpp:10-12, 30-40" + - key: + - "MegaCD/Firmware/BIOS.US" + - "MegaCD/Firmware/BIOS.Japan" + - "MegaCD/Firmware/BIOS.Europe" + source_ref: "desktop-ui/emulator/mega-cd.cpp:15-19, 63-77" + condition: "the disc's region" + - key: "NeoGeoPocket/Firmware/BIOS.World" + source_ref: "desktop-ui/emulator/neo-geo-pocket.cpp:10-12, 32-40" + - key: "NeoGeoPocketColor/Firmware/BIOS.World" + source_ref: "desktop-ui/emulator/neo-geo-pocket-color.cpp:10-12, 32-40" + - key: + - "PlayStation/Firmware/BIOS.US" + - "PlayStation/Firmware/BIOS.Japan" + - "PlayStation/Firmware/BIOS.Europe" + source_ref: "desktop-ui/emulator/playstation.cpp:19-23, 87-101" + condition: "the disc's region" + notes: | Multi-system emulator by Near (byuu), successor to higan/bsnes. Focuses on accuracy and preservation. 40 emulators registered at this pin diff --git a/emulators/armsx2.yml b/emulators/armsx2.yml index 5ee879e61..10de74fdc 100644 --- a/emulators/armsx2.yml +++ b/emulators/armsx2.yml @@ -48,6 +48,25 @@ data_root: source_ref: "source pcsx2/Pcsx2Config.cpp:2523-2530, pcsx2/Pcsx2Config.cpp:2540-2560, pcsx2/Pcsx2Config.cpp:2567-2589" note: "portable.ini or portable.txt beside the binary makes its folder the root" +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261101" + path: "projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/package.mk" + variable: PKG_VERSION + value: "19c83bf065ed21a19d8c46a1f578e37d2dab8efd" +config_reads: + - file: "PCSX2.ini" + platforms: [rocknix] + source_ref: "pcsx2-qt/QtHost.cpp:1307, 1325-1331, pcsx2/Pcsx2Config.cpp:2398-2491, pcsx2/VMManager.cpp:630-641" + keys: + - key: "[Folders] Bios" + source_ref: "pcsx2/Pcsx2Config.cpp:2514-2524" + condition: "[UI] SettingsVersion 1, else the ini is reset (pcsx2-qt/QtHost.cpp:1331-1342)" + - key: "[Filenames] BIOS" + source_ref: "pcsx2/Pcsx2Config.cpp:1879-1881, 2258-2264, pcsx2/ps2/BiosTools.cpp:258-282, 333-348, pcsx2/VMManager.cpp:1638" + condition: "empty: the first PS2 BIOS of 4 to 8 MB found in [Folders] Bios" + notes: | PlayStation 2 emulator, a GPL-3.0 fork of PCSX2 that replaces the x86-64 only EE, IOP, VU and vtlb fast memory recompilers with native AArch64 ones. One tree diff --git a/emulators/dsperate.yml b/emulators/dsperate.yml index 528c5a43f..2017c7e2e 100644 --- a/emulators/dsperate.yml +++ b/emulators/dsperate.yml @@ -32,6 +32,32 @@ data_root: source_ref: "src/frontend/sdl/config.cpp:39-49" note: "~/.config/dsperate when XDG_CONFIG_HOME is unset. System files are read wherever the [paths] keys of the ini point." +config_reads: + - file: "dsperate.ini" + platforms: [rocknix] + revision: "ddf2910e6d80220c6b22b4df61f0786ac9879cda" + revision_from: "PKG_VERSION 3.0.5, the release binaries CI built from tag v3.0.5" + source_ref: "src/frontend/sdl/main.cpp:1065, 1155-1157, src/frontend/sdl/config.cpp:64-81" + keys: + - key: + - "[paths] bios9" + - "[paths] bios7" + source_ref: "src/frontend/sdl/main.cpp:1175, 1270-1274, src/core/nds.cpp:114-129" + condition: "both present, otherwise FreeBIOS" + - key: "[paths] firmware" + source_ref: "src/frontend/sdl/main.cpp:1178, 1270, src/core/nds.cpp:123, 134-135" + - key: + - "[paths] bios9i" + - "[paths] bios7i" + source_ref: "src/frontend/sdl/main.cpp:1179-1186, 1319-1322, 1331-1334, src/core/nds_dsi.cpp:88-99" + condition: "DSi session" + - key: "[paths] dsi_firmware" + source_ref: "src/frontend/sdl/main.cpp:1176-1178, 1270" + condition: "DSi session" + - key: "[paths] dsi_nand" + source_ref: "src/frontend/sdl/main.cpp:1158-1162, 1324-1326, 1337, src/core/nds_dsi.cpp:114-120" + condition: "--dsi-mode, BootMenuDSi.nds or a .dspr.nds shortcut" + notes: | Nintendo DS emulator for ARM handhelds with a DSi mode. ROCKNIX installs the release tarball for its architecture and runs it with --config pointing at diff --git a/emulators/duckstation.yml b/emulators/duckstation.yml index 739810077..bfb8f089d 100644 --- a/emulators/duckstation.yml +++ b/emulators/duckstation.yml @@ -33,6 +33,61 @@ data_root: source_ref: "src/core/core.cpp:137-151, src/core/core.cpp:190-215" note: "portable.txt or settings.ini beside the executable, or beside the AppImage, makes that folder the root" +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emulators/duckstation/package.mk" + variable: PKG_VERSION + value: "fd3507c16d098fb32806c281caaefb205946da8a" + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261101" + path: "projects/ROCKNIX/packages/emulators/standalone/duckstation-sa/package.mk" + variable: PKG_VERSION + value: "0.1-10998" +config_reads: + - file: "settings.ini" + platforms: [emuelec] + source_ref: "src/frontend-common/common_host_interface.cpp:403-407, 3042-3050, 3304-3327, src/duckstation-nogui/main.cpp:62-64, 137-140, src/duckstation-nogui/nogui_host_interface.cpp:39-42" + keys: + - key: "[BIOS] SearchDirectory" + source_ref: "src/core/host_interface.cpp:253-260" + - key: + - "[BIOS] PathNTSCU" + - "[BIOS] PathNTSCJ" + - "[BIOS] PathPAL" + source_ref: "src/core/host_interface.cpp:262-375, src/core/system.cpp:817" + condition: "the console region; an empty value scans SearchDirectory" + - file: "settings.ini" + platforms: [rocknix] + revision: "9b0a4ec559ae83bfdb14685932b036ad7c1701be" + revision_from: "PKG_VERSION 0.1-10998, the AppImage of tag v0.1-10998" + source_ref: "src/core/core.cpp:105-223, 227-230, 232-275, src/duckstation-qt/qthost.cpp:796-800" + keys: + - key: "[BIOS] SearchDirectory" + source_ref: "src/core/settings.cpp:2709-2719, 2723" + - key: + - "[BIOS] PathNTSCU" + - "[BIOS] PathNTSCJ" + - "[BIOS] PathPAL" + source_ref: "src/core/bios.cpp:351-458, src/core/system.cpp:2923" + condition: "the console region; an empty value scans SearchDirectory" + - file: "settings.ini" + platforms: [retrobat] + revision: "5fd366809053fe287291de7a39752c4d5d5b146b" + revision_from: "version 0.1-11752-g5fd366809 of duckstation.exe in RetroBat's stable duckstation.7z, 2026-11-10" + source_ref: "src/core/core.cpp:131-249, 253-256, 258-301, src/duckstation-qt/qthost.cpp:803-807" + keys: + - key: "[BIOS] SearchDirectory" + source_ref: "src/core/settings.cpp:2859-2869, 2873" + - key: + - "[BIOS] PathNTSCU" + - "[BIOS] PathNTSCJ" + - "[BIOS] PathPAL" + source_ref: "src/core/bios.cpp:351-456, src/core/system.cpp:2671" + condition: "the console region; an empty value scans SearchDirectory" + notes: | DuckStation auto-detects BIOS by MD5 hash from a flat scan of the BIOS directory. ref: src/core/bios.cpp:458-479 (FindBIOSImagesInDirectory, scans all files by size) diff --git a/emulators/gzdoom.yml b/emulators/gzdoom.yml index ee25a5c18..d958edfa8 100644 --- a/emulators/gzdoom.yml +++ b/emulators/gzdoom.yml @@ -17,6 +17,17 @@ standalone_pools: "[IWADSearch.Directories] Path": "" "[IWADSearch.Directories] RecursivePath": "" +config_reads: + - file: "gzdoom.ini" + source_ref: "src/gameconfigfile.cpp:95-107, 942-951, src/common/platform/posix/unix/i_specialpaths.cpp:168-171" + keys: + - key: "[IWADSearch.Directories] Path" + source_ref: "src/d_iwad.cpp:456-470" + - key: "[FileSearch.Directories] Path" + source_ref: "src/common/utility/findfile.cpp:224-260" + - key: "[SoundfontSearch.Directories] Path" + source_ref: "src/common/audio/music/i_soundfont.cpp:393-410" + notes: | Doom-engine source port by Christoph Oelckers. Supports Doom, Heretic, Hexen, Strife, Chex Quest and derived games through a unified IWAD diff --git a/emulators/hatari.yml b/emulators/hatari.yml index 801bd818f..940f07f95 100644 --- a/emulators/hatari.yml +++ b/emulators/hatari.yml @@ -38,6 +38,45 @@ data_root: source_ref: "upstream src/paths.c:24-29, upstream src/paths.c:201-274" note: "~/.hatari is used instead when it exists and ~/.config/hatari does not. Holds hatari.cfg, hatari.nvram and auto.sav. The default tos.img is read from ../share/hatari beside the bin directory." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emulators/hatarisa/package.mk" + variable: PKG_VERSION + value: "fda67f05188a925535fefc1b0ef24eb044a029d2" + - platform: amberelec + repo: "https://github.com/AmberELEC/AmberELEC" + revision: "a21975bfb1a3af68f66e07a931b994b523e1e6e2" + path: "packages/games/emulators/hatarisa/package.mk" + variable: PKG_VERSION + value: "d876db9c4b6911c059b1d9318f779f90c1c6f2d7" + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261101" + path: "projects/ROCKNIX/packages/emulators/standalone/hatarisa/package.mk" + variable: PKG_VERSION + value: "6da06056d89bb39777063388d82d065d9e2e31fd" +config_reads: + - file: "*.cfg" + platforms: [emuelec] + source_ref: "upstream src/options.c:1187-1193, upstream src/configuration.c:1088, 1149" + keys: + - key: "[ROM] szTosImageFileName" + source_ref: "upstream src/configuration.c:481, upstream src/tos.c:975-986" + - file: "*.cfg" + platforms: [amberelec] + source_ref: "upstream src/options.c:1283-1288, upstream src/configuration.c:1061, 1123" + keys: + - key: "[ROM] szTosImageFileName" + source_ref: "upstream src/configuration.c:489, upstream src/tos.c:976-987" + - file: "*.cfg" + platforms: [rocknix] + source_ref: "upstream src/options.c:1194-1199, upstream src/configuration.c:1050, 1111" + keys: + - key: "[ROM] szTosImageFileName" + source_ref: "upstream src/configuration.c:480, upstream src/tos.c:975-986" + notes: | The main branch of libretro/hatari is the upstream Hatari tree at c453b85f, whose libretro target in src/retro carries the VFS, disk diff --git a/emulators/lrps2.yml b/emulators/lrps2.yml index 58cc5208d..d8541e5d8 100644 --- a/emulators/lrps2.yml +++ b/emulators/lrps2.yml @@ -24,6 +24,13 @@ systems: [sony-playstation-2] bios_directory: "pcsx2/bios/" resources_directory: "pcsx2/resources/" +config_reads: + - file: "core options" + source_ref: "libretro/main.cpp:506, 525-527" + keys: + - key: "pcsx2_bios" + source_ref: "libretro/main.cpp:525-536, 2552-2560" + notes: | Hard fork of PCSX2 (pre-Qt, 1.7-dev era) ported to libretro, x86_64 only, with much of the emulator since rewritten in C89. The system directory of diff --git a/emulators/mednafen.yml b/emulators/mednafen.yml index 5cdc34595..7d1a95297 100644 --- a/emulators/mednafen.yml +++ b/emulators/mednafen.yml @@ -43,6 +43,61 @@ systems: - snk-ngpc - sony-playstation +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261101" + path: "projects/ROCKNIX/packages/emulators/standalone/mednafen/package.mk" + variable: PKG_URL + value: "https://github.com/sydarn/mednafen/archive/refs/tags/1.32.1-chd.tar.gz" +config_reads: + - file: "mednafen.cfg" + platforms: [rocknix] + revision: "e46995e49cbaf1e9996a8e29cc5be16d7fe6d834" + revision_from: "tag 1.32.1-chd of the sydarn/mednafen fork, the PKG_URL ROCKNIX builds" + source_ref: "src/drivers/main.cpp:1469-1520, 1883-1974, 2249, 2405, src/general.cpp:277-300" + keys: + - key: "gba.bios" + source_ref: "src/gba/GBA.cpp:3278, 772, 2472-2481" + - key: "md.cdbios" + source_ref: "src/md/system.cpp:755, 630-640, src/md/cd/cd.cpp:894-912, 971-982" + condition: "CD content" + - key: "nes.ggrom" + source_ref: "src/nes/nes.cpp:455, 298-299, src/nes/cart.cpp:570-583" + condition: "nes.gg 1" + - key: "pce.cdbios" + source_ref: "src/pce/pce.cpp:1103, 647-649" + condition: "disc not detected as Games Express" + - key: "pce.gecdbios" + source_ref: "src/pce/pce.cpp:1104, 647-649" + condition: "disc detected as Games Express" + - key: "pce_fast.cdbios" + source_ref: "src/pce_fast/pce.cpp:647, 476-487" + - key: "pcfx.bios" + source_ref: "src/pcfx/pcfx.cpp:1115, 606-611" + - key: + - "psx.bios_eu" + - "psx.bios_jp" + - "psx.bios_na" + source_ref: "src/psx/psx.cpp:2373-2375, 1764-1775" + condition: "the disc's region" + - key: + - "ss.bios_jp" + - "ss.bios_na_eu" + source_ref: "src/ss/ss.cpp:2441-2442, 1430-1439" + condition: "not ST-V, by SMPC area" + - key: + - "ss.bios_stv_eu" + - "ss.bios_stv_jp" + - "ss.bios_stv_na" + source_ref: "src/ss/ss.cpp:2444-2446, 1420-1439" + condition: "ST-V cart, by area" + - key: + - "ss.cart.kof95_path" + - "ss.cart.ultraman_path" + source_ref: "src/ss/ss.cpp:2477-2478, 1811-1840, 1320-1350, 1389-1396" + condition: "cart type KOF95 or ULTRAMAN" + notes: | Multi-system emulator by the Mednafen Team, the upstream of the beetle_* and mednafen_* libretro cores, which are forks of individual modules. A module is diff --git a/emulators/melonds.yml b/emulators/melonds.yml index 432e036a9..02e8ac401 100644 --- a/emulators/melonds.yml +++ b/emulators/melonds.yml @@ -28,6 +28,58 @@ data_root: source_ref: "upstream src/frontend/qt_sdl/main.cpp:182-184, 196-200, 206-211" note: "portable beside the executable when that folder exists" +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261101" + path: "projects/ROCKNIX/packages/emulators/standalone/melonds-sa/package.mk" + variable: PKG_VERSION + value: "906e9ebb27da8c6a715cd7abab4abfe8a8d29427" +config_reads: + - file: "melonDS.ini" + platforms: [rocknix] + source_ref: "upstream src/frontend/qt_sdl/main.cpp:366, upstream src/frontend/qt_sdl/Config.cpp:682-774, 777-783, 785-795, upstream src/frontend/qt_sdl/Platform.cpp:144-166, ROCKNIX projects/ROCKNIX/packages/emulators/standalone/melonds-sa/patches/001-default-config.patch:8-13, ROCKNIX projects/ROCKNIX/packages/emulators/standalone/melonds-sa/patches/003-hotkeys.patch:41-76" + keys: + - key: "BIOS9Path" + source_ref: "upstream src/frontend/qt_sdl/Config.cpp:238, 240, upstream src/frontend/qt_sdl/EmuInstance.cpp:855-876, 1261-1263" + condition: "ExternalBIOSEnable=1" + - key: "BIOS7Path" + source_ref: "upstream src/frontend/qt_sdl/Config.cpp:238, 241, upstream src/frontend/qt_sdl/EmuInstance.cpp:878-898, 1265-1267" + condition: "ExternalBIOSEnable=1" + - key: "FirmwarePath" + source_ref: "upstream src/frontend/qt_sdl/Config.cpp:238, 242, upstream src/frontend/qt_sdl/EmuInstance.cpp:992-1016, 1269" + condition: "ExternalBIOSEnable=1 and ConsoleType=0" + - key: "DSiNANDPath" + source_ref: "upstream src/frontend/qt_sdl/Config.cpp:227, 249, upstream src/frontend/qt_sdl/EmuInstance.cpp:1039-1043, 1227, 1312-1322" + condition: "ConsoleType=1" + - file: "melonDS.toml" + platforms: [emudeck, retrobat] + revision: "b86390e4428bf38ce4c1ce0e9ca446d6d25955e8" + revision_from: "melonDS 1.1: the Flathub manifest net.kuribo64.melonDS at e5ce4504 builds it, and RetroBat's melonDS.exe names it" + source_ref: "upstream src/frontend/qt_sdl/main.cpp:196-213, 328, upstream src/frontend/qt_sdl/Config.cpp:42, 785-799" + keys: + - key: "[DS] BIOS9Path" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:868-889, 1277" + condition: "[Emu] ExternalBIOSEnable = true" + - key: "[DS] BIOS7Path" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:891-911" + condition: "[Emu] ExternalBIOSEnable = true" + - key: "[DS] FirmwarePath" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:1013-1031, 1285" + condition: "[Emu] ExternalBIOSEnable = true and ConsoleType = 0" + - key: "[DSi] BIOS9Path" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:913-938, 1328-1336" + condition: "[Emu] ConsoleType = 1" + - key: "[DSi] BIOS7Path" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:940-965, 1328-1332" + condition: "[Emu] ConsoleType = 1" + - key: "[DSi] FirmwarePath" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:1013-1029, 1285" + condition: "[Emu] ConsoleType = 1" + - key: "[DSi] NANDPath" + source_ref: "upstream src/frontend/qt_sdl/EmuInstance.cpp:1064-1068, 1338-1340" + condition: "[Emu] ConsoleType = 1" + notes: | FreeBIOS fallback for DS mode (NTR), while DSi mode requires all native files. Libretro fork based on melonDS ~0.9.x era, validates size only. diff --git a/emulators/nanoboyadvance.yml b/emulators/nanoboyadvance.yml index 0e113e0f4..1141ff63e 100644 --- a/emulators/nanoboyadvance.yml +++ b/emulators/nanoboyadvance.yml @@ -24,6 +24,14 @@ cores: systems: - nintendo-gba +config_reads: + - file: "/storage/.config/nanoboyadvance/config.toml" + platforms: [rocknix] + source_ref: "src/platform/sdl/main.cpp:293, src/platform/core/src/config.cpp:16-31" + keys: + - key: "[general] bios_path" + source_ref: "src/platform/core/src/config.cpp:36, src/platform/sdl/main.cpp:177-191, src/platform/core/src/loader/bios.cpp:19-46" + notes: | Game Boy Advance emulator. ROCKNIX builds the SDL2 frontend only (PLATFORM_SDL2=ON, PLATFORM_QT=OFF). diff --git a/emulators/xemu.yml b/emulators/xemu.yml index 573fd6be8..b18aa80a1 100644 --- a/emulators/xemu.yml +++ b/emulators/xemu.yml @@ -45,6 +45,37 @@ data_root: source_ref: "ui/xemu-settings.cc:51-63, 73-92" note: "xemu.toml beside the executable makes its folder the root" +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261101" + path: "projects/ROCKNIX/packages/emulators/standalone/xemu-sa/package.mk" + variable: PKG_VERSION + value: "cbffb57d084c70351a596e99e32a8b73e0c5eabf" +config_reads: + - file: "xemu.toml" + platforms: [rocknix] + source_ref: "ui/xemu.c:1314-1325, ui/xemu-settings.cc:65-71, 94-104, 158-207" + keys: + - key: "[sys.files] bootrom_path" + source_ref: "system/vl.c:2983-3016" + - key: "[sys.files] flashrom_path" + source_ref: "system/vl.c:3041-3054" + condition: "[general] show_welcome false" + - key: "[sys.files] eeprom_path" + source_ref: "system/vl.c:2909-2949, 3030-3039" + - key: "[sys.files] hdd_path" + source_ref: "system/vl.c:3060-3074" + - file: "xemu.toml" + platforms: [retrobat] + revision: "fc24584ce88f0915ad7f04775bb7712c2e3f49ee" + revision_from: "xemu.exe 0.8.136 in RetroBat's stable xemu.7z, 2026-11-10" + source_ref: "ui/xemu.c:1325, ui/xemu-settings.cc:51-63, 73-92, 94-104, 158-207" + keys: + - key: "[sys.files] flashrom_path" + source_ref: "system/vl.c:3041-3054" + condition: "[general] show_welcome false" + notes: | Full-system Xbox emulator built on QEMU. The libretro core is built from libretro's xemu-libretro tree on its libretro branch, which merges diff --git a/emulators/xroar.yml b/emulators/xroar.yml index 3aa57fb79..ccf2e4f28 100644 --- a/emulators/xroar.yml +++ b/emulators/xroar.yml @@ -29,6 +29,57 @@ standalone_pools: rompath: "" "-rompath": "" +config_reads: + - file: "xroar.conf" + source_ref: "src/xroar.c:882-909, 975-993" + keys: + - key: "rompath" + source_ref: "src/xroar.c:3266, src/romlist.c:125-140" + - key: + - "romlist dragon" + - "romlist d64_1" + - "romlist d64_2" + - "romlist d32" + - "romlist d200e_1" + - "romlist d200e_2" + - "romlist dragon64" + - "romlist dragon64_alt" + - "romlist dragon32" + - "romlist dragon200e" + - "romlist dragon200e_alt" + - "romlist dragon200e_charset" + - "romlist dragonpro_boot" + - "romlist dragonpro_basic" + - "romlist coco" + - "romlist coco_ext" + - "romlist deluxecoco" + - "romlist deluxecoco1" + - "romlist deluxecoco2" + - "romlist deluxecoco3" + - "romlist mx1600" + - "romlist mx1600ext" + - "romlist coco3" + - "romlist coco3p" + - "romlist glenside_ide" + - "romlist mc10" + - "romlist alice" + - "romlist dragondos" + - "romlist dosplus" + - "romlist superdos" + - "romlist cumana" + - "romlist dragondos_compat" + - "romlist rsdos" + - "romlist delta" + - "romlist rsdos_becker" + - "romlist mcx128" + - "romlist mcx128_azerty" + source_ref: "src/xroar.c:646-731, 3267, src/romlist.c:125-140" + - file: "command line" + source_ref: "src/xroar.c:1007" + keys: + - key: "-rompath" + source_ref: "src/xroar.c:3266, src/romlist.c:125-140" + notes: | XRoar by Ciaran Anscomb emulates Dragon 32/64, Tandy CoCo 1/2/3, MC-10, Alice, and clones (MX-1600, CP-400, Dragon 200-E, Deluxe CoCo). diff --git a/platforms/amberelec.yml b/platforms/amberelec.yml index a4085d839..dbbe87c88 100644 --- a/platforms/amberelec.yml +++ b/platforms/amberelec.yml @@ -3180,6 +3180,11 @@ launchers: search_dir: ~/roms/bios passes_to: retroarch.cfg system_directory fails_when_missing: false + installs: + path: /storage/.config/retroarch/retroarch.cfg + source_ref: "packages/sysutils/systemd/scripts/userconfig-setup:20" + kept_if_present: true + overrides: [] source_ref: "packages/games/tools/retroarch/sources/retroarch.cfg:611" - emulator: retrorun build: libretro @@ -3198,25 +3203,6 @@ launchers: 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 @@ -3291,7 +3277,13 @@ launchers: 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 ]" + /* | egrep 'AGA|CD32' | wc -l` -eq 1 ], content not booted with --autoload, a\ + \ WHDLoad .lha or .zip (amiberry.sh:83, 122)" + installs: + path: /tmp/emulation/amiberry/.amiberry_conf.uae + source_ref: "packages/games/emulators/amiberry/scripts/amiberry.sh:59" + kept_if_present: false + overrides: [] 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 @@ -3305,7 +3297,13 @@ launchers: 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 ])" + $AMIBERRY_TMP_DIR\"/* | egrep 'AGA|CD32' | wc -l` -eq 1 ]), content not booted\ + \ with --autoload, a WHDLoad .lha or .zip (amiberry.sh:83, 122)" + installs: + path: /tmp/emulation/amiberry/.amiberry_conf.uae + source_ref: "packages/games/emulators/amiberry/scripts/amiberry.sh:62" + kept_if_present: false + overrides: [] 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 @@ -3317,6 +3315,11 @@ launchers: search_dir: /storage/roms/bios passes_to: "Atari-ST-default.cfg [ROM] szTosImageFileName" fails_when_missing: false + installs: + path: /tmp/hatari/.hatari-temp.cfg + source_ref: "packages/games/emulators/hatarisa/scripts/hatari.sh:36" + kept_if_present: false + overrides: [] 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 diff --git a/platforms/emudeck.yml b/platforms/emudeck.yml index 5ef9d2ebd..6513cf18f 100644 --- a/platforms/emudeck.yml +++ b/platforms/emudeck.yml @@ -937,6 +937,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml ColecoVision/Firmware/BIOS.World fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:270, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -948,6 +954,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml MSX/Firmware/BIOS.Japan fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:278, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -959,6 +971,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml MSX2/Firmware/BIOS.Japan fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:283, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -971,6 +989,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml PCEngineCD/Firmware/BIOS.US, PCEngineCD/Firmware/BIOS.Japan fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:291-292, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -982,6 +1006,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml SuperGrafxCD/Firmware/BIOS.Japan fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:300, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -993,6 +1023,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml FamicomDiskSystem/Firmware/BIOS.Japan fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:308, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1007,6 +1043,12 @@ launchers: passes_to: settings.bml Nintendo64DD/Firmware/BIOS.Japan, Nintendo64DD/Firmware/BIOS.US, Nintendo64DD/Firmware/BIOS.DEV fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:319-321, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1018,6 +1060,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml GameBoyAdvance/Firmware/BIOS.World fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:332, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1032,6 +1080,12 @@ launchers: passes_to: settings.bml MasterSystem/Firmware/BIOS.US, MasterSystem/Firmware/BIOS.Japan, MasterSystem/Firmware/BIOS.Europe fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:340-342, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1043,6 +1097,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml GameGear/Firmware/BIOS.World fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:347, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1056,6 +1116,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml MegaCD/Firmware/BIOS.US, MegaCD/Firmware/BIOS.Japan, MegaCD/Firmware/BIOS.Europe fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:358-360, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1067,6 +1133,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml NeoGeoAES/Firmware/BIOS.World fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:368, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1078,6 +1150,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml NeoGeoPocket/Firmware/BIOS.World fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:378, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1089,6 +1167,12 @@ launchers: search_dir: "${biosPath}" passes_to: settings.bml NeoGeoPocketColor/Firmware/BIOS.World fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:383, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: ares @@ -1103,6 +1187,12 @@ launchers: passes_to: settings.bml PlayStation/Firmware/BIOS.US, PlayStation/Firmware/BIOS.Japan, PlayStation/Firmware/BIOS.Europe fails_when_missing: false + installs: + path: "${HOME}/.var/app/dev.ares.ares/data/ares/settings.bml" + source_ref: "functions/EmuScripts/emuDeckares.sh:26, 42, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "configs/dev.ares.ares/data/ares/settings.bml:388-390, functions/EmuScripts/emuDeckares.sh:6,\ \ 63-64" - emulator: melonds @@ -1121,6 +1211,12 @@ launchers: DSiFirmwarePath, DSiNANDPath fails_when_missing: false depends_on_option: ExternalBIOSEnable = 0 in the shipped melonDS.ini + installs: + path: "${HOME}/.var/app/net.kuribo64.melonDS/config/melonDS/melonDS.ini" + source_ref: "functions/EmuScripts/emuDeckMelonDS.sh:29, 48, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] source_ref: "functions/EmuScripts/emuDeckMelonDS.sh:7, 62-68, 71-77, configs/net.kuribo64.melonDS/config/melonDS/melonDS.ini:77" - emulator: melonds build: standalone @@ -1137,8 +1233,15 @@ launchers: passes_to: "melonDS.toml [DS] FirmwarePath, BIOS7Path, BIOS9Path [DSi] FirmwarePath,\ \ NANDPath, BIOS7Path, BIOS9Path" fails_when_missing: false - depends_on_option: ExternalBIOSEnable = false in the shipped melonDS.toml - source_ref: "configs/net.kuribo64.melonDS/config/melonDS/melonDS.toml:176, 181-183,\ + depends_on_option: "[DS] keys: ExternalBIOSEnable = true, false in the shipped melonDS.toml;\ + \ [DSi] keys: ConsoleType = 1, 0 in the shipped melonDS.toml" + installs: + path: "${HOME}/.var/app/net.kuribo64.melonDS/config/melonDS/melonDS.toml" + source_ref: "functions/EmuScripts/emuDeckMelonDS.sh:29, 48, functions/configEmuFP.sh:11,\ + \ 15" + kept_if_present: true + overrides: [] + source_ref: "configs/net.kuribo64.melonDS/config/melonDS/melonDS.toml:176-178, 181-183,\ \ 186-189, functions/EmuScripts/emuDeckMelonDS.sh:8, 32" - emulator: mupen64plus_next build: standalone diff --git a/platforms/emuelec.yml b/platforms/emuelec.yml index 1c0622059..3c181e6a4 100644 --- a/platforms/emuelec.yml +++ b/platforms/emuelec.yml @@ -4241,96 +4241,6 @@ launchers: fails_when_missing: false depends_on_option: /storage/roms/bios/mame/ini/mame.ini absent source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame - passes_to: mame.ini homepath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:10, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/bios - passes_to: mame.ini rompath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:11, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/hash - passes_to: mame.ini hashpath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:12, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/samples - passes_to: mame.ini samplepath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:13, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/artwork - passes_to: mame.ini artpath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:14, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/ini - passes_to: mame.ini inipath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:16, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/cheat - passes_to: mame.ini cheatpath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:18, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/crosshair - passes_to: mame.ini crosshairpath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:19, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" -- emulator: mame - build: libretro - kind: passes_path - names: [] - search_dir: /storage/roms/bios/mame/plugins - passes_to: mame.ini pluginspath - fails_when_missing: false - depends_on_option: mame_read_config = enabled - source_ref: "packages/sx05re/libretro/mame/config/ini/mame.ini:20, packages/sx05re/libretro/mame/config/MAME/MAME.opt:19,\ - \ packages/sx05re/emuelec/bin/emuelecRunEmu.sh:357-358, packages/sx05re/libretro/mame/scripts/mame.sh:21-23" - emulator: pico8 build: standalone kind: moves @@ -4384,6 +4294,11 @@ launchers: passes_to: "settings.ini [BIOS] SearchDirectory (-settings), [BIOS] PathNTSCU, PathNTSCJ,\ \ PathPAL unset" fails_when_missing: false + installs: + path: /emuelec/configs/duckstation/settings.ini + source_ref: "packages/sx05re/emulators/duckstation/scripts/duckstation.sh:15" + kept_if_present: true + overrides: [] source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:207-209, packages/sx05re/emulators/duckstation/config/settings.ini:6-9,\ \ packages/sx05re/emulators/duckstation/package.mk:56, packages/sx05re/emulators/duckstation/scripts/duckstation.sh:40" - emulator: yabasanshiroSA @@ -4439,6 +4354,11 @@ launchers: search_dir: /storage/roms/bios passes_to: "Atari-ST-default.cfg [ROM] szTosImageFileName (--configfile)" fails_when_missing: false + installs: + path: /tmp/hatari/.hatari-temp.cfg + source_ref: "packages/sx05re/emulators/hatarisa/scripts/hatari.start:36" + kept_if_present: false + overrides: [] source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:172-174, packages/sx05re/emulators/hatarisa/config/conf/Atari-ST-default.cfg:200,\ \ packages/sx05re/emulators/hatarisa/package.mk:39, packages/sx05re/emulators/hatarisa/scripts/hatari.start:36,\ \ 66, 73" @@ -4450,6 +4370,12 @@ launchers: passes_to: advmame.rc dir_artwork fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4463,6 +4389,12 @@ launchers: search_dir: /storage/roms/arcade/artwork passes_to: advmame.rc dir_artwork fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4475,6 +4407,12 @@ launchers: passes_to: advmame.rc dir_diff fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4488,6 +4426,12 @@ launchers: search_dir: /storage/roms/arcade/diff passes_to: advmame.rc dir_diff fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4500,6 +4444,12 @@ launchers: passes_to: advmame.rc dir_hi fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4513,6 +4463,12 @@ launchers: search_dir: /storage/roms/arcade/hi passes_to: advmame.rc dir_hi fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4525,6 +4481,12 @@ launchers: passes_to: advmame.rc dir_image fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4538,6 +4500,12 @@ launchers: search_dir: /storage/roms/arcade/image passes_to: advmame.rc dir_image fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4550,6 +4518,12 @@ launchers: passes_to: advmame.rc dir_inp fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4563,6 +4537,12 @@ launchers: search_dir: /storage/roms/arcade/inp passes_to: advmame.rc dir_inp fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4575,6 +4555,12 @@ launchers: passes_to: advmame.rc dir_memcard fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4588,6 +4574,12 @@ launchers: search_dir: /storage/roms/arcade/memcard passes_to: advmame.rc dir_memcard fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4600,6 +4592,12 @@ launchers: passes_to: advmame.rc dir_nvram fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4613,6 +4611,12 @@ launchers: search_dir: /storage/roms/arcade/nvram passes_to: advmame.rc dir_nvram fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4625,6 +4629,12 @@ launchers: passes_to: advmame.rc dir_rom fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4638,6 +4648,12 @@ launchers: search_dir: /storage/roms/arcade passes_to: advmame.rc dir_rom fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4649,6 +4665,12 @@ launchers: search_dir: /storage/roms/bios/advmame/samples passes_to: advmame.rc dir_sample fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:80" @@ -4660,6 +4682,12 @@ launchers: passes_to: advmame.rc dir_snap fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4673,6 +4701,12 @@ launchers: search_dir: /storage/roms/arcade/snap passes_to: advmame.rc dir_snap fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4685,6 +4719,12 @@ launchers: passes_to: advmame.rc dir_sta fails_when_missing: false depends_on_option: system != arcade + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4698,6 +4738,12 @@ launchers: search_dir: /storage/roms/arcade/nvram passes_to: advmame.rc dir_sta fails_when_missing: false + installs: + path: /emuelec/configs/advmame/advmame.rc + source_ref: "packages/sx05re/emulators/advancemame/bin/advmame.sh:17, 21" + kept_if_present: false + overrides: + - "packages/sx05re/emulators/advancemame/bin/advmame.sh:27" source_ref: "packages/sx05re/emuelec/bin/emuelecRunEmu.sh:213-215, packages/sx05re/emulators/advancemame/config/advmame.rc_oga:1-11,\ \ packages/sx05re/emulators/advancemame/config/advmame.rc_gf:1-11, packages/sx05re/emulators/advancemame/config/advmame.rc:4-14,\ \ packages/sx05re/emulators/advancemame/package.mk:45-49, packages/sx05re/emulators/advancemame/bin/advmame.sh:26-27,\ @@ -4720,7 +4766,8 @@ launchers: search_dir: /storage/.config/scummvm passes_to: /storage/roms/bios/scummvm.ini fails_when_missing: false - depends_on_option: scummvm.start add + depends_on_option: "scummvm.start add, an action no launch runs (emuelecRunEmu.sh\ + \ passes libretro, sa)" source_ref: "packages/sx05re/emulators/scummvmsa/bin/scummvm.start:9, 49-51, 54" - emulator: dolphin build: standalone diff --git a/platforms/knulli.yml b/platforms/knulli.yml index 2bbbad334..63aadbd72 100644 --- a/platforms/knulli.yml +++ b/platforms/knulli.yml @@ -5662,9 +5662,10 @@ launchers: 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']))" + choices_from: "package/emulationstation/knulli-es-system/es_features.yml:11037-11043" 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 + \ 375-379, 384-385" +- emulator: mame build: libretro kind: fixed_name names: @@ -5685,7 +5686,7 @@ launchers: \ 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 +- emulator: mame build: libretro kind: fixed_name names: @@ -5704,8 +5705,9 @@ launchers: \ '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']))" + choices_from: "package/emulationstation/knulli-es-system/es_features.yml:3727-3733" 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" + \ 123, 197, 265-268, 273-274" - emulator: dolphin build: standalone kind: fixed_name diff --git a/platforms/retrobat.yml b/platforms/retrobat.yml index 185be4204..290d649fa 100644 --- a/platforms/retrobat.yml +++ b/platforms/retrobat.yml @@ -4259,7 +4259,8 @@ launchers: passes_to: "settings.ini [BIOS] PathNTSCJ = PathNTSCU = PathPAL = " fails_when_missing: not checked by the launcher depends_on_option: duck_bios - source_ref: "emulatorLauncher/Generators/Duckstation.Generator.cs:197-202, .emulationstation/es_features.cfg:12489-12497" + choices_from: ".emulationstation/es_features.cfg:12489-12497" + source_ref: "emulatorLauncher/Generators/Duckstation.Generator.cs:197-202" - emulator: eka2l1 build: standalone kind: passes_path @@ -4599,7 +4600,8 @@ launchers: passes_to: "hatari.cfg [ROM] szTosImageFileName=/hatari/tos/" fails_when_missing: ApplicationException 'Missing TOS file in 'bios' folder.' depends_on_option: hatari_tos - source_ref: "emulatorLauncher/Generators/Hatari.Generator.cs:410-415, 454, .emulationstation/es_features.cfg:13768-13774" + choices_from: ".emulationstation/es_features.cfg:13768-13774" + source_ref: "emulatorLauncher/Generators/Hatari.Generator.cs:410-415, 454" - emulator: hatari build: standalone kind: fixed_name @@ -4639,7 +4641,8 @@ launchers: passes_to: hatari_tosimage = , if the file is there | default fails_when_missing: hatari_tosimage = default depends_on_option: hatari2014_tos - source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3409-3417, .emulationstation/es_features.cfg:7095-7102" + choices_from: ".emulationstation/es_features.cfg:7095-7102" + source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3409-3417" - emulator: hatarib build: libretro kind: fixed_name @@ -4667,8 +4670,8 @@ launchers: passes_to: hatarib_tos = hatarib/ fails_when_missing: hatarib_tos = , or when bios holds it depends_on_option: hatarib_tos - source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3434-3435, 3457-3474,\ - \ .emulationstation/es_features.cfg:7125-7134" + choices_from: ".emulationstation/es_features.cfg:7125-7134" + source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3434-3435, 3457-3474" - emulator: hatarib build: libretro kind: copies @@ -4885,7 +4888,8 @@ launchers: fails_when_missing: falls back to dsi_nand.bin depends_on_option: melonds_console == "dsi", melonds_nandregion set, rom extension not ".bin" - source_ref: "emulatorLauncher/Generators/MelonDS.Generator.cs:193-202, .emulationstation/es_features.cfg:16816-16823" + choices_from: ".emulationstation/es_features.cfg:16816-16823" + source_ref: "emulatorLauncher/Generators/MelonDS.Generator.cs:193-202" - emulator: mesen build: standalone kind: copies @@ -5081,7 +5085,8 @@ launchers: passes_to: "kronos.ini [1.0] General\\Bios = /, only if the file exists" fails_when_missing: General\Bios not written depends_on_option: kronos_force_bios - source_ref: "emulatorLauncher/Generators/Kronos.Generator.cs:162-170, .emulationstation/es_features.cfg:14519-14524" + choices_from: ".emulationstation/es_features.cfg:14519-14524" + source_ref: "emulatorLauncher/Generators/Kronos.Generator.cs:162-170" - emulator: ssf build: standalone kind: core_option @@ -5094,7 +5099,8 @@ launchers: passes_to: "SSF.ini [Peripheral] SaturnBIOS = /, only if the file exists" fails_when_missing: SaturnBIOS not written depends_on_option: use_external_bios - source_ref: "emulatorLauncher/Generators/SSF.Generator.cs:76-83, .emulationstation/es_features.cfg:20762-20767" + choices_from: ".emulationstation/es_features.cfg:20762-20767" + source_ref: "emulatorLauncher/Generators/SSF.Generator.cs:76-83" - emulator: yabasanshiro build: standalone kind: fixed_name @@ -5119,7 +5125,8 @@ launchers: \ exists" fails_when_missing: General\Bios not written depends_on_option: yabasanshiro_force_bios - source_ref: "emulatorLauncher/Generators/Yabasanshiro.Generator.cs:123-131, .emulationstation/es_features.cfg:22724-22729" + choices_from: ".emulationstation/es_features.cfg:22724-22729" + source_ref: "emulatorLauncher/Generators/Yabasanshiro.Generator.cs:123-131" - emulator: ymir build: standalone kind: fixed_name @@ -5144,7 +5151,8 @@ launchers: \ check" fails_when_missing: path written without an existence check depends_on_option: ymir_force_bios - source_ref: "emulatorLauncher/Generators/Ymir.Generator.cs:120-125, .emulationstation/es_features.cfg:22784-22788" + choices_from: ".emulationstation/es_features.cfg:22784-22788" + source_ref: "emulatorLauncher/Generators/Ymir.Generator.cs:120-125" - emulator: ymir build: standalone kind: passes_path @@ -5222,7 +5230,8 @@ launchers: fails_when_missing: ApplicationException 'BIOS file does not exist in ''.' depends_on_option: lrps2_bios - source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3695-3704, .emulationstation/es_features.cfg:5875-5881" + choices_from: ".emulationstation/es_features.cfg:5875-5881" + source_ref: "emulatorLauncher/Generators/LibRetro.CoreOptions.cs:3695-3704" - emulator: pcsx2 build: libretro kind: scans_dir @@ -5635,8 +5644,10 @@ launchers: search_dir: bios passes_to: "xemu.toml [sys.files] flashrom_path=/, default Complex_4627.bin" fails_when_missing: path written without an existence check - depends_on_option: xemu_flashrom - source_ref: "emulatorLauncher/Generators/Xemu.Generator.cs:286-289, .emulationstation/es_features.cfg:22081-22085" + depends_on_option: "xemu_flashrom, not (emulator == \"xemu-chihiro\" || system ==\ + \ \"chihiro\")" + choices_from: ".emulationstation/es_features.cfg:22081-22085" + source_ref: "emulatorLauncher/Generators/Xemu.Generator.cs:48, 284-289" - emulator: xemu build: standalone kind: names_searched @@ -5752,7 +5763,8 @@ launchers: - coco3h - coco3p - coco3ph - source_ref: "emulatorLauncher/Generators/Xroar.Generator.cs:76-80, .emulationstation/es_features.cfg:22650-22659" + choices_from: ".emulationstation/es_features.cfg:22650-22659" + source_ref: "emulatorLauncher/Generators/Xroar.Generator.cs:76-80" - emulator: xroar build: standalone kind: passes_path diff --git a/platforms/retrodeck.yml b/platforms/retrodeck.yml index 3592a2537..5df006cdd 100644 --- a/platforms/retrodeck.yml +++ b/platforms/retrodeck.yml @@ -16763,6 +16763,11 @@ launchers: search_dir: $roms_path/doom passes_to: "gzdoom.ini [IWADSearch.Directories] Path" fails_when_missing: false + installs: + path: $gzdoom_config + source_ref: "gzdoom/component_prepare.sh:23, 26-30" + kept_if_present: false + overrides: [] source_ref: "gzdoom/rd_assets/rd_config/gzdoom.ini:9, gzdoom/component_prepare.sh:23,\ \ 27-28" - emulator: gzdoom @@ -16772,6 +16777,11 @@ launchers: search_dir: $roms_path/doom passes_to: "gzdoom.ini [FileSearch.Directories] Path" fails_when_missing: false + installs: + path: $gzdoom_config + source_ref: "gzdoom/component_prepare.sh:23, 26-30" + kept_if_present: false + overrides: [] source_ref: "gzdoom/rd_assets/rd_config/gzdoom.ini:18, gzdoom/component_prepare.sh:23,\ \ 27-28" - emulator: gzdoom @@ -16781,6 +16791,11 @@ launchers: search_dir: $bios_path/gzdoom passes_to: "gzdoom.ini [FileSearch.Directories] Path" fails_when_missing: false + installs: + path: $gzdoom_config + source_ref: "gzdoom/component_prepare.sh:23, 26-30" + kept_if_present: false + overrides: [] source_ref: "gzdoom/rd_assets/rd_config/gzdoom.ini:19, gzdoom/component_prepare.sh:23,\ \ 28" - emulator: gzdoom @@ -16790,6 +16805,11 @@ launchers: search_dir: $roms_path/bios/gzdoom passes_to: "gzdoom.ini [SoundfontSearch.Directories] Path" fails_when_missing: false + installs: + path: $gzdoom_config + source_ref: "gzdoom/component_prepare.sh:23, 26-30" + kept_if_present: false + overrides: [] source_ref: "gzdoom/rd_assets/rd_config/gzdoom.ini:31, gzdoom/component_prepare.sh:23,\ \ 27-28" - emulator: mame @@ -17023,6 +17043,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: xroar.conf rompath fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:290, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17033,6 +17058,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:291, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17048,6 +17078,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist d64_1 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:292, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17062,6 +17097,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist d64_2 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:293, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17075,6 +17115,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist d32 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:294, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17087,6 +17132,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist d200e_1 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:295, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17099,6 +17149,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist d200e_2 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:296, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17115,6 +17170,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon64 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:297, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17129,6 +17189,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon64_alt fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:298, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17143,6 +17208,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon32 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:299, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17162,6 +17232,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon200e fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:300, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17179,6 +17254,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon200e_alt fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:301, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17190,6 +17270,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragon200e_charset fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:302, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17204,6 +17289,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragonpro_boot fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:303, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17222,6 +17312,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragonpro_basic fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:304, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17236,6 +17331,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist coco fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:305, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17249,89 +17349,12 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist coco_ext fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] 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 @@ -17342,6 +17365,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist deluxecoco fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:313, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17352,6 +17380,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist deluxecoco1 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:314, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17362,6 +17395,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist deluxecoco2 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:315, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17373,6 +17411,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist deluxecoco3 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:316, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17384,6 +17427,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist mx1600 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:317, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17394,6 +17442,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist mx1600ext fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:318, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17404,6 +17457,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist coco3 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:319, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17414,6 +17472,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist coco3p fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:320, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17425,6 +17488,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist glenside_ide fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:321, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17435,6 +17503,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist mc10 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:322, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17445,6 +17518,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist alice fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:323, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17460,6 +17538,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragondos fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:324, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17473,6 +17556,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dosplus fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:325, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17486,6 +17574,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist superdos fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:326, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17498,6 +17591,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist cumana fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:327, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17524,6 +17622,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist dragondos_compat fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:328, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17535,6 +17638,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist rsdos fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:329, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17550,6 +17658,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist delta fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:330, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17560,6 +17673,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist rsdos_becker fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:331, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17571,6 +17689,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist mcx128 fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:332, xroar/component_prepare.sh:13-15" - emulator: xroar build: standalone @@ -17582,6 +17705,11 @@ launchers: search_dir: "$roms_path/coco:$roms_path/dragon32:$roms_path/tanodragon:$bios_path" passes_to: romlist mcx128_azerty fails_when_missing: false + installs: + path: $xroar_config + source_ref: "xroar/component_prepare.sh:13-15" + kept_if_present: false + overrides: [] source_ref: "xroar/rd_assets/rd_config/xroar.conf:333, xroar/component_prepare.sh:13-15" - emulator: retroarch build: libretro @@ -17590,6 +17718,11 @@ launchers: - scph5500.bin passes_to: core option duckstation_BIOS.PathNTSCJ fails_when_missing: false + installs: + path: $retroarch_config_core_options + source_ref: "retroarch/component_prepare.sh:24" + kept_if_present: false + overrides: [] source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:165, retroarch/component_prepare.sh:24" - emulator: retroarch build: libretro @@ -17598,6 +17731,11 @@ launchers: - scph5501.bin passes_to: core option duckstation_BIOS.PathNTSCU fails_when_missing: false + installs: + path: $retroarch_config_core_options + source_ref: "retroarch/component_prepare.sh:24" + kept_if_present: false + overrides: [] source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:166, retroarch/component_prepare.sh:24" - emulator: retroarch build: libretro @@ -17606,6 +17744,11 @@ launchers: - scph5502.bin passes_to: core option duckstation_BIOS.PathPAL fails_when_missing: false + installs: + path: $retroarch_config_core_options + source_ref: "retroarch/component_prepare.sh:24" + kept_if_present: false + overrides: [] source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:167, retroarch/component_prepare.sh:24" - emulator: retroarch build: libretro @@ -17614,4 +17757,9 @@ launchers: - scph10000.bin passes_to: core option pcsx2_bios fails_when_missing: false + installs: + path: $retroarch_config_core_options + source_ref: "retroarch/component_prepare.sh:24" + kept_if_present: false + overrides: [] source_ref: "retroarch/rd_assets/rd_config/retroarch-core-options.cfg:515, retroarch/component_prepare.sh:24" diff --git a/platforms/rocknix.yml b/platforms/rocknix.yml index b01c2b45d..9f85b7126 100644 --- a/platforms/rocknix.yml +++ b/platforms/rocknix.yml @@ -570,6 +570,11 @@ launchers: passes_to: retroarch.cfg system_directory = "~/roms/bios", systemfiles_in_content_dir = "false" fails_when_missing: false + installs: + path: /storage/.config/retroarch/retroarch.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/libretro/retroarch/sources/AMD64/retroarch.cfg:3395-3396,\ \ projects/ROCKNIX/packages/emulators/libretro/retroarch/sources/H700/retroarch.cfg:711-712,\ \ projects/ROCKNIX/packages/emulators/libretro/retroarch/sources/RK3326/retroarch.cfg:716-717,\ @@ -594,7 +599,13 @@ launchers: search_dir: /storage/roms/bios passes_to: "AmigaA1200-default.uae kickstart_rom_file (amiberry -f)" fails_when_missing: false - depends_on_option: game name matches AGA|CD32 + depends_on_option: "game name matches AGA|CD32, content not booted with --autoload,\ + \ a WHDLoad .lha or .zip (start_amiberry.sh:90, 129)" + installs: + path: /tmp/emulation/amiberry/.amiberry_conf.uae + source_ref: "projects/ROCKNIX/packages/emulators/standalone/amiberry/scripts/start_amiberry.sh:64" + kept_if_present: false + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/amiberry/scripts/start_amiberry.sh:17,\ \ 62-69, projects/ROCKNIX/packages/emulators/standalone/amiberry/config/conf/AmigaA1200-default.uae:114" - emulator: amiberry @@ -606,7 +617,13 @@ launchers: search_dir: /storage/roms/bios passes_to: "AmigaA500-default.uae kickstart_rom_file (amiberry -f)" fails_when_missing: false - depends_on_option: game name does not match AGA|CD32 + depends_on_option: "game name does not match AGA|CD32, content not booted with --autoload,\ + \ a WHDLoad .lha or .zip (start_amiberry.sh:90, 129)" + installs: + path: /tmp/emulation/amiberry/.amiberry_conf.uae + source_ref: "projects/ROCKNIX/packages/emulators/standalone/amiberry/scripts/start_amiberry.sh:67" + kept_if_present: false + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/amiberry/scripts/start_amiberry.sh:17,\ \ 62-69, projects/ROCKNIX/packages/emulators/standalone/amiberry/config/conf/AmigaA500-default.uae:114" - emulator: amiberry @@ -645,6 +662,11 @@ launchers: search_dir: /storage/roms/bios passes_to: "Atari-ST-default.cfg [ROM] szTosImageFileName (--configfile)" fails_when_missing: false + installs: + path: /tmp/hatari/.hatari-temp.cfg + source_ref: "projects/ROCKNIX/packages/emulators/standalone/hatarisa/scripts/start_hatarisa.sh:35" + kept_if_present: false + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/hatarisa/config/conf/Atari-ST-default.cfg:200,\ \ projects/ROCKNIX/packages/emulators/standalone/hatarisa/scripts/start_hatarisa.sh:35,\ \ 65" @@ -652,13 +674,20 @@ launchers: build: standalone kind: fixed_name names: [] - passes_to: melonDS.ini BIOS9Path, BIOS7Path, FirmwarePath, DSiBIOS9Path, DSiBIOS7Path, - DSiFirmwarePath, DSiNANDPath left empty + passes_to: melonDS.ini BIOS9Path, BIOS7Path, FirmwarePath, DSiNANDPath left empty fails_when_missing: false depends_on_option: direct_boot = 0 sets ExternalBIOSEnable=1, otherwise 0 device: AMD64 - source_ref: "projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/AMD64/melonDS.ini:84-90,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/package.mk:60, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/scripts/start_melonds.sh:41,\ + installs: + path: /storage/.config/melonDS/melonDS.ini + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/scripts/start_melonds.sh:20,\ + \ 34" + kept_if_present: true + overrides: [] + source_ref: "projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/AMD64/melonDS.ini:84-86,\ + \ 90, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/package.mk:60,\ + \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/scripts/start_melonds.sh:41,\ \ 71-73" - emulator: melonds build: standalone @@ -667,24 +696,28 @@ launchers: - bios9.bin - bios7.bin - firmware.bin - - dsi_bios9.bin - - dsi_bios7.bin - - dsi_firmware.bin - dsi_nand.bin search_dir: /storage/roms/bios - passes_to: melonDS.ini BIOS9Path, BIOS7Path, FirmwarePath, DSiBIOS9Path, DSiBIOS7Path, - DSiFirmwarePath, DSiNANDPath + passes_to: melonDS.ini BIOS9Path, BIOS7Path, FirmwarePath, DSiNANDPath fails_when_missing: false depends_on_option: direct_boot = 0 sets ExternalBIOSEnable=1, otherwise 0 device: RK3399, RK3566, RK3576, RK3588, S922X, SDM845, SM4450, SM6115, SM8250, SM8550, SM8650, SM8750 - source_ref: "projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/InputPlumber/melonDS.ini:94-100,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/RK3399/melonDS.ini:93-99,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/RK3566/melonDS.ini:93-99,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/RK3588/melonDS.ini:93-99,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/S922X/melonDS.ini:95-101,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/SM4450/melonDS.ini:90-96,\ - \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/package.mk:60, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/scripts/start_melonds.sh:41,\ + installs: + path: /storage/.config/melonDS/melonDS.ini + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/scripts/start_melonds.sh:20,\ + \ 34" + kept_if_present: true + overrides: [] + source_ref: "projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/InputPlumber/melonDS.ini:94-96,\ + \ 100, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/RK3399/melonDS.ini:93-95,\ + \ 99, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/RK3566/melonDS.ini:93-95,\ + \ 99, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/RK3588/melonDS.ini:93-95,\ + \ 99, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/S922X/melonDS.ini:95-97,\ + \ 101, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/config/SM4450/melonDS.ini:90-92,\ + \ 96, projects/ROCKNIX/packages/emulators/standalone/melonds-sa/package.mk:60,\ + \ projects/ROCKNIX/packages/emulators/standalone/melonds-sa/scripts/start_melonds.sh:41,\ \ 71-73" - emulator: dsperate build: standalone @@ -701,6 +734,12 @@ launchers: passes_to: "dsperate.ini [paths] bios9, bios7, firmware, bios9i, bios7i, dsi_firmware,\ \ dsi_nand (--config)" fails_when_missing: false + installs: + path: /storage/.config/dsperate/dsperate.ini + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/dsperate-sa/scripts/start_dsperate.sh:19" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/dsperate-sa/config/H700/dsperate.ini:11-17,\ \ projects/ROCKNIX/packages/emulators/standalone/dsperate-sa/config/InputPlumber/dsperate.ini:11-17,\ \ projects/ROCKNIX/packages/emulators/standalone/dsperate-sa/config/RK3326/dsperate.ini:11-17,\ @@ -716,7 +755,13 @@ launchers: - gba_bios.bin search_dir: /storage/roms/bios passes_to: "config.toml [general] bios_path" - fails_when_missing: false + fails_when_missing: true + installs: + path: /storage/.config/nanoboyadvance/config.toml + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/nanoboyadvance-sa/scripts/start_nanoboyadvance.sh:12" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/nanoboyadvance-sa/config/config.toml:2,\ \ projects/ROCKNIX/packages/emulators/standalone/nanoboyadvance-sa/package.mk:30" - emulator: xemu @@ -731,6 +776,13 @@ launchers: passes_to: "xemu.toml [sys.files] bootrom_path, flashrom_path, eeprom_path, hdd_path\ \ (copied at each launch)" fails_when_missing: false + installs: + path: /storage/.config/xemu/xemu.toml + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/xemu-sa/scripts/start_xemu.sh:15,\ + \ 19" + kept_if_present: false + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/xemu-sa/config/AMD64/xemu.toml:28-31,\ \ projects/ROCKNIX/packages/emulators/standalone/xemu-sa/config/SM4450/xemu.toml:29-32,\ \ projects/ROCKNIX/packages/emulators/standalone/xemu-sa/config/SM8250/xemu.toml:29-32,\ @@ -747,6 +799,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg gba.bios fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 729, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -758,6 +816,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg md.cdbios fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 1050, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -769,6 +833,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg nes.ggrom fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 2292, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -781,6 +851,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg pce.cdbios, pce.gecdbios fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 3309, 3342, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -792,6 +868,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg pce_fast.cdbios fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 4128, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -803,6 +885,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg pcfx.bios fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 4611, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -816,6 +904,12 @@ launchers: search_dir: /storage/roms/bios/ passes_to: mednafen.cfg psx.bios_eu, psx.bios_jp, psx.bios_na fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 7366-7370, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: mednafen @@ -834,6 +928,12 @@ launchers: passes_to: mednafen.cfg ss.bios_jp, ss.bios_na_eu, ss.bios_stv_eu, ss.bios_stv_jp, ss.bios_stv_na, ss.cart.kof95_path, ss.cart.ultraman_path fails_when_missing: false + installs: + path: /storage/.config/mednafen/mednafen.cfg + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:21" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/mednafen/config/common/mednafen.template:570,\ \ 9818-9820, 9824-9828, 9834-9836, projects/ROCKNIX/packages/emulators/standalone/mednafen/scripts/start_mednafen.sh:19-21" - emulator: yabasanshiro @@ -861,6 +961,13 @@ launchers: passes_to: "settings.ini [BIOS] SearchDirectory = /storage/roms/bios, [BIOS] PathNTSCU,\ \ PathNTSCJ, PathPAL unset" fails_when_missing: false + installs: + path: /storage/.config/duckstation/settings.ini + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/duckstation-sa/scripts/start_duckstation.sh:19,\ + \ 22" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/duckstation-sa/config/AMD64/settings.ini:134,\ \ projects/ROCKNIX/packages/emulators/standalone/duckstation-sa/config/H700/settings.ini:24,\ \ projects/ROCKNIX/packages/emulators/standalone/duckstation-sa/config/InputPlumber/settings.ini:29-32,\ @@ -878,6 +985,13 @@ launchers: passes_to: "PCSX2.ini [Folders] Bios = /storage/roms/bios/armsx2, [Filenames] BIOS\ \ unset" fails_when_missing: false + installs: + path: /storage/.config/ARMSX2/inis/PCSX2.ini + source_ref: "projects/ROCKNIX/packages/sysutils/systemd/scripts/userconfig-setup:13,\ + \ projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/scripts/start_armsx2.sh:11,\ + \ 16" + kept_if_present: true + overrides: [] source_ref: "projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/config/S922X/ARMSX2/inis/PCSX2.ini:21,\ \ 475, projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/config/inputplumber/ARMSX2/inis/PCSX2.ini:21,\ \ 473, projects/ROCKNIX/packages/emulators/standalone/armsx2-sa/package.mk:117-118" diff --git a/schemas/emulator.schema.json b/schemas/emulator.schema.json index 7de174331..7bb563009 100644 --- a/schemas/emulator.schema.json +++ b/schemas/emulator.schema.json @@ -11,15 +11,16 @@ ], "allOf": [ { - "comment": "A build reference documents why a pin is held: it never stands without the pin_frozen that says so.", + "comment": "A build reference documents why a pin is held, or the build config_reads are read at: it never stands without the pin_frozen or the config_reads that say so.", "if": { "required": [ "pin_build" ] }, "then": { - "required": [ - "pin_frozen" + "anyOf": [ + {"required": ["pin_frozen"]}, + {"required": ["config_reads"]} ] } }, @@ -1078,14 +1079,36 @@ "standalone_directories": { "type": "array", "items": { - "type": "string" + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "additionalProperties": false, + "required": ["handle", "source_ref"], + "properties": { + "handle": {"type": "string", "minLength": 1}, + "source_ref": {"type": "string", "minLength": 1, "description": "Where the build reads the handle and resolves its files against it"} + } + } + ] }, "description": "What the standalone build resolves standalone_path against, as its code names it: a directory ($XDG_DATA_HOME/flycast), a setting (Dreamcast.BiosPath), an environment variable or the function returning the directory. A platform launcher record that links or passes a folder of its BIOS tree under one of these puts the standalone files in that folder." }, "standalone_pools": { "type": "object", "additionalProperties": { - "type": "string" + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "additionalProperties": false, + "required": ["folder", "source_ref"], + "properties": { + "folder": {"type": "string"}, + "source_ref": {"type": "string", "minLength": 1, "description": "Where the build reads the setting as a search list"} + } + } + ] }, "description": "Settings the standalone build reads as a list of directories it searches one after the other, each mapped to the subfolder of the layout (relative to standalone_directories, empty for the whole of it) its directories stand for: openMSX's filepool entries are searched like share/systemroms, XRoar's rompath entries hold the ROMs themselves. A file in any entry is read, so several platform records naming entries give the first one a pack can fill." }, @@ -1101,7 +1124,7 @@ "pin_build": { "type": "array", "minItems": 1, - "description": "The build file each platform that ships a pin_frozen build reads its revision from, so check_freshness can read it again where the platform's YAML now stands and say when the platform builds another revision.", + "description": "The build file each platform that ships a pin_frozen build, or the build a config_reads entry is read at, reads its revision from, so check_freshness can read it again where the platform's YAML now stands and say when the platform builds another revision.", "items": { "type": "object", "additionalProperties": false, @@ -1135,6 +1158,37 @@ } } }, + "config_reads": { + "type": "array", + "description": "The configs the build loads and the keys it opens a file from, as a platform launcher record citing a config needs them: link (c) is where the file is found and loaded, link (d) where each key is read and its value opened. An entry naming platforms is read at their pin_build revision, one naming none at source_commit.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["file", "source_ref", "keys"], + "dependentRequired": {"revision": ["platforms", "revision_from"]}, + "properties": { + "file": {"type": "string", "minLength": 1, "description": "The config as the code finds it: a name, a glob for any file a flag passes (*.uae), a path, the flag itself, \"command line\" or \"core options\""}, + "platforms": {"type": "array", "items": {"type": "string"}, "minItems": 1, "uniqueItems": true, "description": "Registry slugs of the platforms shipping the build these refs are read at, each with a pin_build entry unless revision names the build"}, + "revision": {"type": "string", "pattern": "^[0-9a-f]{40}$", "description": "The commit these refs are read at, for platforms installing a package no build file of theirs pins (RetroBat's stable archives, a Flathub app)"}, + "revision_from": {"type": "string", "minLength": 1, "description": "Where revision was read: the version string of the shipped binary, the Flathub manifest commit"}, + "source_ref": {"type": "string", "minLength": 1}, + "keys": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["key", "source_ref"], + "properties": { + "key": {"oneOf": [{"type": "string", "minLength": 1}, {"type": "array", "items": {"type": "string", "minLength": 1}, "minItems": 1}], "description": "Spelled as a launcher record's passes_to spells it: [Section] Key, or Key"}, + "source_ref": {"type": "string", "minLength": 1}, + "condition": {"type": "string", "description": "What else the read depends on"} + } + } + } + } + } + }, "pin_follows": { "type": "string", "description": "Why source_commit follows the branch tip although a single platform ships the profile: that platform builds the tip, cited by its build file. Without it or pin_frozen, a bump refuses a profile one platform ships." diff --git a/schemas/platform.schema.json b/schemas/platform.schema.json index c0a972192..1b3fb9479 100644 --- a/schemas/platform.schema.json +++ b/schemas/platform.schema.json @@ -341,6 +341,29 @@ "type": "string", "description": "Devices whose shipped config this record reads, comma-separated, set only where the configs of a package differ between devices" }, + "installs": { + "type": "object", + "additionalProperties": false, + "required": ["path", "source_ref"], + "properties": { + "path": {"type": "string", "minLength": 1, "description": "Where the config the record cites lands on the device, as the platform's code spells it"}, + "source_ref": {"type": "string", "minLength": 1, "description": "path:lines of the code that puts it there"} + }, + "description": "How the config file a record cites reaches the emulator: link (a) of the evidence a config record carries" + }, + "kept_if_present": { + "type": "boolean", + "description": "True when the install leaves a file already there in place (cp if absent, rsync --ignore-existing): the record holds on a fresh install" + }, + "overrides": { + "type": "array", + "items": {"type": "string"}, + "description": "path:lines of launch code that rewrites the record's keys or replaces the installed config, empty when the launch path writes neither: link (b)" + }, + "choices_from": { + "type": "string", + "description": "path:lines of the feature or option file (es_features, a core .opt) listing the values the option the record writes takes. It is never the record's evidence: source_ref cites the code writing the choice" + }, "source_ref": { "type": "string", "description": "path:lines in the platform's repository at the pinned revision" diff --git a/scripts/common.py b/scripts/common.py index cb3e294d1..9ed339cb7 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3189,11 +3189,12 @@ def standalone_bindings_for( ``systems`` are those the entry is launched for (entry_launch_systems). """ - declared = profile.get("standalone_directories") or [] - pools = profile.get("standalone_pools") or {} + from launchers import directory_handles, pool_folders, standalone_bindings # noqa: PLC0415 + + declared = directory_handles(profile) + pools = pool_folders(profile) if not declared and not pools: return {} - from launchers import standalone_bindings # noqa: PLC0415 names = {emu_name, *(str(c) for c in profile.get("cores") or [])} layout = [ @@ -3298,9 +3299,9 @@ def platform_layouts( # The layout folders a search setting stands for (standalone_pools): # bound, the launcher passes the setting and the default is not read. - pool_folders = { - str(v).strip("/") for v in (profile.get("standalone_pools") or {}).values() - } - {""} + from launchers import pool_folders as _pool_folders # noqa: PLC0415 + + pool_folders = {v.strip("/") for v in _pool_folders(profile).values()} - {""} def passed_setting(entry: dict) -> bool: layout = str(standalone_layout_path(entry, profile) or "") diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index 2f993d8c8..580bacd39 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -56,9 +56,11 @@ from common import ( from hashing import parse_hash_list from launchers import ( LauncherLayer, + bound_launchers, catalogued_model_files, declared_destinations, held_record, + launcher_unproven, name_settings, set_by, setting_readers, @@ -1095,6 +1097,8 @@ def _settle_launcher_places( # The --json key of the launcher reads category, beside the emulator keys. LAUNCHER_READS_KEY = "_launcher_reads_not_held" +# The --json key of the launcher records a config file does not prove. +LAUNCHER_UNPROVEN_KEY = "_launcher_unproven" # A package record names no file: it records what a package builds. _NAMELESS_KINDS = frozenset({"package"}) @@ -1238,7 +1242,9 @@ def launcher_reads_not_held( rows: list[dict] = [] for platform, config in configs.items(): by_key: dict[str, dict] = {} - for record in config.get("launchers") or []: + # A record resting on a config file it cannot prove reads nothing + # yet: it is reported apart (launcher_unproven). + for record in bound_launchers(config, profiles): names = launcher_file_names(record) if not names: continue @@ -1331,6 +1337,25 @@ def launcher_reads_not_held( return rows +def print_launcher_unproven(rows: list[dict]) -> None: + """The launcher records resting on a config file the code does not prove. + + Neither a pack, a manifest nor a severity reads them: each is listed + with the links it lacks. + """ + if not rows: + return + platforms = sorted({r["platform"] for r in rows}) + noun = "platform" if len(platforms) == 1 else "platforms" + print(f"\nLauncher records unproven: {len(rows)} across {len(platforms)} {noun}") + for platform in platforms: + print(f"\n{platform}") + for row in (r for r in rows if r["platform"] == platform): + print(f" [{row['index']}] {row['emulator']} {row['kind']}: {row['passes_to']}") + for link in row["missing"]: + print(f" missing {link}") + + def print_launcher_reads(rows: list[dict]) -> None: """The launcher reads gap category, after the profile gaps. @@ -1649,8 +1674,25 @@ def main() -> None: db, all_profiles, registry, emulator or "", ) + unproven = [ + row for row in launcher_unproven( + { + name: load_platform_config(name, args.platforms_dir) + for name in (selected or list_registered_platforms(args.platforms_dir)) + }, + all_profiles, + ) + if not emulator or any( + emulator in LauncherLayer._profiles_named(part, all_profiles, set(all_profiles)) + for part in row["emulator"].split(", ") + ) + ] + if args.json: - print(json.dumps({**report, LAUNCHER_READS_KEY: launcher_reads}, indent=2)) + print(json.dumps( + {**report, LAUNCHER_READS_KEY: launcher_reads, LAUNCHER_UNPROVEN_KEY: unproven}, + indent=2, + )) else: if emulator: scope = emulator @@ -1660,6 +1702,7 @@ def main() -> None: scope = "all emulators" print_report(report, scope, show_clean=bool(args.emulator)) print_launcher_reads(launcher_reads) + print_launcher_unproven(unproven) if __name__ == "__main__": diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 660c58ac5..e1fb0141f 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -3647,13 +3647,15 @@ def _served_line(f: dict, declared_sha1: str = "") -> str: def _launcher_refs(config: dict) -> dict[str, list[str]]: """Entry name, casefolded -> each launcher record that opens it, rendered.""" from launchers import _names as launcher_names + from launchers import bound_launchers from sitesources import launcher_ref_markdown, list_source # Every scraper reads its launchers in the repository and at the revision - # of the list it scrapes, so `source` pins each source_ref. + # of the list it scrapes, so `source` pins each source_ref. A record a + # config file does not prove opens nothing: it is listed apart. source = list_source(str(config.get("source") or "")) found: dict[str, list[str]] = {} - for record in config.get("launchers") or []: + for record in bound_launchers(config): if not isinstance(record, dict) or not record.get("source_ref"): continue text = (f"{_escape_tags(str(record.get('emulator', '')))} at " @@ -3665,6 +3667,31 @@ def _launcher_refs(config: dict) -> dict[str, list[str]]: return found +UNPROVEN_TITLE = "Launcher records a config does not prove" + + +def _launcher_unproven_section(config: dict) -> list[str]: + """The platform's launcher records resting on a config the code does not prove. + + A config file is proof only when the code installing it and the code + reading it are shown: these records place no file. + """ + from launchers import launcher_unproven + from sitesources import launcher_ref_markdown, list_source + + rows = launcher_unproven({str(config.get("_key") or ""): config}) + if not rows: + return [] + source = list_source(str(config.get("source") or "")) + body = [ + f"- {_escape_tags(row['emulator'])}, `{_escape_tags(row['passes_to'] or row['kind'])}` " + f"at {launcher_ref_markdown(source, row['source_ref'])}: " + f"{_escape_tags('; '.join(row['missing']))}" + for row in rows + ] + return ["", *markup.details("note", f"{UNPROVEN_TITLE} ({len(rows)})", body)] + + # The hash a row shows first, when the list does not say which it checks. _ROW_HASHES = ("sha1", "md5", "sha256", "crc32") # An untested entry whose reason is a hash the bytes do not match. @@ -4799,6 +4826,7 @@ def generate_platform_page( _render_platform_systems(by_system, config_files, emulator_files, (name, display), moved, config) ) + lines.extend(_launcher_unproven_section(config)) lines.extend(["", _date_line()]) return "\n".join(lines) + "\n" diff --git a/scripts/launchers.py b/scripts/launchers.py index 2884035b9..c790af076 100644 --- a/scripts/launchers.py +++ b/scripts/launchers.py @@ -254,11 +254,318 @@ def _names(record: dict) -> list[str]: return names -def launcher_places(config: dict) -> list[LauncherPlace]: +# How a record's passes_to names what carries the file when it names no +# config file: an option on the command line, or a libretro core option. +COMMAND_LINE = "command line" +CORE_OPTIONS = "core options" +_TAIL_WORDS = re.compile(r"\s+(?:unset|left empty)$") +_KEY_TOKEN = re.compile(r"^-{0,2}[\w.$()/\\]+$") + + +def config_target(record: dict) -> tuple[str, list[str]]: + """The config a record writes its file to, and the keys it names there. + + passes_to spells them as describe_keys does: "settings.ini [BIOS] + SearchDirectory, [BIOS] PathNTSCU, PathNTSCJ unset", "command line + -hard", "core option pcsx2_bios", "hatarib_tos = hatarib/". A + part that reads as prose ("only if the file exists") names no key. When + passes_to names no file ("romlist coco"), the file is the one the record + installs and the whole of passes_to is the key. + """ + # A parenthesis says how the file is passed, unless a config variable + # opens it: $(FILE_PATH) is amiberry's. + text = re.sub(r"(?" sets every key before the value. + names = [p.strip() for p in re.split(r"\s*=\s*", chunk)] + if len(names) > 1: + names = names[:-1] + for name in names: + if not _KEY_TOKEN.match(name): + continue + key = f"{section} {name}" if section else name + if key not in keys: + keys.append(key) + return head, keys + + +def _read_keys(read: dict) -> dict[str, str]: + """Each key a config_reads entry says the build reads, to its source_ref.""" + found: dict[str, str] = {} + for entry in read.get("keys") or []: + spelled = entry.get("key") + for key in spelled if isinstance(spelled, list) else [spelled]: + if key: + found[str(key)] = str(entry.get("source_ref") or "") + return found + + +def _handle_refs(profile: dict) -> dict[str, str]: + """The handles a profile names with the code that reads them.""" + found: dict[str, str] = {} + for item in profile.get("standalone_directories") or []: + if isinstance(item, dict) and item.get("source_ref"): + handle = str(item.get("handle") or "").partition(" = ")[0] + found[handle] = str(item["source_ref"]) + for handle, value in (profile.get("standalone_pools") or {}).items(): + if isinstance(value, dict) and value.get("source_ref"): + found[str(handle)] = str(value["source_ref"]) + return found + + +def directory_handles(profile: dict) -> list[str]: + """standalone_directories as handle strings, a {handle, source_ref} item + by its handle.""" + return [ + str(item.get("handle") or "") if isinstance(item, dict) else str(item) + for item in profile.get("standalone_directories") or [] + ] + + +def pool_folders(profile: dict) -> dict[str, str]: + """standalone_pools as handle to subfolder, a {folder, source_ref} value + by its folder.""" + return { + str(handle): str(value.get("folder") or "") if isinstance(value, dict) else str(value) + for handle, value in (profile.get("standalone_pools") or {}).items() + } + + +def _reads_apply(read: dict, profile: dict, platform: str) -> bool: + """Whether a config_reads entry describes the build ``platform`` ships. + + An entry naming platforms is read at their pin_build revision. One that + names none is read at the profile's pin, the build of every platform + with no pin_build of its own or one pinning the same commit. + """ + named = [str(p) for p in read.get("platforms") or []] + if named: + return platform in named + pin = str(profile.get("source_commit") or "") + builds = [b for b in profile.get("pin_build") or [] if b.get("platform") == platform] + return all(pin and str(b.get("value") or "").startswith(pin[:12]) for b in builds) + + +def _file_matches(read_file: str, file: str, record: dict) -> bool: + """Whether a config_reads file is the one a record writes. + + The entry names the file as the build finds it: a name, a glob ("*.uae", + any file the launcher passes), a path, or the flag the launcher passes + it with (amiberry's -f). + """ + from fnmatch import fnmatch # noqa: PLC0415 + + spelled = read_file.rsplit("/", 1)[-1].casefold() + if file and fnmatch(file.casefold(), spelled): + return True + installed = str((record.get("installs") or {}).get("path") or "").rsplit("/", 1)[-1] + if installed and fnmatch(installed.casefold(), spelled): + return True + return read_file.startswith("-") and read_file in str(record.get("passes_to") or "").split() + + +@dataclass(frozen=True) +class ConfigEvidence: + """Whether a launcher record that rests on a config holds, and why not. + + ``config`` holds the parts of its source_ref that cite a config, option + or feature file. A record citing none, and choosing among no listed + option values, rests on code alone and holds. + """ + + config: tuple[str, ...] + missing: tuple[str, ...] + readers: tuple[str, ...] = () + + @property + def proven(self) -> bool: + return not self.missing + + +def _named_profiles(record: dict, profiles: Mapping[str, dict], platform: str) -> list[str]: + """The profiles a record's emulator names, a platform's own build first.""" + found = [] + for part in (e.strip() for e in str(record.get("emulator", "")).split(",")): + named = [ + key for key, profile in profiles.items() + if profile.get("type") not in ("alias", "launcher") + and (key == part or part in {str(c) for c in profile.get("cores") or []}) + ] + own = [k for k in named if platform in (profiles[k].get("built_by") or [])] + found.extend(own or [k for k in named if not profiles[k].get("built_by")]) + return sorted(set(found)) + + +def config_evidence( + record: dict, profiles: Mapping[str, dict], platform: str = "" +) -> ConfigEvidence: + """What a record citing a config file proves, link by link. + + A config file is proof only when the code that installs it and the code + that reads it are shown: (a) the platform installs the file at a path + (``installs``), (b) its launch code was read for what replaces the file + or the key (``overrides``), (c) the emulator loads that file and (d) + reads each key and opens the value (``config_reads`` of its profile, at + the build the platform ships). A feature or option file only lists the + values a menu offers: it goes in ``choices_from``, beside the code that + writes the choice, and such a record needs (c) and (d) too. + """ + from scraper.shell_launchers import config_ref, is_feature_ref # noqa: PLC0415 + + cited = config_ref(str(record.get("source_ref") or "")) + choices = str(record.get("choices_from") or "") + if not cited and not choices: + return ConfigEvidence((), ()) + missing: list[str] = [] + features = [part for part in cited if is_feature_ref(part)] + if features: + missing.append("an option list cited as source, not as choices_from") + shipped = [part for part in cited if not is_feature_ref(part)] + if shipped: + installs = record.get("installs") or {} + if not (installs.get("path") and installs.get("source_ref")): + missing.append("installs: the code that puts the config in place") + if "overrides" not in record: + missing.append("overrides: the launch code read for what replaces it") + file, keys = config_target(record) + if not keys: + missing.append("a key the config hands the file to") + readers: list[str] = [] + uncovered = list(keys) + loaded = False + for key in _named_profiles(record, profiles, platform) or sorted(profiles): + profile = profiles[key] + handles = _handle_refs(profile) + for read in profile.get("config_reads") or []: + if not _reads_apply(read, profile, platform): + continue + if not _file_matches(str(read.get("file") or ""), file, record): + continue + loaded = True + known = _read_keys(read) + before = len(uncovered) + uncovered = [k for k in uncovered if not (known.get(k) or handles.get(k))] + if len(uncovered) < before and key not in readers: + readers.append(key) + if not loaded: + missing.append(f"config_reads: an emulator that loads {file or 'the config'}") + elif uncovered: + missing.append("config_reads: no read of " + ", ".join(uncovered)) + return ConfigEvidence(tuple(cited or [choices]), tuple(missing), tuple(readers)) + + +_DEFAULT_PROFILES: dict[str, dict] | None = None + + +def _profiles_or_repo(profiles: Mapping[str, dict] | None) -> Mapping[str, dict]: + """The profiles given, else this repository's, read once.""" + global _DEFAULT_PROFILES # noqa: PLW0603 + if profiles is not None: + return profiles + if _DEFAULT_PROFILES is None: + from pathlib import Path # noqa: PLC0415 + + from common import load_emulator_profiles # noqa: PLC0415 + + _DEFAULT_PROFILES = load_emulator_profiles( + str(Path(__file__).resolve().parent.parent / "emulators") + ) + return _DEFAULT_PROFILES + + +def _cites_config(record: dict) -> bool: + from scraper.shell_launchers import config_ref # noqa: PLC0415 + + return bool(record.get("choices_from")) or bool(config_ref(str(record.get("source_ref") or ""))) + + +def bound_launchers(config: dict, profiles: Mapping[str, dict] | None = None) -> list[dict]: + """The launcher records a platform's packs, manifests and reports consume. + + A record resting on a config file binds only when config_evidence holds + for it. The repository's profiles are read only when a record cites one. + """ + records = [r for r in config.get("launchers") or [] if isinstance(r, dict)] + if not any(_cites_config(r) for r in records): + return records + known = _profiles_or_repo(profiles) + platform = str(config.get("_key") or "") + return [r for r in records if config_evidence(r, known, platform).proven] + + +def launcher_unproven( + configs: Mapping[str, dict], profiles: Mapping[str, dict] | None = None +) -> list[dict]: + """The records resting on a config file whose evidence does not hold. + + One row per record, with the config parts it cites and each missing + link. Reported apart: no severity, count or exit code reads it. + """ + rows: list[dict] = [] + for platform, config in configs.items(): + records = [r for r in config.get("launchers") or [] if isinstance(r, dict)] + if not any(_cites_config(r) for r in records): + continue + known = _profiles_or_repo(profiles) + for index, record in enumerate(records): + evidence = config_evidence(record, known, str(config.get("_key") or platform)) + if evidence.proven: + continue + rows.append({ + "platform": platform, + "index": index, + "emulator": str(record.get("emulator", "")), + "kind": str(record.get("kind", "")), + "passes_to": str(record.get("passes_to") or ""), + "source_ref": str(record.get("source_ref", "")), + "config": list(evidence.config), + "missing": list(evidence.missing), + }) + return rows + + +def launcher_places( + config: dict, profiles: Mapping[str, dict] | None = None +) -> list[LauncherPlace]: """Every place a platform's launchers open a named file from.""" base = str(config.get("base_destination") or "bios") places: list[LauncherPlace] = [] - for record in config.get("launchers") or []: + for record in bound_launchers(config, profiles): kind = record.get("kind") if kind not in PLACE_KINDS: continue @@ -484,7 +791,9 @@ def _listed_directories(raw: str) -> list[str] | None: return None -def handed_directories(config: dict) -> list[HandedDirectory]: +def handed_directories( + config: dict, profiles: Mapping[str, dict] | None = None +) -> list[HandedDirectory]: """Every whole directory a platform links or passes to a standalone build. A record naming files (gpsp's gba_bios.bin) or one slot of a directory @@ -499,7 +808,7 @@ def handed_directories(config: dict) -> list[HandedDirectory]: natives = native_systems(config) declared = {d.lower() for d in declared_destinations(config)} found: list[HandedDirectory] = [] - for record in config.get("launchers") or []: + for record in bound_launchers(config, profiles): if record.get("build") != "standalone" or record.get("kind") not in HANDED_KINDS: continue if record.get("names") or record.get("slot"): @@ -988,7 +1297,7 @@ class LauncherLayer: db: dict, declared: Iterable[str], ) -> None: - self.places = launcher_places(config) + self.places = launcher_places(config, profiles) self.declared = set(declared) self.declared_folded = {d.lower() for d in self.declared} self.declared_names = {d.rsplit("/", 1)[-1] for d in self.declared} diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index b10c2531c..94c663b81 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -1047,6 +1047,11 @@ class ProfileReport: new_files_elided: dict[str, int] = field(default_factory=dict) new_hashes: list[dict] | None = None detection_skipped: str | None = None + # config_reads refs written for a build a platform ships, each read at + # that build (pin_build) rather than at source_commit: {platform, + # revision, ref, status, detail}. A status other than BUILD_OK is review + # work. + build_refs: list[dict] = field(default_factory=list) # Skips that say the profile cannot be checked as written: a repository # that no longer answers, no pin to read, a death the forge contradicts. @@ -1070,6 +1075,7 @@ class ProfileReport: + len(self.orphaned) + len(self.stale_prose or ()) + len(self.moved_repos) + + sum(1 for ref in self.build_refs if ref["status"] != "BUILD_OK") ) @@ -1603,6 +1609,81 @@ def moved_repositories( return moved +def shipped_build_holders(profile: dict) -> dict[int, dict]: + """The source_ref holders of config_reads entries naming platforms, by id. + + Such an entry describes the build those platforms ship, so its refs are + read at their pin_build revision, never at source_commit. + """ + found: dict[int, dict] = {} + for read in profile.get("config_reads") or []: + if not isinstance(read, dict) or not read.get("platforms"): + continue + found[id(read)] = read + for key in read.get("keys") or []: + if isinstance(key, dict): + found[id(key)] = read + return found + + +def build_revision(profile: dict, platform: str) -> str | None: + """The emulator revision a platform's pin_build entry names, None when + its value is no revision (a URL, a package name).""" + for build in profile.get("pin_build") or []: + if build.get("platform") != platform: + continue + value = str(build.get("value") or "").strip() + return value if value and re.fullmatch(r"[\w.+-]+", value) else None + return None + + +def _check_build_refs( + profile: dict, + deferred: list[tuple[Citation, dict]], + views: list[RepoView], + fetch: Callable[[upstream.Repo, str, str], list[str] | None], +) -> list[dict]: + """Each deferred config_reads ref read at the build of its platforms. + + The file must exist at that revision and every cited line lie inside + it. The entry declares no value to anchor on, so this is the check a + ref past the end of its file fails (BUILD_GONE), not a content match. + """ + results: list[dict] = [] + for citation, read in deferred: + for platform in read.get("platforms") or []: + revision = str(read.get("revision") or "") or build_revision(profile, str(platform)) + row = {"platform": str(platform), "revision": revision or "", + "ref": citation.ref, "field": citation.field} + if revision is None: + results.append({**row, "status": "BUILD_UNREAD", + "detail": "no pin_build revision for this platform"}) + continue + problems: list[str] = [] + for part in split_source_ref(citation.ref): + role, path = ref_role(part.path) + if " " in path: + # A project word in front (`ROCKNIX patches/x.patch:4`): + # a file of the platform's tree, read with the record. + continue + lines = next( + (found for view in views + if (not role or view.field == role) + and (found := fetch(view.repo, revision, path)) is not None), + None, + ) + if lines is None: + problems.append(f"{part.path} absent at {revision[:12]}") + elif (part.end or part.start or 0) > len(lines): + problems.append(f"{part.raw} past the end ({len(lines)} lines)") + results.append({ + **row, + "status": "BUILD_GONE" if problems else "BUILD_OK", + "detail": "; ".join(problems), + }) + return results + + def build_report( name: str, profile: dict, cache_dir: str, offline: bool = False, text: str | None = None, @@ -1621,7 +1702,13 @@ def build_report( ) modes = {mode for _, mode, _ in declared_repositories(profile) if mode} refs = [] + at_build = shipped_build_holders(profile) + deferred: list[tuple[Citation, dict]] = [] for citation in collect_citations(profile, text): + if citation.kind == "ref" and id(citation.holder) in at_build: + # Written for the build a platform ships: read at pin_build. + deferred.append((citation, at_build[id(citation.holder)])) + continue if citation.kind == "ref": entry = citation.entry display = str(entry.get("name") or "") or citation.field @@ -2204,6 +2291,8 @@ def build_report( ) return result + report.build_refs = _check_build_refs(profile, deferred, views, fetch_from) + staged = [] for entry_name, ref, tokens, hashes, ref_parts, citation in refs: flags = [self_checked(part, tokens) for part in ref_parts] @@ -2335,6 +2424,14 @@ def format_report(report: ProfileReport, changed_only: bool = False) -> str: lines.append(f" {len(report.entries)} refs: {summary}") for line in report.stale_prose or (): lines.append(f" prose under an older pin: {line}") + for ref in report.build_refs: + if changed_only and ref["status"] == "BUILD_OK": + continue + detail = f": {ref['detail']}" if ref["detail"] else "" + lines.append( + f" {ref['status']} at {ref['platform']} build " + f"{ref['revision'][:12] or '-'} {ref['ref']}{detail}" + ) lines.append(f" {report.needs_review()} to review") return "\n".join(lines) diff --git a/scripts/scraper/amberelec_launchers.py b/scripts/scraper/amberelec_launchers.py index 259d46cd4..f4f7819a0 100644 --- a/scripts/scraper/amberelec_launchers.py +++ b/scripts/scraper/amberelec_launchers.py @@ -7,7 +7,7 @@ runs the script its standalone_emulators table names (/usr/bin/.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. +tos.img. runemu.py is read with ast, the scripts with shell_launchers' readers. An extractor whose construct is gone raises instead of returning nothing. @@ -26,7 +26,9 @@ try: from .shell_launchers import ( LauncherPatternMissing, Script, + config_placement, describe_keys, + evidence_fields, settings, source_ref, ) @@ -35,7 +37,9 @@ except ImportError: from shell_launchers import ( # type: ignore[no-redef] LauncherPatternMissing, Script, + config_placement, describe_keys, + evidence_fields, settings, source_ref, ) @@ -45,8 +49,8 @@ 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" +# The first-boot copy of every package's /usr/config into /storage/.config. +USERCONFIG = "packages/sysutils/systemd/scripts/userconfig-setup" AMIBERRY_PACKAGE = "packages/games/emulators/amiberry/package.mk" AMIBERRY_CONFIGS = "packages/games/emulators/amiberry/config/conf" FREEJ2ME = ( @@ -103,16 +107,35 @@ class Runemu: return path, line +def _evidence( + read: Read, record: Launcher, target: str, keys: list[str], + start: Script | None = None, source_name: str = "", +) -> Launcher: + """A config record with the steps that put its file at ``target``: the + first-boot copy of /usr/config, then the start script's own copy.""" + setup = Script(USERCONFIG, read(USERCONFIG)) + placements = [config_placement(setup, target, required=False, sources=("/usr/config/",))] + if start is not None: + placements.append( + config_placement(start, target, keys, source_name=source_name, required=False) + ) + fields = evidence_fields(placements) + record.installs = fields["installs"] + record.kept_if_present = fields["kept_if_present"] + record.overrides = fields["overrides"] + return record + + def retroarch(read: Read) -> list[Launcher]: """system_directory of the retroarch.cfg the retroarch package installs.""" found = [s for s in settings(read(RETROARCH_CFG)) if s.key == "system_directory"] if len(found) != 1: raise LauncherPatternMissing(RETROARCH_CFG, "one system_directory") - return [Launcher( + return [_evidence(read, Launcher( emulator="retroarch", build="libretro", kind="passes_path", names=[], search_dir=found[0].value, passes_to="retroarch.cfg system_directory", fails_when_missing=False, source_ref=source_ref([(RETROARCH_CFG, [found[0].line])]), - )] + ), "/storage/.config/retroarch/retroarch.cfg", ["system_directory"])] def retrorun(read: Read) -> list[Launcher]: @@ -170,10 +193,12 @@ def hatari(read: Read, runemu: Runemu) -> list[Launcher]: 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}") + copied, copy = script.find( + rf"^\s*cp\s+\S*/{re.escape(name)}\s+\"?(\$\{{?\w+\}}?)", f"cp of {name}" + ) passed, _ = script.find(r"\shatari\s+--configfile\s", "hatari --configfile") files, lines = _bios_files(HATARI_CONFIG, read(HATARI_CONFIG)) - return [Launcher( + return [_evidence(read, Launcher( emulator="HATARISA", build="standalone", kind="fixed_name", names=[n for _k, n in files], case_insensitive=False, search_dir=BIOS, passes_to=describe_keys(name, tuple(k for k, _n in files)), @@ -181,24 +206,8 @@ def hatari(read: Read, runemu: Runemu) -> list[Launcher]: 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)]), - )] + ), script.expand(copy.group(1), copied, set()), + [f"[{s}] {k}" if s else k for (s, k), _n in files], script, name)] def yabasanshiro(read: Read, runemu: Runemu) -> list[Launcher]: @@ -279,6 +288,15 @@ def amiberry(read: Read, runemu: Runemu) -> list[Launcher]: installed = re.sub(r"^\$\{INSTALL\}", "", link.link) path, table_line = runemu.script("AMIBERRY") script = Script(path, read(path)) + # A WHDLoad archive is booted by --autoload, which builds its own setup + # over the config: the kickstart records hold for the other content. + autoload = [n for n, text in script.code() if re.search(r"amiberry .*--autoload", text)] + if not autoload: + raise script.missing("WHDLoad boot with --autoload") + content = ( + "content not booted with --autoload, a WHDLoad .lha or .zip " + f"({posixpath.basename(path)}:{', '.join(map(str, autoload))})" + ) launchers = [Launcher( emulator="AMIBERRY", build="standalone", kind="links", names=[], case_insensitive=False, search_dir=BIOS, @@ -314,19 +332,23 @@ def amiberry(read: Read, runemu: Runemu) -> list[Launcher]: else: test = _shell_test(script, condition_line) condition = test - launchers.append(Launcher( + target = re.search(r'"?(\$\{?\w+\}?)"?\s*$', line) + if target is None: + raise script.missing(f"destination of the copy at line {number}") + launchers.append(_evidence(read, Launcher( emulator="AMIBERRY", build="standalone", kind="fixed_name", slot=copied.group(1).removesuffix("-default.uae").removeprefix("Amiga"), names=[found[0].value.removeprefix("$(FILE_PATH)/")], case_insensitive=False, search_dir=BIOS, passes_to=f"{copied.group(1)} kickstart_rom_file, $(FILE_PATH) = {rom_path[0].value}", fails_when_missing=False, - depends_on_option=condition, + depends_on_option=f"{condition}, {content}", source_ref=source_ref([ (RUNEMU, [table_line]), (path, [condition_line, number]), (config, [rom_path[0].line, found[0].line]), ]), - )) + ), script.expand(target.group(1), number, set()), ["kickstart_rom_file"], + script, copied.group(1))) if len(launchers) == 1: raise script.missing("cp of a default .uae config") return launchers @@ -420,9 +442,13 @@ def easyrpg(read: Read) -> list[Launcher]: def extract_launchers(read: Read) -> list[dict[str, Any]]: """Every launcher record, read through `read(repo-relative path)`.""" runemu = Runemu(read) + # The atari800.cfg AmberELEC ships names the OS ROMs, but the core opens + # it only under its atari800_cfg option, off by default, and finds the + # ROMs by a scan of the content folder (libretro-atari800 atari.c:512-516, + # 537, libretro_core_options.h:1203): no record reads it. launchers = [ *retroarch(read), *retrorun(read), *mame(runemu), - *atari800(read), *scummvm(read), *freej2me(read), *easyrpg(read), + *scummvm(read), *freej2me(read), *easyrpg(read), *amiberry(read, runemu), *hatari(read, runemu), *pico8(read, runemu), *piemu(read, runemu), *yabasanshiro(read, runemu), ] diff --git a/scripts/scraper/configgen_launchers.py b/scripts/scraper/configgen_launchers.py index 03957145e..4f6d05442 100644 --- a/scripts/scraper/configgen_launchers.py +++ b/scripts/scraper/configgen_launchers.py @@ -39,6 +39,14 @@ class Launcher: machines: list[str] | None = None # The platform systems the record is written for, none for every one. systems: list[str] | None = None + # For a record citing a config: where the platform's code puts it + # ({path, source_ref}), whether that leaves a file already there, and + # the launch code rewriting its keys ([] when none does). + installs: dict[str, str] | None = None + kept_if_present: bool | None = None + overrides: list[str] | None = None + # The feature or option file listing the values the option written takes. + choices_from: str | None = None def entry(self) -> dict[str, Any]: """The platform YAML form, in a fixed key order, unset keys left out.""" @@ -46,7 +54,8 @@ class Launcher: "emulator", "systems", "build", "kind", "slot", "names", "expands", "case_insensitive", "search_dir", "passes_to", "fails_when_missing", "fails_scope", "depends_on_option", - "device", "machines", "source_ref", + "device", "machines", "installs", "kept_if_present", "overrides", + "choices_from", "source_ref", ) return {key: getattr(self, key) for key in order if getattr(self, key) is not None} diff --git a/scripts/scraper/emudeck_launchers.py b/scripts/scraper/emudeck_launchers.py index 2723d62d8..b8f42696f 100644 --- a/scripts/scraper/emudeck_launchers.py +++ b/scripts/scraper/emudeck_launchers.py @@ -44,6 +44,8 @@ BIOS = "${biosPath}" EMULATION_BIOS = "${emulationPath}/bios" STORAGE = "${storagePath}" FLATPAK_DATA = "${HOME}/.var/app/" +# The function every EmuScript installs its Flatpak config with. +CONFIG_EMU_FP = "functions/configEmuFP.sh" # EmuScripts file names that differ from the emulator profile they set up. # The platform and target scrapers both read them here. @@ -95,6 +97,31 @@ def _is_bios(value: str) -> bool: return any(value == root or value.startswith(root + "/") for root in (BIOS, EMULATION_BIOS)) +def with_evidence(tree: RepoTree, script: Script, record: Launcher, config: str) -> Launcher: + """A record citing a shipped config, with the steps that install it. + + configEmuFP rsyncs the repository's configs/ over the Flatpak's: + the _init call overwrites (keeping a .bak), every other call passes + --ignore-existing and leaves the user's file in place. + """ + fp = Script(CONFIG_EMU_FP, tree.read(CONFIG_EMU_FP)) + rsync_line, _ = fp.find(r"^\s*rsync .*\$emudeckBackend/configs/", "rsync of the configs") + keep_line, _ = fp.find(r'overwrite="--ignore-existing"', "--ignore-existing") + calls = {n for n, line in script.code() if re.match(r"\s*configEmuFP\s", line)} + if not calls: + raise script.missing("configEmuFP call") + variable = script.assignment_ending("/" + config) + if variable is None: + raise script.missing(f"config variable for {config}") + record.installs = { + "path": script.expand(variable.value, variable.line, set()), + "source_ref": source_ref([(script.path, calls), (fp.path, {keep_line, rsync_line})]), + } + record.kept_if_present = True + record.overrides = [] + return record + + def _repo_config(script: Script, path: str) -> str: """The repository copy of a Flatpak config EmuDeck installs.""" if not path.startswith(FLATPAK_DATA): @@ -268,7 +295,8 @@ def ares(tree: RepoTree) -> list[Launcher]: raise LauncherPatternMissing(path, f"Firmware path under {old}") for system, found in entries.items(): folder = posixpath.commonpath([posixpath.dirname(t) for _, t, _ in found]) - records.append( + records.append(with_evidence( + tree, script, Launcher( emulator="ares", build="standalone", @@ -281,16 +309,20 @@ def ares(tree: RepoTree) -> list[Launcher]: search_dir=folder, passes_to="settings.bml " + ", ".join(f"{system}/Firmware/{k}" for k, _, _ in found), - ) - ) + ), + posixpath.basename(path), + )) return records def melonds(tree: RepoTree) -> list[Launcher]: """melonDS.ini paths set by changeLine, melonDS.toml paths shipped and rewritten by sed.""" name = "emuDeckMelonDS.sh" - records = setter_records(tree, name, option_key="ExternalBIOSEnable") script = _script(tree, name) + records = [ + with_evidence(tree, script, record, str(record.passes_to or "").split(" ", 1)[0]) + for record in setter_records(tree, name, option_key="ExternalBIOSEnable") + ] # A config whose BIOS keys the setters write again keeps their values. set_by_setters = {file for file, _, _, value, _ in setters(script) if _is_bios(value)} for file, old, new, sed_lines in sed_rewrites(script, "melonDS_init"): @@ -306,22 +338,29 @@ def melonds(tree: RepoTree) -> list[Launcher]: continue folder = posixpath.commonpath([posixpath.dirname(t) for _, t in found]) config = posixpath.basename(path) - option = next((s for s in settings(tree.read(path)) if s.key == "ExternalBIOSEnable"), None) - if option is None: - raise LauncherPatternMissing(path, "ExternalBIOSEnable") + shipped = {s.key: s for s in settings(tree.read(path))} + if "ExternalBIOSEnable" not in shipped or "ConsoleType" not in shipped: + raise LauncherPatternMissing(path, "ExternalBIOSEnable and ConsoleType") + option, console = shipped["ExternalBIOSEnable"], shipped["ConsoleType"] + # The DS keys are read under ExternalBIOSEnable, the DSi keys only + # when the console is a DSi (ConsoleType 1). record = Launcher( emulator="melonds", build="standalone", kind="fixed_name", names=[posixpath.relpath(t, folder) for _, t in found], - source_ref=source_ref( - [(path, {option.line, *(s.line for s, _ in found)}), (script.path, sed_lines)] - ), + source_ref=source_ref([ + (path, {option.line, console.line, *(s.line for s, _ in found)}), + (script.path, sed_lines), + ]), search_dir=folder, passes_to=describe_keys(config, tuple((s.section, s.key) for s, _ in found)), - depends_on_option=f"ExternalBIOSEnable = {option.value} in the shipped {config}", + depends_on_option=( + f"[DS] keys: ExternalBIOSEnable = true, {option.value} in the shipped {config}; " + f"[DSi] keys: ConsoleType = 1, {console.value} in the shipped {config}" + ), ) - records.append(record) + records.append(with_evidence(tree, script, record, config)) if len(records) < 2: raise script.missing("melonDS.toml BIOS paths rewritten by sed") return records diff --git a/scripts/scraper/emuelec_launchers.py b/scripts/scraper/emuelec_launchers.py index f4aed1e60..e52a23fc9 100644 --- a/scripts/scraper/emuelec_launchers.py +++ b/scripts/scraper/emuelec_launchers.py @@ -14,7 +14,7 @@ from __future__ import annotations import posixpath import re -from collections.abc import Callable +from collections.abc import Callable, Iterable from dataclasses import dataclass, field from .configgen_launchers import Launcher @@ -22,6 +22,8 @@ from .rocknix_launchers import fixed_paths from .shell_launchers import ( LauncherPatternMissing, Link, + config_placement, + evidence_fields, RepoTree, Script, describe_keys, @@ -104,6 +106,28 @@ class Runs: return (RUN_EMU, set(self.of(name).lines)) +def _evidence( + record: Launcher, + script: Script, + target: str, + keys: list[str], + source_name: str = "", + rewrites: Iterable[int] = (), +) -> Launcher: + """A config record with the start script lines that put its file in place. + + ``rewrites`` are lines the extractor read as rewriting the record's + values without naming their keys (advmame.sh's sed of /roms/mame). + """ + placement = config_placement(script, target, keys, source_name=source_name) + placement.overrides |= set(rewrites) + fields = evidence_fields([placement]) + record.installs = fields["installs"] + record.kept_if_present = fields["kept_if_present"] + record.overrides = fields["overrides"] + return record + + def _read(tree: RepoTree, path: str) -> Script: return Script(path, tree.read(path)) @@ -317,7 +341,7 @@ def duckstation(tree: RepoTree, runs: Runs) -> list[Launcher]: name = posixpath.basename(script.path) lines = {found[k].line for k in (folder_key, *file_keys) if k in found} unset = describe_keys("", file_keys).strip() - return [ + return [_evidence( Launcher( emulator=runs.emulator(name), build="standalone", @@ -328,8 +352,11 @@ def duckstation(tree: RepoTree, runs: Runs) -> list[Launcher]: ), search_dir=found[folder_key].value, passes_to=f"settings.ini [BIOS] SearchDirectory (-settings), {unset} unset", - ) - ] + ), + script, + script.expand("${CONFIG_DIR}/settings.ini", run_line, set()), + [f"[{s}] {k}" for s, k in (folder_key, *file_keys)], + )] YABASANSHIRO_SCRIPTS = ( @@ -473,7 +500,7 @@ def hatari(tree: RepoTree, runs: Runs) -> list[Launcher]: mk = _read(tree, f"{package}/package.mk") mk_line, _ = mk.find(r"cp -R \$\{PKG_DIR\}/config/\* ", "config install") name = posixpath.basename(script.path) - return [ + return [_evidence( Launcher( emulator=runs.emulator(name), build="standalone", @@ -485,8 +512,12 @@ def hatari(tree: RepoTree, runs: Runs) -> list[Launcher]: ), search_dir=value.search_dir, passes_to=f"{describe_keys(copy.group(1), value.keys)} (--configfile)", - ) - ] + ), + script, + script.expand("${" + copy.group(2) + "}", copy_line, set()), + [f"[{s}] {k}" if s else k for s, k in value.keys], + source_name=copy.group(1), + )] _ADVMAME_SYSTEM = re.compile(r'^\s*if \[\[ "\$\{1\}" = "([\w-]+)" \]\]; then$') @@ -535,29 +566,34 @@ def advancemame(tree: RepoTree, runs: Runs) -> list[Launcher]: (script.path, {run_line, *lines}), ]) - found = [] + target = script.expand("${CONFIG_DIR}/advmame.rc", run_line, set()) + + def proved(record: Launcher, key: str) -> Launcher: + return _evidence(record, script, target, [key], rewrites={sed_line}) + + records = [] for key, folder in value: rewritten = folder.replace(sed.group(1), sed.group(2)) if rewritten != folder: # Every system but the one the script rewrites the key for, and # that system with the rewritten folder. - found.append(Launcher( + records.append(proved(Launcher( emulator=runs.emulator(name), build="standalone", kind="passes_path", names=[], source_ref=refs(test_line, sed_line), search_dir=folder, passes_to=f"advmame.rc {key}", depends_on_option=f"system != {test.group(1)}", - )) - found.append(Launcher( + ), key)) + records.append(proved(Launcher( emulator=runs.emulator(name), build="standalone", kind="passes_path", names=[], source_ref=refs(test_line, sed_line), search_dir=rewritten, passes_to=f"advmame.rc {key}", systems=[test.group(1)], - )) + ), key)) continue - found.append(Launcher( + records.append(proved(Launcher( emulator=runs.emulator(name), build="standalone", kind="passes_path", names=[], source_ref=refs(), search_dir=folder, passes_to=f"advmame.rc {key}", - )) - return found + ), key)) + return records def mame(tree: RepoTree, runs: Runs) -> list[Launcher]: @@ -594,42 +630,11 @@ def mame(tree: RepoTree, runs: Runs) -> list[Launcher]: ) if not records: raise script.missing(f"copy into {BIOS_ROOT}") - ini = next((r for r in records if r.names == ["mame*"]), None) - if ini is None: - raise script.missing("copy of mame.ini") - option_path = f"{package}/config/MAME/MAME.opt" - option = next( - (s for s in settings(tree.read(option_path)) if s.key == "mame_read_config"), None - ) - if option is None: - raise LauncherPatternMissing(option_path, "mame_read_config") - ini_path = f"{package}/config/ini/mame.ini" - # MAME reads from the folders its *path options name and writes to its - # *_directory ones. - folders = [ - (s, [p for p in s.value.split(";") if _under(p)]) - for s in settings(tree.read(ini_path), separator=" ") - if s.key.endswith("path") - ] - folders = [(s, parts) for s, parts in folders if parts] - if not folders: - raise LauncherPatternMissing(ini_path, f"path under {BIOS_ROOT}") - for setting, parts in folders: - for part in parts: - records.append( - Launcher( - emulator=emulator, - build="libretro", - kind="passes_path", - names=[], - source_ref=source_ref( - [(ini_path, {setting.line}), (option_path, {option.line})] - ) + f", {ini.source_ref}", - search_dir=part.rstrip("/"), - passes_to=f"mame.ini {setting.key}", - depends_on_option=f"mame_read_config = {option.value}", - ) - ) + # mame.ini hands the core no folder: MAME.opt turns on + # mame_mame_paths_enable, so the core passes no path option + # (libretro mame src/osd/libretro/libretro-internal/retro_init.cpp:554-555), + # and the ini MAME would read sits under a working directory no launcher + # sets (src/emu/emuopts.cpp:49). Only the copies are the launcher's. return records @@ -644,6 +649,16 @@ def scummvm(tree: RepoTree, runs: Runs) -> list[Launcher]: core_line, core = script.find(r"-L /tmp/cores/(\w+)_libretro\.so", "libretro start") arm_line, arm = script.find(r'^\s*"(\w+)"\)', "setup arm", core_line) copy_line, copy = script.find(r"^\s*cp (\S+) (\S+)$", "copy into the BIOS folder", arm_line) + launched = sorted({ + m.group(1) for _, line in runs.script.code() + if (m := re.search(rf"\b{re.escape(name)} (\w+)", line)) + }) + if not launched: + raise runs.script.missing(f"start of {name} with an action") + action = f"{name} {arm.group(1)}" + if arm.group(1) not in launched: + # The copy runs from the setup action, never from a launch. + action += f", an action no launch runs ({RUN_EMU.rsplit('/', 1)[-1]} passes {', '.join(launched)})" used = {copy_line} source = script.expand(copy.group(1), copy_line, used) target = script.expand(copy.group(2), copy_line, used) @@ -669,7 +684,7 @@ def scummvm(tree: RepoTree, runs: Runs) -> list[Launcher]: search_dir=posixpath.dirname(source), passes_to=target, fails_when_missing=False, - depends_on_option=f"{name} {arm.group(1)}", + depends_on_option=action, ), ] diff --git a/scripts/scraper/knulli_launchers.py b/scripts/scraper/knulli_launchers.py index e5a138afd..d2e464324 100644 --- a/scripts/scraper/knulli_launchers.py +++ b/scripts/scraper/knulli_launchers.py @@ -1001,6 +1001,14 @@ def boot_disks( emulator = ( ", ".join(_caller_cores(cg, spec.caller)) if spec.caller else cg.emulator(module) ) + if spec.caller and spec.feature[:2] == ("libretro", "cores"): + # The option is offered for one core of the system: es_features + # lists it under that core, the other cores the generator serves + # never see it (KNULLI's macintosh offers mame alone). + core = spec.feature[2] + if core not in emulator.split(", "): + src.fail(f"es_features offers the option for {core}, which the generator does not serve") + emulator = core found = 0 for chain in ast.walk(src.tree): if not ( @@ -1054,10 +1062,10 @@ def boot_disks( expands={option: values}, case_insensitive=False, search_dir=cg.bios, passes_to=f"command line {flag}", fails_when_missing=False, depends_on_option=f"{option} ({' and '.join(tests)})", - source_ref=", ".join([ - src.ref([*spans, src.span(statement), _lines(added)]), - f"{ES_FEATURES}:{lines[0]}-{lines[1]}" if not listed else "", - ]).rstrip(", "), + # The values the menu offers come from es_features.yml; + # the generator writing the choice is the evidence. + choices_from=None if listed else f"{ES_FEATURES}:{lines[0]}-{lines[1]}", + source_ref=src.ref([*spans, src.span(statement), _lines(added)]), )) if not found: src.fail("names no BIOS file from an option value") diff --git a/scripts/scraper/retrobat_launchers.py b/scripts/scraper/retrobat_launchers.py index 5405580c6..a49709194 100644 --- a/scripts/scraper/retrobat_launchers.py +++ b/scripts/scraper/retrobat_launchers.py @@ -14,6 +14,11 @@ import bisect import re from collections.abc import Callable, Iterator +try: + from .shell_launchers import is_feature_ref, split_ref +except ImportError: + from shell_launchers import is_feature_ref, split_ref # type: ignore[no-redef] + GENERATORS = "emulatorLauncher/Generators" FEATURES = ".emulationstation/es_features.cfg" @@ -1254,6 +1259,11 @@ def xemu(sources: dict[str, Source]) -> list[Record]: ) flash = src.find(r'isOptSet\("(xemu_flashrom)"\)', "xemu_flashrom") + # The flashrom is written outside a Chihiro launch only. + chihiro_set = src.find(r"(_chihiro) = \(([^;]+)\);", "Chihiro test") + chihiro_guard = src.find_all( + rf"if \(!{chihiro_set.group(1)}\)", "flashrom guard", end=flash.start() + )[-1] flash_default = src.find( r'ini\.WriteValue\("([\w.]+)", "(flashrom_path)", "\'" \+ Path\.Combine\(flashromPath, "([^"]+)"\)', "flashrom default", @@ -1267,7 +1277,10 @@ def xemu(sources: dict[str, Source]) -> list[Record]: "core_option", _unique([flash_default.group(3), *choices]), source_ref( - (src.path, [src.lines(flash.start(), flash_default.end())]), + (src.path, [ + src.lines(chihiro_set.start(), chihiro_set.end()), + src.lines(chihiro_guard.start(), flash_default.end()), + ]), (features.path, [choice_lines]), ), case_insensitive=True, @@ -1277,7 +1290,7 @@ def xemu(sources: dict[str, Source]) -> list[Record]: f" default {flash_default.group(3)}" ), fails_when_missing="path written without an existence check", - depends_on_option=flash.group(1), + depends_on_option=f"{flash.group(1)}, not ({chihiro_set.group(2)})", ) ) @@ -3890,4 +3903,23 @@ def extract_launchers(texts: dict[str, str]) -> list[Record]: if missing: raise LauncherPatternMissing(missing[0], "source file") sources = {path: Source(path, text) for path, text in texts.items()} - return [record for extractor in EXTRACTORS for record in extractor(sources)] + return [ + _choices_apart(record) for extractor in EXTRACTORS for record in extractor(sources) + ] + + +def _choices_apart(record: Record) -> Record: + """The es_features.cfg part of a record's source_ref, moved to choices_from. + + The feature file lists the values a menu option offers. The generator + writing the choice is the record's evidence, never the list. + """ + parts = split_ref(str(record["source_ref"])) + listed = [p for p in parts if is_feature_ref(p)] + if not listed: + return record + code = [p for p in parts if not is_feature_ref(p)] + if not code: + raise LauncherPatternMissing(listed[0], "generator code beside the option list") + moved = {k: v for k, v in record.items() if k != "source_ref"} + return {**moved, "choices_from": ", ".join(listed), "source_ref": ", ".join(code)} diff --git a/scripts/scraper/retrodeck_launchers.py b/scripts/scraper/retrodeck_launchers.py index 51b6cf06b..3d7ed0a52 100644 --- a/scripts/scraper/retrodeck_launchers.py +++ b/scripts/scraper/retrodeck_launchers.py @@ -35,6 +35,8 @@ PLACEHOLDER = "RETRODECKBIOSDIR" # gzdoom/component_prepare.sh:27 rewrites to $roms_path. ROMS_PLACEHOLDER = "RETRODECKROMSDIR" CORE_OPTIONS = "retroarch/rd_assets/rd_config/retroarch-core-options.cfg" +# XRoar ROM lists RetroDECK's xroar.conf defines that no machine looks up. +UNREAD_ROMLISTS = frozenset({"coco1", "coco1e", "coco1e_ext", "coco2", "coco2_ext", "coco2b"}) # The component that runs libretro cores. LIBRETRO_COMPONENT = "retroarch" # Values of a core option that name a file rather than a setting. @@ -301,6 +303,19 @@ class Component: 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]}" + # The reset copies the preset over the user's file, then fills its + # placeholders: that is the install, and nothing keeps an older file. + evidence: dict[str, Any] = { + "installs": { + "path": target, + "source_ref": source_ref( + [(script.path, copied[0])] + + [(script.path, line) for _v, line in substitutions.values()] + ), + }, + "kept_if_present": False, + "overrides": self.launch_rewrites(target), + } text = self.read(preset) if text is None: raise LauncherPatternMissing(preset, "preset is missing") @@ -332,10 +347,10 @@ class Component: self.record( "passes_path", [], [(preset, line_no)] + applied, search_dir=value, passes_to=f"{self.config_name(target)} {where}", - fails_when_missing=False, + fails_when_missing=False, **evidence, ) if key == "rompath": - self.romlists(preset, text, value, applied) + self.romlists(preset, text, value, applied, evidence) if not found: raise LauncherPatternMissing(preset, f"no {PLACEHOLDER} line") @@ -349,10 +364,28 @@ class Component: found[m.group(1)] = (f"${m.group(2)}", line_no) return found + def launch_rewrites(self, target: str) -> list[str]: + """The sed lines of the component launcher rewriting ``target``.""" + path = f"{self.name}/component_launcher.sh" + text = self.read(path) + if text is None: + return [] + return [ + source_ref([(path, number)]) + for number, line in enumerate(text.splitlines(), 1) + if re.search(r"\bsed\b.*\s-i\b", line) and target in line + ] + def romlists( - self, preset: str, text: str, rompath: str, applied: list[tuple[str, int]] + self, preset: str, text: str, rompath: str, applied: list[tuple[str, int]], + evidence: dict[str, Any], ) -> None: - """XRoar's `romlist NAME=a,b,@other`: the names tried for each ROM, in order.""" + """XRoar's `romlist NAME=a,b,@other`: the names tried for each ROM, in order. + + A list no XRoar machine or cartridge looks up is no read: coco1, + coco1e, coco1e_ext, coco2, coco2_ext and coco2b are defined by XRoar + (src/xroar.c:663-670) and named by nothing else. + """ 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()) @@ -375,10 +408,12 @@ class Component: return list(dict.fromkeys(names)) for name, (line_no, _items) in lists.items(): + if name in UNREAD_ROMLISTS: + continue 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, + fails_when_missing=False, **evidence, ) def read_launcher(self) -> None: @@ -480,6 +515,7 @@ def core_options(read: Read) -> list[Launcher]: ] if not copied: raise LauncherPatternMissing(prepare_path, "the core options preset is never copied") + target = _words(prepare.splitlines()[copied[0] - 1].strip())[-1] records = [] for line_no, line in enumerate(text.splitlines(), 1): found = re.match(r'^([\w.-]+)\s*=\s*"([^"]*)"$', line.strip()) @@ -489,6 +525,8 @@ def core_options(read: Read) -> list[Launcher]: records.append(Launcher( emulator=LIBRETRO_COMPONENT, build="libretro", kind="core_option", names=[value], passes_to=f"core option {key}", fails_when_missing=False, + installs={"path": target, "source_ref": source_ref([(prepare_path, copied[0])])}, + kept_if_present=False, overrides=[], source_ref=source_ref([(CORE_OPTIONS, line_no), (prepare_path, copied[0])]), )) if not records: diff --git a/scripts/scraper/retropie_launchers.py b/scripts/scraper/retropie_launchers.py index 1f48b28c0..e6462323c 100644 --- a/scripts/scraper/retropie_launchers.py +++ b/scripts/scraper/retropie_launchers.py @@ -94,6 +94,11 @@ class Launcher: passes_to: str | None = None fails_when_missing: bool | str | None = None depends_on_option: str | None = None + # For a record citing a config: where the platform's code puts it, whether + # that leaves a file already there, and the launch code rewriting it. + installs: dict[str, str] | None = None + kept_if_present: bool | None = None + overrides: list[str] | None = None lines: list[tuple[str, int]] = field(default_factory=list, repr=False) def entry(self) -> dict[str, Any]: @@ -101,7 +106,8 @@ class Launcher: order = ( "emulator", "package", "repo", "systems", "build", "kind", "slot", "names", "case_insensitive", "search_dir", "passes_to", - "fails_when_missing", "depends_on_option", "source_ref", + "fails_when_missing", "depends_on_option", "installs", "kept_if_present", + "overrides", "source_ref", ) return {key: getattr(self, key) for key in order if getattr(self, key) is not None} diff --git a/scripts/scraper/rocknix_launchers.py b/scripts/scraper/rocknix_launchers.py index 303d92365..eb18cba3e 100644 --- a/scripts/scraper/rocknix_launchers.py +++ b/scripts/scraper/rocknix_launchers.py @@ -18,6 +18,7 @@ from dataclasses import dataclass try: from .configgen_launchers import Launcher + from .shell_launchers import config_placement, evidence_fields from .shell_launchers import ( LauncherPatternMissing, Link, @@ -30,6 +31,7 @@ try: ) except ImportError: from configgen_launchers import Launcher + from shell_launchers import config_placement, evidence_fields # type: ignore[no-redef] from shell_launchers import ( LauncherPatternMissing, Link, @@ -46,6 +48,9 @@ DEVICES = f"{ROCKNIX}/devices" STANDALONE = f"{ROCKNIX}/packages/emulators/standalone" RETROARCH = f"{ROCKNIX}/packages/emulators/libretro/retroarch" BIOS_ROOT = "/storage/roms/bios" +CONFIG_ROOT = "/storage/.config" +# The first-boot step copying every package's /usr/config to /storage/.config. +USERCONFIG = f"{ROCKNIX}/packages/sysutils/systemd/scripts/userconfig-setup" Lines = list[tuple[str, set[int]]] @@ -186,6 +191,38 @@ def bios_keys(tree: RepoTree, paths: list[str], root: str = BIOS_ROOT) -> list[t return keys +def with_evidence( + tree: RepoTree, + record: Launcher, + target: str, + keys: list[str], + start: Script | None = None, + writers: tuple[str, ...] = (), + source_name: str = "", +) -> Launcher: + """A config record with the steps that put its file at ``target``. + + The first boot copies /usr/config to /storage/.config, leaving a file + already there. A start script can copy or generate it again, and its + sed lines rewriting one of the record's keys are the overrides. + """ + setup = Script(USERCONFIG, tree.read(USERCONFIG)) + placements = [config_placement(setup, target, required=False, sources=("/usr/config/",))] + if start is not None: + placements.append(config_placement( + start, target, keys, writers, source_name, required=False + )) + fields = evidence_fields(placements) + record.installs = fields["installs"] + record.kept_if_present = fields["kept_if_present"] + record.overrides = fields["overrides"] + return record + + +def _keys(value: FixedPaths) -> list[str]: + return [f"[{section}] {key}" if section else key for section, key in value.keys] + + def config_records( tree: RepoTree, emulator: str, @@ -193,10 +230,17 @@ def config_records( file: str, passes_to_suffix: str = "", depends_on_option: str | None = None, + target: str = "", + start: Script | None = None, + unread: Callable[[tuple[str, str]], bool] = lambda key: False, ) -> list[Launcher]: - """fixed_name records for the bios paths of a config installed per device.""" + """fixed_name records for the bios paths of a config installed per device. + + ``unread`` drops the keys the shipped configs leave the emulator + reading no file from. + """ configs = device_configs(tree, package, file) - keys = bios_keys(tree, [c.path for c in configs]) + keys = [k for k in bios_keys(tree, [c.path for c in configs]) if not unread(k)] name = posixpath.basename(file) def read(path: str, text: str) -> tuple[object, set[int]]: @@ -206,7 +250,8 @@ def config_records( for device, value, refs in per_device(tree, configs, f"{package}/package.mk", read): assert isinstance(value, FixedPaths) described = describe_keys(name, value.keys) - records.append( + records.append(with_evidence( + tree, Launcher( emulator=emulator, build="standalone", @@ -219,8 +264,11 @@ def config_records( ), device=device, depends_on_option=depends_on_option, - ) - ) + ), + target or f"{CONFIG_ROOT}/{posixpath.basename(package)}/{name}", + _keys(value), + start, + )) return records @@ -241,7 +289,8 @@ def retroarch(tree: RepoTree) -> list[Launcher]: for device, value, refs in per_device(tree, configs, f"{RETROARCH}/package.mk", read): assert isinstance(value, tuple) values = dict(value) - records.append( + records.append(with_evidence( + tree, Launcher( emulator="retroarch", build="libretro", @@ -251,8 +300,10 @@ def retroarch(tree: RepoTree) -> list[Launcher]: search_dir=values["system_directory"], passes_to="retroarch.cfg " + ", ".join(f'{k} = "{v}"' for k, v in value), device=device, - ) - ) + ), + f"{CONFIG_ROOT}/retroarch/retroarch.cfg", + [k for k, _ in value], + )) return records @@ -275,6 +326,19 @@ def amiberry(tree: RepoTree) -> list[Launcher]: ) used: set[int] = {test_line, then_line, else_line, other_line, sed_line} root = script.expand("${" + sed.group(2) + "}", sed_line, used) + written = re.search(r'"?(\$\{?\w+\}?)"?\s*$', script.lines[then_line - 1]) + if written is None: + raise script.missing("destination of the config copy") + target = script.expand(written.group(1), then_line, set()) + # A WHDLoad archive is booted by --autoload, which builds its own setup + # (kickstart included) over the config: the record holds for floppies. + autoload = [n for n, line in script.code() if re.search(r"amiberry .*--autoload", line)] + if not autoload: + raise script.missing("WHDLoad boot with --autoload") + content = ( + "content not booted with --autoload, a WHDLoad .lha or .zip " + f"({posixpath.basename(script.path)}:{', '.join(map(str, autoload))})" + ) records = [] for config, condition in ( (then.group(1), f"game name matches {test.group(1)}"), @@ -290,7 +354,8 @@ def amiberry(tree: RepoTree) -> list[Launcher]: raise LauncherPatternMissing(path, f"key naming a file under {sed.group(1)}") value, lines = fixed_paths(found, keys, path) slot = re.fullmatch(r"Amiga(\w+)-default\.uae", config) - records.append( + records.append(with_evidence( + tree, Launcher( emulator="amiberry", build="standalone", @@ -300,9 +365,13 @@ def amiberry(tree: RepoTree) -> list[Launcher]: slot=slot.group(1) if slot else config, search_dir=value.search_dir, passes_to=f"{describe_keys(config, value.keys)} (amiberry -f)", - depends_on_option=condition, - ) - ) + depends_on_option=f"{condition}, {content}", + ), + target, + _keys(value), + script, + source_name=config, + )) mk = Script(f"{package}/package.mk", tree.read(f"{package}/package.mk")) links = mk.links(BIOS_ROOT, fixed={"INSTALL": ""}) if not links: @@ -375,7 +444,11 @@ def hatari(tree: RepoTree) -> list[Launcher]: if not keys: raise LauncherPatternMissing(path, f"key naming a file under {BIOS_ROOT}") value, lines = fixed_paths(found, keys, path) - return [ + copied = re.search(r'"?(\$\{?\w+\}?)"?\s*$', script.lines[copy_line - 1]) + if copied is None: + raise script.missing("destination of the default config copy") + return [with_evidence( + tree, Launcher( emulator="hatari", build="standalone", @@ -384,8 +457,12 @@ def hatari(tree: RepoTree) -> list[Launcher]: source_ref=source_ref([(path, lines), (script.path, {copy_line, run_line})]), search_dir=value.search_dir, passes_to=f"{describe_keys(copy.group(1), value.keys)} (--configfile)", - ) - ] + ), + script.expand(copied.group(1), copy_line, set()), + _keys(value), + script, + source_name=copy.group(1), + )] def melonds(tree: RepoTree) -> list[Launcher]: @@ -406,7 +483,22 @@ def melonds(tree: RepoTree) -> list[Launcher]: rf"^\s*{test.group(1)}=\$\(get_setting (\w+) ", f"setting read into {test.group(1)}" ) condition = f"{setting.group(1)} = {test.group(2)} sets ExternalBIOSEnable=1, otherwise 0" - records = config_records(tree, "melonds", package, "melonDS.ini", depends_on_option=condition) + configs = [c.path for c in device_configs(tree, package, "melonDS.ini")] + # melonDS opens the DSi BIOS and firmware keys only under + # DSiExternalBIOSEnable, off unless a config sets it. + dsi_external = any( + s.key == "DSiExternalBIOSEnable" and s.value not in ("", "0") + for path in configs for s in settings(tree.read(path)) + ) + + def unread(key: tuple[str, str]) -> bool: + return not dsi_external and key[1] in ("DSiBIOS9Path", "DSiBIOS7Path", "DSiFirmwarePath") + + conf_line, conf = script.find(r'^\s*CONF_DIR="([^"]+)"', "CONF_DIR") + records = config_records( + tree, "melonds", package, "melonDS.ini", depends_on_option=condition, + target=f"{conf.group(1)}/melonDS.ini", start=script, unread=unread, + ) for record in records: record.source_ref += ( f", {source_ref([(script.path, {setting_line, test_line, enable_line})])}" @@ -420,7 +512,10 @@ def dsperate(tree: RepoTree) -> list[Launcher]: f"{package}/scripts/start_dsperate.sh", tree.read(f"{package}/scripts/start_dsperate.sh") ) run_line, _ = script.find(r"/usr/bin/dsperate --config ", "dsperate --config") - records = config_records(tree, "dsperate", package, "dsperate.ini", " (--config)") + records = config_records( + tree, "dsperate", package, "dsperate.ini", " (--config)", + target=script.expand("${DSPERATE_INI}", run_line, set()), start=script, + ) for record in records: record.source_ref += f", {source_ref([(script.path, {run_line})])}" return records @@ -434,7 +529,10 @@ def xemu(tree: RepoTree) -> list[Launcher]: copy_line, _ = script.find( r'cp -r "\$\{SOURCE_DIR\}/\$\{XEMU_INI\}"', "xemu.toml copy at launch" ) - records = config_records(tree, "xemu", package, "xemu.toml", " (copied at each launch)") + records = config_records( + tree, "xemu", package, "xemu.toml", " (copied at each launch)", + target=script.expand("${CONF_DIR}/${XEMU_INI}", copy_line, set()), start=script, + ) for record in records: record.source_ref += f", {source_ref([(script.path, {copy_line})])}" return records @@ -452,7 +550,9 @@ def nanoboyadvance(tree: RepoTree) -> list[Launcher]: mk_line, _ = Script(mk_path, tree.read(mk_path)).find( r"config/config\.toml", "config.toml install" ) - return [ + start_path = f"{package}/scripts/start_nanoboyadvance.sh" + return [with_evidence( + tree, Launcher( emulator="nanoboyadvance", build="standalone", @@ -461,8 +561,14 @@ def nanoboyadvance(tree: RepoTree) -> list[Launcher]: source_ref=source_ref([(path, lines), (mk_path, {mk_line})]), search_dir=value.search_dir, passes_to=describe_keys("config.toml", value.keys), - ) - ] + # NanoBoyAdvance exits when its BIOS cannot be read + # (src/platform/sdl/main.cpp:180-185 at the build ROCKNIX ships). + fails_when_missing=True, + ), + f"{CONFIG_ROOT}/nanoboyadvance/config.toml", + _keys(value), + Script(start_path, tree.read(start_path)), + )] def mednafen(tree: RepoTree) -> list[Launcher]: @@ -486,7 +592,9 @@ def mednafen(tree: RepoTree) -> list[Launcher]: modules: dict[str, list[Setting]] = {} for setting in firmware: modules.setdefault(setting.key.split(".", 1)[0], []).append(setting) - return [ + home = script.expand("${MEDNAFEN_HOME}", gen_line, set()) + return [with_evidence( + tree, Launcher( emulator="mednafen", build="standalone", @@ -501,9 +609,12 @@ def mednafen(tree: RepoTree) -> list[Launcher]: slot=module, search_dir=folder.value, passes_to="mednafen.cfg " + ", ".join(s.key for s in group), - ) - for module, group in modules.items() - ] + ), + f"{home}/mednafen.cfg", + [s.key for s in group], + script, + writers=("mednafen_gen_config.sh",), + ) for module, group in modules.items()] def yabasanshiro(tree: RepoTree) -> list[Launcher]: @@ -549,6 +660,8 @@ def directory_records( file: str, folder_key: tuple[str, str], file_keys: tuple[tuple[str, str], ...], + target: str = "", + start: str = "", ) -> list[Launcher]: """scans_dir records: the config hands a directory and leaves the file keys empty.""" configs = device_configs(tree, package, file) @@ -568,7 +681,8 @@ def directory_records( records = [] for device, folder, refs in per_device(tree, configs, f"{package}/package.mk", read): passes = f"{name} [{folder_key[0]}] {folder_key[1]} = {folder}, {unset} unset" - records.append( + records.append(with_evidence( + tree, Launcher( emulator=emulator, build="standalone", @@ -578,8 +692,11 @@ def directory_records( search_dir=folder, passes_to=passes, device=device, - ) - ) + ), + target, + [f"[{s}] {k}" for s, k in (folder_key, *file_keys)], + Script(start, tree.read(start)) if start else None, + )) return records @@ -591,6 +708,8 @@ def duckstation(tree: RepoTree) -> list[Launcher]: "settings.ini", ("BIOS", "SearchDirectory"), (("BIOS", "PathNTSCU"), ("BIOS", "PathNTSCJ"), ("BIOS", "PathPAL")), + f"{CONFIG_ROOT}/duckstation/settings.ini", + f"{STANDALONE}/duckstation-sa/scripts/start_duckstation.sh", ) @@ -602,6 +721,8 @@ def armsx2(tree: RepoTree) -> list[Launcher]: "ARMSX2/inis/PCSX2.ini", ("Folders", "Bios"), (("Filenames", "BIOS"),), + f"{CONFIG_ROOT}/ARMSX2/inis/PCSX2.ini", + f"{STANDALONE}/armsx2-sa/scripts/start_armsx2.sh", ) diff --git a/scripts/scraper/shell_launchers.py b/scripts/scraper/shell_launchers.py index 5aa9a56bf..d847b2666 100644 --- a/scripts/scraper/shell_launchers.py +++ b/scripts/scraper/shell_launchers.py @@ -63,6 +63,48 @@ def source_ref(parts: Iterable[tuple[str, Iterable[int]]]) -> str: ) +# The files a platform ships as configuration: an emulator reads them, the +# platform's code never runs them. Batocera's .yml lists and an EmuELEC .opt +# only offer the values a menu option takes. +_CONFIG_FILE = re.compile( + r"\.(?:ini|cfg|conf|rc|rc_\w+|opt|bml|toml|uae|template|yml|yaml)$", re.IGNORECASE +) +_FEATURE_FILE = re.compile(r"(?:^|/)es_features[^/]*$|\.opt$", re.IGNORECASE) + + +def split_ref(ref: str) -> list[str]: + """The parts of a source_ref, each "path:lines", a bare line span + continuing the path before it.""" + parts: list[str] = [] + for part in (p.strip() for p in ref.split(", ")): + if not part: + continue + if re.fullmatch(r"\d+(?:-\d+)?", part) and parts: + parts[-1] += f", {part}" + else: + parts.append(part) + return parts + + +def ref_path(part: str) -> str: + return part.rsplit(":", 1)[0] if re.search(r":\d", part) else part + + +def config_ref(ref: str) -> list[str]: + """The parts of a source_ref that cite a config, option or feature file. + + Such a part proves what the file says, never that an emulator reads it: + melonDS.ini keys are written on ROCKNIX for a build that reads them only + when another key is set. + """ + return [part for part in split_ref(ref) if _CONFIG_FILE.search(ref_path(part))] + + +def is_feature_ref(part: str) -> bool: + """Whether a source_ref part cites a file that lists a menu option's values.""" + return bool(_FEATURE_FILE.search(ref_path(part))) + + @dataclass class Word: """A shell word: as written, and with quotes removed.""" @@ -394,6 +436,149 @@ def _merge_links(links: list[Link]) -> list[Link]: return [link for link in merged if id(link) not in folded] +_COPY = re.compile(r"(?:^|[\s;&|(])(cp|rsync|install|mv)\s") +_GUARD = re.compile(r"\[\[?\s*!\s+-[fde]\s+(\S+)\s*\]") + + +@dataclass +class ConfigPlacement: + """How a start script puts a config where the emulator reads it. + + ``install`` are the lines copying it there, ``kept_if_present`` whether + every such copy runs only when the file (or its folder) is absent, and + ``overrides`` the sed lines rewriting one of the record's keys in it. + """ + + script: str + path: str + install: set[int] + kept_if_present: bool + overrides: set[int] + + +def _bare_key(key: str) -> str: + return key.rsplit("] ", 1)[-1].rsplit("/", 1)[-1].strip() + + +def _guarded(script: Script, number: int, target: str) -> bool: + """Whether a line runs only when target, or a folder above it, is absent.""" + + def covers(found: re.Match[str], at: int) -> bool: + guarded = script.expand(words(found.group(1))[0].text, at, set()).rstrip("/") + return target == guarded or target.startswith(guarded + "/") + + line = script.lines[number - 1] + if any(covers(m, number) for m in _GUARD.finditer(line)): + return True + depth = 0 + for at in range(number - 1, 0, -1): + text = script.lines[at - 1].strip() + if re.match(r"fi\b", text): + depth += 1 + elif re.match(r"if\b", text): + if depth: + depth -= 1 + continue + return any(covers(m, at) for m in _GUARD.finditer(text)) + return False + + +def config_placement( + script: Script, + target: str, + keys: Iterable[str] = (), + writers: Iterable[str] = (), + source_name: str = "", + required: bool = True, + sources: tuple[str, ...] = (), +) -> ConfigPlacement: + """The lines of a start script that install ``target`` and rewrite its keys. + + A copy installs the target when its destination is the file or a folder + above it. ``writers`` are other commands that write the file (ROCKNIX + generates mednafen.cfg with mednafen_gen_config.sh). A copy guarded by + `[ ! -f X ]` or `[ ! -d X ]` on the target or a folder above it, or made + with cp -n or rsync --ignore-existing, leaves a file already there. + """ + install: set[int] = set() + kept = True + overrides: set[int] = set() + bare = {_bare_key(k) for k in keys} - {""} + writer = re.compile("|".join(re.escape(w) for w in writers)) if writers else None + for number, line in script.code(): + if (copy := _COPY.search(line)) or (writer and writer.search(line)): + if copy: + args = [] + for word in words(line[copy.start(1):])[1:]: + # The command ends at a separator or a redirection. + if word.raw in {";", "&&", "||", "|"} or re.match(r"\d*>", word.raw): + break + args.append(word.text) + operands = [a for a in args if not a.startswith("-")] + if not operands: + continue + destination = script.expand(operands[-1], number, set()).rstrip("/") + source = script.expand(operands[0], number, set()).rstrip("/") + hits = target == destination or target.startswith(destination + "/") or ( + target.startswith(f"{destination}/{source.rsplit('/', 1)[-1]}") + ) + # A copy of another file into the same place is another + # record's: amiberry copies the A500 or the A1200 setup. + named = source.rsplit("/", 1)[-1] + if not hits or (source_name and "." in named and named != source_name): + continue + # A named file copied into a folder above the target is + # another file of that folder (ARMSX2's secrets.ini). + if ( + not source_name and "." in named and "*" not in named + and destination != target and named != target.rsplit("/", 1)[-1] + ): + continue + if sources and not source.startswith(sources): + continue + skip = "-n" in args or "--ignore-existing" in args + else: + skip = False + install.add(number) + if not (skip or _guarded(script, number, target)): + kept = False + elif re.search(r"\bsed\b.*\s-i\b|\bsed -i", line) and bare: + expanded = script.expand(line, number, set()) + if target in expanded or target.rsplit("/", 1)[-1] in expanded: + if any(re.search(rf"\b{re.escape(k)}\b", expanded) for k in bare): + overrides.add(number) + if not install and required: + raise script.missing(f"install of {target}") + return ConfigPlacement(script.path, target, install, kept, overrides) + + +def evidence_fields( + placements: Iterable[ConfigPlacement], + installed: Iterable[tuple[str, Iterable[int]]] = (), +) -> dict: + """The installs, kept_if_present and overrides fields of a config record. + + ``placements`` are the steps that put the file in place, in the order + they run (a first-boot copy, then the start script). ``installed`` are + the build steps that put it where they copy it from (a package.mk copy + into /usr/config). + """ + placements = list(placements) + steps = [p for p in placements if p.install] + if not steps: + raise LauncherPatternMissing(placements[0].path if placements else "", "install") + return { + "installs": { + "path": steps[-1].path, + "source_ref": source_ref([*installed, *((p.script, p.install) for p in steps)]), + }, + "kept_if_present": all(p.kept_if_present for p in steps), + "overrides": [ + source_ref([(p.script, {line})]) for p in placements for line in sorted(p.overrides) + ], + } + + @dataclass class Setting: section: str diff --git a/scripts/validate_schemas.py b/scripts/validate_schemas.py index 1d1809500..dc84746b6 100644 --- a/scripts/validate_schemas.py +++ b/scripts/validate_schemas.py @@ -81,7 +81,10 @@ def _validate_yaml_directory( continue out.extend(_errors(validator, data, str(path.relative_to(ROOT)))) out.extend(_unreachable_citations(path, data)) + if schema_name == "platform.schema.json": + out.extend(_config_records_without_evidence(path, data)) if schema_name == "emulator.schema.json": + out.extend(_config_reads_without_build(path, data)) out.extend(_checks_without_values(path, data)) out.extend(_save_dir_without_library_name(path, data)) out.extend(_systems_outside_profile(path, data)) @@ -102,6 +105,59 @@ _CHECK_FIELDS = { } +def _config_records_without_evidence(path: Path, document: object) -> list[str]: + """A launcher record citing a config without the links its extractor reads. + + A config file is proof only when the code installing it and the code + reading it are shown (launchers.config_evidence). The platform's half + is the extractor's to write: a record citing a shipped config carries + installs and overrides, and a feature or option file is never a + record's source, only its choices_from. + """ + if not isinstance(document, dict): + return [] + from scraper.shell_launchers import config_ref, is_feature_ref # noqa: PLC0415 + + out: list[str] = [] + for index, record in enumerate(document.get("launchers") or []): + if not isinstance(record, dict): + continue + cited = config_ref(str(record.get("source_ref") or "")) + where = f"{path.relative_to(ROOT)}: launchers[{index}] ({record.get('emulator')})" + features = [part for part in cited if is_feature_ref(part)] + if features: + out.append(f"{where} cites {features[0]} as source, choices_from holds it") + if [part for part in cited if not is_feature_ref(part)] and not ( + record.get("installs") and "overrides" in record + ): + out.append(f"{where} cites a config without installs and overrides") + return out + + +def _config_reads_without_build(path: Path, document: object) -> list[str]: + """A config_reads entry naming a platform the profile gives no pin_build. + + Such an entry is read at the build that platform ships, and pin_build is + where that build is named. + """ + if not isinstance(document, dict): + return [] + builds = {str(b.get("platform")) for b in document.get("pin_build") or []} + out: list[str] = [] + for index, read in enumerate(document.get("config_reads") or []): + if not isinstance(read, dict): + continue + if read.get("revision"): + continue + for platform in read.get("platforms") or []: + if str(platform) not in builds: + out.append( + f"{path.relative_to(ROOT)}: config_reads[{index}] ({read.get('file')}) " + f"names {platform}, which has no pin_build" + ) + return out + + def _systems_outside_profile(path: Path, document: object) -> list[str]: """An entry's system: or systems: naming a system its profile does not list. diff --git a/scripts/verify.py b/scripts/verify.py index 8c2d40e57..a3fe5fb02 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -100,7 +100,7 @@ from common import ( yaml = require_yaml() from exporter.candidates import BIOS, EntryCategories -from launchers import LauncherLayer, declared_destinations, held_record +from launchers import LauncherLayer, declared_destinations, held_record, launcher_unproven from cross_reference import launcher_reads_not_held from nativemode import ( SIZE_AND_ANY_HASH, @@ -1764,6 +1764,8 @@ def verify_platform( "launcher_reads_not_held": launcher_reads_not_held( {config.get("_key", platform): config}, db, profiles, data_dir_registry ), + # Records a config file does not prove: listed, never consumed. + "launcher_unproven": launcher_unproven({config.get("_key", platform): config}, profiles), "exclusion_notes": exclusions, "region_fallbacks": sorted(region_fallbacks), **({"left_out": left_out} if no_game_data else {}), @@ -2130,6 +2132,21 @@ def _print_undeclared_section(result: dict, verbose: bool) -> None: ) +def _print_launcher_unproven(result: dict, verbose: bool) -> None: + """Launcher records resting on a config file the code does not prove.""" + rows = result.get("launcher_unproven") or [] + if not rows: + return + listed = "" if verbose else " (--verbose lists them)" + print(f" Launcher records unproven: {len(rows)}{listed}") + if not verbose: + return + for row in rows: + print(f" [{row['index']}] {row['emulator']} {row['kind']}: {row['passes_to']}") + for link in row["missing"]: + print(f" missing {link}") + + def _print_launcher_reads(result: dict, verbose: bool) -> None: """Names the platform's launch code reads that the collection lacks.""" every = result.get("launcher_reads_not_held") or [] @@ -2220,6 +2237,7 @@ def print_platform_result( _print_detail_entries(result["details"], seen_details, verbose) _print_undeclared_section(result, verbose) _print_launcher_reads(result, verbose) + _print_launcher_unproven(result, verbose) # The same notes come back for every platform running these cores and # filled 985 of the 1114 lines of verify --all: counted, listed on ask. diff --git a/tests/test_launcher_evidence.py b/tests/test_launcher_evidence.py new file mode 100644 index 000000000..5f01c2929 --- /dev/null +++ b/tests/test_launcher_evidence.py @@ -0,0 +1,330 @@ +"""A launcher record resting on a config file binds only when its chain holds. + +The platform's code installs the file (installs), its launch code was read +for what replaces it (overrides), and the emulator's profile says where the +build the platform ships loads the file and reads each key (config_reads). +""" + +from __future__ import annotations + +import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache + +import sys +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) + +import yaml # noqa: E402 + +from common import load_platform_config # noqa: E402 +from launchers import ( # noqa: E402 + bound_launchers, + config_evidence, + config_target, + handed_directories, + launcher_places, + launcher_unproven, +) +from profile_sync import _check_build_refs, shipped_build_holders # noqa: E402 +from scraper.retrobat_launchers import _choices_apart # noqa: E402 +from scraper.shell_launchers import ( # noqa: E402 + LauncherPatternMissing, + Script, + config_placement, + config_ref, + is_feature_ref, +) +from validate_schemas import ( # noqa: E402 + _config_reads_without_build, + _config_records_without_evidence, +) + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def record(**fields: object) -> dict: + """A ROCKNIX-like DuckStation record citing its shipped settings.ini.""" + base = { + "emulator": "duck", "build": "standalone", "kind": "scans_dir", "names": [], + "search_dir": "/storage/roms/bios", + "passes_to": "settings.ini [BIOS] SearchDirectory = /storage/roms/bios, [BIOS] PathPAL unset", + "installs": {"path": "/storage/.config/duck/settings.ini", "source_ref": "setup.sh:13"}, + "overrides": [], + "source_ref": "duck/config/settings.ini:4, duck/package.mk:33", + } + return {**base, **fields} + + +def profile(**fields: object) -> dict: + base = { + "emulator": "Duck", "type": "standalone", "cores": ["duck"], + "standalone_directories": ["[BIOS] SearchDirectory"], + "config_reads": [{ + "file": "settings.ini", "source_ref": "src/core.cpp:10", + "keys": [ + {"key": "[BIOS] SearchDirectory", "source_ref": "src/settings.cpp:20"}, + {"key": ["[BIOS] PathPAL", "[BIOS] PathNTSCU"], "source_ref": "src/bios.cpp:30"}, + ], + }], + } + return {**base, **fields} + + +class ConfigRefs(unittest.TestCase): + def test_config_option_and_feature_files_are_cited_apart(self) -> None: + ref = "a/settings.ini:4, 6, a/start.sh:3, b/MAME.opt:19, .emulationstation/es_features.cfg:9" + self.assertEqual( + config_ref(ref), + ["a/settings.ini:4, 6", "b/MAME.opt:19", ".emulationstation/es_features.cfg:9"], + ) + self.assertEqual([is_feature_ref(p) for p in config_ref(ref)], [False, True, True]) + + def test_passes_to_names_the_file_and_its_keys(self) -> None: + cases = { + "settings.ini [BIOS] SearchDirectory (-settings), [BIOS] PathNTSCU, PathPAL unset": + ("settings.ini", ["[BIOS] SearchDirectory", "[BIOS] PathNTSCU", "[BIOS] PathPAL"]), + "melonDS.toml [DS] BIOS9Path [DSi] NANDPath, first file whose name holds the region": + ("melonDS.toml", ["[DS] BIOS9Path", "[DSi] NANDPath"]), + "settings.ini [BIOS] PathNTSCJ = PathNTSCU = ": + ("settings.ini", ["[BIOS] PathNTSCJ", "[BIOS] PathNTSCU"]), + "AmigaA500-default.uae kickstart_rom_file, $(FILE_PATH) = /storage/k/": + ("AmigaA500-default.uae", ["kickstart_rom_file", "$(FILE_PATH)"]), + "command line -rompath bios/coco": ("command line", ["-rompath"]), + "core option pcsx2_bios": ("core options", ["pcsx2_bios"]), + } + for passes_to, expected in cases.items(): + with self.subTest(passes_to=passes_to): + self.assertEqual(config_target({"passes_to": passes_to}), expected) + core = {"build": "libretro", "kind": "core_option", "passes_to": "hatarib_tos = x/"} + self.assertEqual(config_target(core), ("core options", ["hatarib_tos"])) + + def test_a_record_naming_no_file_takes_the_config_it_installs(self) -> None: + romlist = record( + passes_to="romlist coco", + installs={"path": "$xroar_config", "source_ref": "prepare.sh:13"}, + source_ref="xroar/rd_config/xroar.conf:305, xroar/prepare.sh:13", + ) + self.assertEqual(config_target(romlist), ("xroar.conf", ["romlist coco"])) + + +class Evidence(unittest.TestCase): + profiles = {"duck": profile()} + + def test_a_record_resting_on_code_holds(self) -> None: + code = {"emulator": "duck", "kind": "fixed_name", "source_ref": "start.sh:3"} + self.assertTrue(config_evidence(code, {}).proven) + + def test_the_whole_chain_holds(self) -> None: + evidence = config_evidence(record(), self.profiles, "rocknix") + self.assertTrue(evidence.proven, evidence.missing) + self.assertEqual(evidence.readers, ("duck",)) + + def test_each_missing_link_is_named(self) -> None: + bare = {k: v for k, v in record().items() if k not in ("installs", "overrides")} + missing = config_evidence(bare, self.profiles).missing + self.assertEqual(len(missing), 2) + self.assertTrue(missing[0].startswith("installs")) + self.assertTrue(missing[1].startswith("overrides")) + + def test_a_profile_that_reads_no_such_file_proves_nothing(self) -> None: + missing = config_evidence(record(), {"duck": profile(config_reads=[])}).missing + self.assertEqual(missing, ("config_reads: an emulator that loads settings.ini",)) + + def test_a_key_the_build_does_not_read_is_named(self) -> None: + unread = record(passes_to="settings.ini [BIOS] PathNTSCJ") + self.assertEqual( + config_evidence(unread, self.profiles).missing, + ("config_reads: no read of [BIOS] PathNTSCJ",), + ) + + def test_a_handle_with_its_code_ref_reads_the_key(self) -> None: + handled = profile( + config_reads=[{"file": "settings.ini", "source_ref": "src/core.cpp:10", "keys": [ + {"key": "[BIOS] PathPAL", "source_ref": "src/bios.cpp:30"}]}], + standalone_directories=[ + {"handle": "[BIOS] SearchDirectory", "source_ref": "src/settings.cpp:20"} + ], + ) + self.assertTrue(config_evidence(record(), {"duck": handled}).proven) + + def test_an_option_list_is_never_the_source(self) -> None: + listed = record(source_ref="gen.cs:3, .emulationstation/es_features.cfg:9") + self.assertIn( + "an option list cited as source, not as choices_from", + config_evidence(listed, self.profiles).missing, + ) + + def test_an_option_choice_needs_the_emulator_side_too(self) -> None: + chosen = { + "emulator": "duck", "kind": "core_option", "passes_to": "settings.ini [BIOS] PathPAL = ", + "choices_from": "es_features.cfg:9", "source_ref": "gen.cs:3", + } + self.assertTrue(config_evidence(chosen, self.profiles).proven) + self.assertFalse(config_evidence(chosen, {"duck": profile(config_reads=[])}).proven) + + def test_reads_written_for_a_shipped_build_apply_to_its_platform_only(self) -> None: + built = profile( + source_commit="a" * 40, + pin_build=[{"platform": "rocknix", "value": "b" * 40}], + config_reads=[{**profile()["config_reads"][0], "platforms": ["rocknix"]}], + ) + self.assertTrue(config_evidence(record(), {"duck": built}, "rocknix").proven) + self.assertFalse(config_evidence(record(), {"duck": built}, "emuelec").proven) + + def test_reads_at_the_pin_skip_a_platform_shipping_another_build(self) -> None: + other = profile(source_commit="a" * 40, pin_build=[{"platform": "rocknix", "value": "b" * 40}]) + self.assertFalse(config_evidence(record(), {"duck": other}, "rocknix").proven) + self.assertTrue(config_evidence(record(), {"duck": other}, "emuelec").proven) + + +class Gate(unittest.TestCase): + def config(self, *records: dict) -> dict: + return {"_key": "rocknix", "base_destination": "bios", "launchers": list(records)} + + def test_an_unproven_record_hands_no_directory(self) -> None: + proven = handed_directories(self.config(record()), {"duck": profile()}) + self.assertEqual([h.folder for h in proven], [""]) + self.assertEqual(handed_directories(self.config(record()), {"duck": profile(config_reads=[])}), []) + + def test_an_unproven_record_places_no_file(self) -> None: + named = record( + kind="fixed_name", names=["tos.img"], passes_to="settings.ini [BIOS] PathPAL", + ) + self.assertEqual(len(launcher_places(self.config(named), {"duck": profile()})), 1) + self.assertEqual(launcher_places(self.config(named), {"duck": profile(config_reads=[])}), []) + + def test_the_unproven_are_reported_with_what_they_lack(self) -> None: + config = self.config({"emulator": "x", "kind": "links", "source_ref": "a.sh:1"}, record()) + self.assertEqual(launcher_unproven({"rocknix": config}, {"duck": profile()}), []) + (row,) = launcher_unproven({"rocknix": config}, {"duck": profile(config_reads=[])}) + self.assertEqual((row["platform"], row["index"], row["emulator"]), ("rocknix", 1, "duck")) + self.assertEqual(row["config"], ["duck/config/settings.ini:4"]) + self.assertEqual(len(bound_launchers(config, {"duck": profile(config_reads=[])})), 1) + + +class StartScripts(unittest.TestCase): + SCRIPT = ( + 'CONF_DIR="/storage/.config/duck"\n' + 'CONF_FILE="${CONF_DIR}/settings.ini"\n' + '[ ! -d "${CONF_DIR}" ] && cp -r /usr/config/duck /storage/.config\n' + 'if [ ! -f "${CONF_FILE}" ]; then\n' + ' cp /usr/config/duck/settings.ini ${CONF_FILE}\n' + 'fi\n' + "sed -i '/^AspectRatio/c\\\\AspectRatio = 4:3' ${CONF_FILE}\n" + "sed -i '/^PathPAL/c\\\\PathPAL = x.bin' ${CONF_FILE}\n" + 'cp /usr/config/duck/other.ini ${CONF_DIR}/ >/dev/null 2>&1\n' + ) + + def test_a_guarded_copy_keeps_the_file_and_a_key_rewrite_is_an_override(self) -> None: + placement = config_placement( + Script("start.sh", self.SCRIPT), "/storage/.config/duck/settings.ini", ["[BIOS] PathPAL"] + ) + self.assertEqual(placement.install, {3, 5}) + self.assertTrue(placement.kept_if_present) + self.assertEqual(placement.overrides, {8}) + + def test_a_copy_at_each_launch_replaces_the_file(self) -> None: + script = Script("start.sh", 'cp /usr/config/duck/settings.ini /storage/.config/duck/settings.ini\n') + self.assertFalse(config_placement(script, "/storage/.config/duck/settings.ini").kept_if_present) + + def test_a_script_installing_nothing_raises(self) -> None: + with self.assertRaises(LauncherPatternMissing): + config_placement(Script("start.sh", "echo hi\n"), "/storage/.config/duck/settings.ini") + + def test_retrobat_moves_the_option_list_to_choices_from(self) -> None: + moved = _choices_apart({ + "emulator": "x", "passes_to": "p", + "source_ref": "Gen.cs:3-4, .emulationstation/es_features.cfg:9-12", + }) + self.assertEqual(moved["source_ref"], "Gen.cs:3-4") + self.assertEqual(moved["choices_from"], ".emulationstation/es_features.cfg:9-12") + self.assertEqual(list(moved)[-1], "source_ref") + + +class Invariants(unittest.TestCase): + def test_a_config_record_without_its_platform_links_is_an_error(self) -> None: + path = REPO_ROOT / "platforms" / "x.yml" + bare = {k: v for k, v in record().items() if k not in ("installs", "overrides")} + listed = record(source_ref="gen.cs:3, es_features.yml:9") + errors = _config_records_without_evidence(path, {"launchers": [record(), bare, listed]}) + self.assertEqual(len(errors), 2) + self.assertIn("without installs and overrides", errors[0]) + self.assertIn("choices_from holds it", errors[1]) + + def test_reads_naming_a_platform_need_its_build(self) -> None: + path = REPO_ROOT / "emulators" / "x.yml" + reads = [{"file": "a.ini", "platforms": ["rocknix"], "source_ref": "a:1", "keys": []}] + self.assertEqual(len(_config_reads_without_build(path, {"config_reads": reads})), 1) + self.assertEqual(_config_reads_without_build( + path, {"config_reads": reads, "pin_build": [{"platform": "rocknix"}]} + ), []) + pinned = [{**reads[0], "revision": "c" * 40, "revision_from": "the exe"}] + self.assertEqual(_config_reads_without_build(path, {"config_reads": pinned}), []) + + +class BuildRefs(unittest.TestCase): + def test_reads_for_a_shipped_build_are_read_at_that_build(self) -> None: + document = { + "pin_build": [{"platform": "rocknix", "value": "b" * 40}], + "config_reads": [{ + "file": "a.ini", "platforms": ["rocknix"], "source_ref": "src/a.c:2", + "keys": [{"key": "k", "source_ref": "src/a.c:9, ROCKNIX x.patch:1"}], + }], + } + held = shipped_build_holders(document) + self.assertEqual(len(held), 2) + + class View: + repo, field = "repo", "source" + + class Citation: + def __init__(self, ref: str, field: str) -> None: + self.ref, self.field = ref, field + + asked: list[tuple[str, str]] = [] + + def fetch(_repo: str, revision: str, path: str) -> list[str] | None: + asked.append((revision, path)) + return ["line"] * 5 if path == "src/a.c" else None + + rows = _check_build_refs( + document, + [(Citation("src/a.c:2", "c"), document["config_reads"][0]), + (Citation("src/a.c:9, ROCKNIX x.patch:1", "k"), document["config_reads"][0])], + [View()], fetch, + ) + self.assertEqual([r["status"] for r in rows], ["BUILD_OK", "BUILD_GONE"]) + self.assertTrue(all(revision == "b" * 40 for revision, _ in asked)) + self.assertNotIn(("b" * 40, "ROCKNIX x.patch"), asked) + + +class RepositoryRecords(unittest.TestCase): + """The placement chains the audit of 2026-11-10 found moving files.""" + + def test_the_chains_that_move_placements_still_bind(self) -> None: + platforms = REPO_ROOT / "platforms" + cases = ( + ("rocknix", "armsx2", "/storage/roms/bios/armsx2"), + ("retrodeck", "gzdoom", "$roms_path/doom"), + ("retrodeck", "xroar", "$roms_path/coco"), + ("retrobat", "xroar", "bios/coco"), + ) + for platform, emulator, directory in cases: + with self.subTest(platform=platform, emulator=emulator): + handed = handed_directories(load_platform_config(platform, str(platforms))) + self.assertIn( + directory, {h.directory for h in handed if h.emulator == emulator} + ) + + def test_every_config_record_carries_its_platform_links(self) -> None: + for path in sorted((REPO_ROOT / "platforms").glob("*.yml")): + if path.name.startswith("_"): + continue + with self.subTest(platform=path.stem): + document = yaml.safe_load(path.read_text(encoding="utf-8")) + self.assertEqual(_config_records_without_evidence(path, document), []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_launcher_gaps.py b/tests/test_launcher_gaps.py index e1bdac934..a2e8bdbc1 100644 --- a/tests/test_launcher_gaps.py +++ b/tests/test_launcher_gaps.py @@ -33,6 +33,7 @@ import verify # noqa: E402 from common import _emulator_profiles_cache, load_emulator_profiles, load_platform_config # noqa: E402 from cross_reference import ( # noqa: E402 LAUNCHER_READS_KEY, + LAUNCHER_UNPROVEN_KEY, launcher_file_names, launcher_reads_not_held, print_launcher_reads, @@ -278,7 +279,11 @@ class LauncherGapTest(unittest.TestCase): names = {r["name"] for r in report[LAUNCHER_READS_KEY]} self.assertEqual(names, {"absent.bin", "mixed.rom", "labelled.rom", "user.sf2"}) # The emulator rows keep their own shape and counts. - self.assertEqual(set(report) - {LAUNCHER_READS_KEY}, {"aliasemu", "missemu", "setemu"}) + self.assertEqual( + set(report) - {LAUNCHER_READS_KEY, LAUNCHER_UNPROVEN_KEY}, + {"aliasemu", "missemu", "setemu"}, + ) + self.assertEqual(report[LAUNCHER_UNPROVEN_KEY], []) def test_verify_counts_and_exit_code_are_unchanged(self) -> None: """A launcher name is reported, not judged: nothing else moves.""" diff --git a/tests/test_launcher_layer.py b/tests/test_launcher_layer.py index 0e6e8fae0..7f37fef6b 100644 --- a/tests/test_launcher_layer.py +++ b/tests/test_launcher_layer.py @@ -314,9 +314,9 @@ class LauncherLayerTest(unittest.TestCase): config = {"base_destination": "bios", "launchers": [{ "emulator": "e", "build": "libretro", "kind": "fixed_name", "names": ["a.rom", "b.rom"], "search_dir": "/storage/roms/bios", - "passes_to": "e.cfg ROM_A, ROM_B", "source_ref": "e.cfg:1", + "passes_to": "e.cfg ROM_A, ROM_B", "source_ref": "launch.sh:1", }]} - places = {p.paths[0]: p.passes_to for p in launcher_places(config)} + places = {p.paths[0]: p.passes_to for p in launcher_places(config, {})} self.assertEqual(places, {"a.rom": "e.cfg ROM_A", "b.rom": "e.cfg ROM_B"}) def test_a_core_option_names_the_dump_of_a_model(self) -> None: @@ -341,12 +341,16 @@ class LauncherLayerTest(unittest.TestCase): "files": [{"name": ".bin", "path": "ps2/bios/.bin", "required": True, "min_size": 32, "max_size": 64, "validation": ["size"], "config_key": "emu_bios"}], + "config_reads": [{"file": "core options", "source_ref": "core.c:1", + "keys": [{"key": "emu_bios", "source_ref": "core.c:2"}]}], } with open(os.path.join(self.emulators_dir, "optemu.yml"), "w") as fh: yaml.dump(profile, fh) self.config["launchers"].append({ "emulator": "retroarch", "build": "libretro", "kind": "core_option", "names": ["scph10000.bin"], "passes_to": "core option emu_bios", + "installs": {"path": "retroarch-core-options.cfg", "source_ref": "prepare.sh:24"}, + "overrides": [], "source_ref": "core-options.cfg:515", }) _emulator_profiles_cache.clear() diff --git a/tests/test_launcher_placement.py b/tests/test_launcher_placement.py index b8a24598c..71550bebb 100644 --- a/tests/test_launcher_placement.py +++ b/tests/test_launcher_placement.py @@ -962,6 +962,17 @@ class GzdoomOnRetroDECK(unittest.TestCase): self.assertFalse(any(e.get("standalone_path") for e in view["files"])) +def _reads_configs(profile: str) -> bool: + """Whether a repository profile carries config_reads.""" + path = REPO_ROOT / "emulators" / f"{profile}.yml" + return bool((yaml.safe_load(path.read_text(encoding="utf-8")) or {}).get("config_reads")) + + +# EmuELEC hands AdvanceMAME its folders through advmame.rc: the chain binds +# once the profile says where the build reads them (config_reads). +@unittest.skipUnless( + _reads_configs("advancemame"), "advancemame.yml carries no config_reads, dir_rom is unproven" +) class AdvanceMameOnEmuELEC(unittest.TestCase): """advmame.rc sets dir_rom /storage/roms/mame, /storage/roms/arcade for the arcade system (advmame.rc:11, advmame.sh:26-30).""" diff --git a/tests/test_launchers_amberelec.py b/tests/test_launchers_amberelec.py index c0ba27d36..39ce37130 100644 --- a/tests/test_launchers_amberelec.py +++ b/tests/test_launchers_amberelec.py @@ -105,22 +105,6 @@ HATARI_CFG = text( """ ) -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() { @@ -141,6 +125,17 @@ AMIBERRY_SH = text( cp $AMIBERRY_CONFIG_DIR/AmigaA500-default.uae "$AMIBERRY_TMP_CONFIG" fi } + amiberry -f "$AMIBERRY_TMP_CONFIG" --autoload "$1" >> $AMIBERRY_LOG 2>&1 + """ +) + +USERCONFIG = text( + """ + #!/bin/bash + if [ ! -e "/storage/.configured" ] + then + rsync -a --ignore-existing --exclude=es_systems.cfg /usr/config/* /storage/.config/ || exit 1 + fi """ ) @@ -195,8 +190,7 @@ def tree(**overrides: str) -> dict[str, str]: 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.USERCONFIG: USERCONFIG, al.AMIBERRY_PACKAGE: AMIBERRY_MK, al.SCRIPTS["AMIBERRY"]: AMIBERRY_SH, f"{al.AMIBERRY_CONFIGS}/AmigaA1200-default.uae": uae("kick40068.A1200.rom"), @@ -218,6 +212,13 @@ class Frontends(unittest.TestCase): (record,) = al.retroarch(read()) self.assertEqual(record.search_dir, "~/roms/bios") self.assertEqual(record.source_ref, f"{al.RETROARCH_CFG}:2") + # The first boot copies /usr/config and leaves a file already there. + self.assertEqual(record.installs, { + "path": "/storage/.config/retroarch/retroarch.cfg", + "source_ref": f"{al.USERCONFIG}:4", + }) + self.assertIs(record.kept_if_present, True) + self.assertEqual(record.overrides, []) def test_retrorun_directory(self): (record,) = al.retrorun(read()) @@ -266,6 +267,9 @@ class Standalone(unittest.TestCase): record.source_ref, f"{al.RUNEMU}:3, {al.SCRIPTS['HATARISA']}:3-4, {al.HATARI_CONFIG}:2", ) + # Copied over at each launch: nothing the user edits survives. + self.assertEqual(record.installs["source_ref"], f"{al.SCRIPTS['HATARISA']}:3") + self.assertIs(record.kept_if_present, False) def test_amiberry_link_and_one_kickstart_per_config(self): link, a1200, a500 = al.amiberry(read(), al.Runemu(read())) @@ -273,7 +277,11 @@ class Standalone(unittest.TestCase): 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})") + self.assertTrue(a500.depends_on_option.startswith("not (")) + # A WHDLoad boot replaces the setup the config gives. + self.assertIn("--autoload", a1200.depends_on_option) + self.assertEqual(a1200.installs["source_ref"], f"{al.SCRIPTS['AMIBERRY']}:5") + self.assertEqual(a500.installs["source_ref"], f"{al.SCRIPTS['AMIBERRY']}:7") def test_a_table_running_another_script_raises(self): broken = RUNEMU.replace("/usr/bin/yabasanshiro.sh", "/usr/bin/yaba.sh") @@ -287,15 +295,9 @@ class Standalone(unittest.TestCase): 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_atari800_config_is_no_record(self): + # The core reads atari800.cfg only under an option that is off. + self.assertFalse(any(r["emulator"] == "atari800" for r in al.extract_launchers(read()))) def test_scummvm_paths_written_under_bios(self): theme, extra = al.scummvm(read()) @@ -319,7 +321,7 @@ class Cores(unittest.TestCase): 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) + self.assertEqual(len(records), 15) class PlatformFile(unittest.TestCase): diff --git a/tests/test_launchers_emudeck.py b/tests/test_launchers_emudeck.py index 1dd106f6c..acd606440 100644 --- a/tests/test_launchers_emudeck.py +++ b/tests/test_launchers_emudeck.py @@ -40,6 +40,9 @@ ARES_SH = text( UserBIOSPath='/home/deck/Emulation/bios/' sed -i "s|$UserBIOSPath|${biosPath}\\/|g" "$ares_configFile" } + ares_init(){ + configEmuFP "${ares_emuName}" "${ares_emuPath}" "true" + } """ ) @@ -87,6 +90,9 @@ MELONDS_SH = text( buttonA='Joy_A=' changeLine "$buttonA" "$buttonA""0" "${melonDS_configFile}" } + melonDS_update(){ + configEmuFP "${melonDS_emuName}" "${melonDS_emuPath}" + } """ ) @@ -103,6 +109,7 @@ MELONDS_TOML = text( LastROMFolder = "/run/media/mmcblk0p1/Emulation/roms/ds" [Emu] ExternalBIOSEnable = false + ConsoleType = 0 [DS] FirmwarePath = "/run/media/mmcblk0p1/Emulation/bios/firmware.bin" BIOS7Path = "/run/media/mmcblk0p1/Emulation/bios/bios7.bin" @@ -206,8 +213,23 @@ SHADPS4_SH = text( ) +CONFIG_EMU_FP = text( + """ + configEmuFP(){ + if [[ $overwrite == 'true' ]]; then + overwrite="--backup --suffix=.bak" + else + overwrite="--ignore-existing" + fi + rsync -avhp --mkpath "$emudeckBackend/configs/${ID}" "$HOME/.var/app/" $overwrite + } +""" +) + + def tree(**scripts: str) -> RepoTree: files = {f"{S}/{name}": body for name, body in scripts.items()} + files[el.CONFIG_EMU_FP] = CONFIG_EMU_FP files[f"{el.CONFIGS}/dev.ares.ares/data/ares/settings.bml"] = ARES_BML files[f"{el.CONFIGS}/net.kuribo64.melonDS/config/melonDS/melonDS.ini"] = MELONDS_INI files[f"{el.CONFIGS}/net.kuribo64.melonDS/config/melonDS/melonDS.toml"] = MELONDS_TOML @@ -253,8 +275,17 @@ class WrittenIntoTheConfig(unittest.TestCase): self.assertEqual( toml["passes_to"], "melonDS.toml [DS] FirmwarePath, BIOS7Path [DSi] NANDPath" ) + # The DS keys wait for ExternalBIOSEnable, the DSi keys for a DSi. self.assertEqual( - toml["depends_on_option"], "ExternalBIOSEnable = false in the shipped melonDS.toml" + toml["depends_on_option"], + "[DS] keys: ExternalBIOSEnable = true, false in the shipped melonDS.toml; " + "[DSi] keys: ConsoleType = 1, 0 in the shipped melonDS.toml", + ) + # configEmuFP's update call passes --ignore-existing. + self.assertIs(toml["kept_if_present"], True) + self.assertEqual( + toml["installs"]["path"], + "${HOME}/.var/app/net.kuribo64.melonDS/config/melonDS/melonDS.toml", ) def test_a_config_the_setters_rewrite_keeps_their_values(self): diff --git a/tests/test_launchers_emuelec.py b/tests/test_launchers_emuelec.py index ae6315c98..630547701 100644 --- a/tests/test_launchers_emuelec.py +++ b/tests/test_launchers_emuelec.py @@ -228,6 +228,9 @@ DUCKSTATION_SH = text( """ CONFIG_DIR="/emuelec/configs/duckstation" duckstation-nogui -fullscreen -settings "${CONFIG_DIR}/settings.ini" -- "${1}" + if [ ! -d "${CONFIG_DIR}" ]; then + cp -rf /usr/config/emuelec/configs/duckstation/* "${CONFIG_DIR}" + fi """ ) @@ -319,6 +322,9 @@ ADVMAME_SH = text( sed -i "s|/roms/arcade|/roms/mame|g" ${CONFIG_DIR}/advmame.rc fi SDL_AUDIODRIVER=alsa advmame ${ARG} -quiet + if [ ! -d "${CONFIG_DIR}" ]; then + cp -rf /usr/config/emuelec/configs/advmame/* "${CONFIG_DIR}/" + fi """ ) @@ -480,24 +486,12 @@ class LibretroRecords(unittest.TestCase): self.assertEqual(jar["passes_to"], "${HOME}/roms/bios") self.assertEqual(jar["depends_on_option"], "/storage/roms/bios/jdk/eeversion exists") - def test_mame_copies_its_own_files_and_reads_only_path_options(self): + def test_mame_copies_its_own_files_and_hands_no_folder(self): + # The core passes no path option and reads no mame.ini: only the + # copies are the launcher's. found = records(el.mame) - copies = [r for r in found if r["kind"] == "copies"] - self.assertEqual([r["names"] for r in copies], [["fm*"], ["mame*"]]) - folders = [(r["passes_to"], r["search_dir"]) for r in found if r["kind"] == "passes_path"] - self.assertEqual( - folders, - [ - ("mame.ini rompath", "/storage/roms/bios/mame/bios"), - ("mame.ini samplepath", "/storage/roms/bios/mame/samples"), - ], - ) - self.assertTrue(all(r["depends_on_option"] == "mame_read_config = enabled" - for r in found if r["kind"] == "passes_path")) - - def test_mame_without_the_read_config_option_raises(self): - with self.assertRaises(LauncherPatternMissing): - records(el.mame, **{f"{el.LIBRETRO}/mame/config/MAME/MAME.opt": "x = 1\n"}) + self.assertEqual([r["kind"] for r in found], ["copies", "copies"]) + self.assertEqual([r["names"] for r in found], [["fm*"], ["mame*"]]) class StandaloneRecords(unittest.TestCase): @@ -546,6 +540,12 @@ class StandaloneRecords(unittest.TestCase): (record,) = records(el.duckstation) self.assertEqual((record["kind"], record["search_dir"]), ("scans_dir", "/storage/roms/bios")) + def test_duckstation_config_is_kept_when_present(self): + (record,) = records(el.duckstation) + self.assertEqual(record["installs"]["path"], "/emuelec/configs/duckstation/settings.ini") + self.assertIs(record["kept_if_present"], True) + self.assertEqual(record["overrides"], []) + def test_duckstation_with_a_fixed_bios_path_raises(self): changed = DUCKSTATION_INI.replace("PathPAL =", "PathPAL = /storage/roms/bios/scph5502.bin") with self.assertRaises(LauncherPatternMissing): @@ -631,7 +631,8 @@ class StandaloneRecords(unittest.TestCase): def test_every_extractor_runs_on_the_fixtures(self): found = el.extract_launchers(tree()) - self.assertEqual(len(found), 28) + # The two mame.ini folders are no longer records. + self.assertEqual(len(found), 26) class Revision(unittest.TestCase): diff --git a/tests/test_launchers_knulli.py b/tests/test_launchers_knulli.py index 2657c8dd5..16500d681 100644 --- a/tests/test_launchers_knulli.py +++ b/tests/test_launchers_knulli.py @@ -598,10 +598,11 @@ class BootDisks(unittest.TestCase): 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", - ) + # The menu's values are where the choices come from, the generator + # writing the choice is the evidence. + self.assertEqual(hard.source_ref, f"{G}/mame/mameGenerator.py:3-4, 9-10") + self.assertEqual(hard.choices_from, f"{kl.ES_FEATURES}:8-10") + self.assertIsNone(floppy.choices_from) def test_a_name_built_another_way_raises(self): broken = MAME.replace('"/userdata/bios/" + system.config["bootdisk"] + ".chd"', "disk") diff --git a/tests/test_launchers_rocknix.py b/tests/test_launchers_rocknix.py index 29d31dce7..730645c63 100644 --- a/tests/test_launchers_rocknix.py +++ b/tests/test_launchers_rocknix.py @@ -85,13 +85,14 @@ MELONDS_SH = text( sed -i '/^ExternalBIOSEnable=/c\\ExternalBIOSEnable=0' /storage/.config/melonDS/melonDS.ini fi fi + CONF_DIR="/storage/.config/melonDS" """ ) def melonds_tree() -> RepoTree: pkg = f"{SA}/melonds-sa" - return RepoTree.from_files( + return rocknix_tree( { **devices("AMD64", "H700", "RK3576", "S922X"), f"{pkg}/package.mk": MELONDS_MK, @@ -148,6 +149,7 @@ AMIBERRY_SH = text( cp -rf /usr/config/amiberry ${AMIBERRY_DIR} fi cd ${AMIBERRY_DIR} + amiberry -f "${AMIBERRY_TMP_CONFIG}" --autoload "$1" >> ${AMIBERRY_LOG} 2>&1 """ ) @@ -291,6 +293,22 @@ FLYCAST_SH = text( ) +USERCONFIG = text( + """ + #!/bin/bash + if [ ! -e "/storage/.configured" ] + then + rsync -a --ignore-existing --exclude={es_features.cfg,es_systems.cfg} /usr/config/* /storage/.config/ >/var/log/configure.log 2>&1 + fi + """ +) + + +def rocknix_tree(files: dict[str, str], links: dict[str, str] | None = None) -> RepoTree: + """A ROCKNIX tree holding the first-boot copy every config record cites.""" + return RepoTree.from_files({rl.USERCONFIG: USERCONFIG, **files}, links) + + def by(records: list[dict], emulator: str) -> list[dict]: return [r for r in records if r["emulator"] == emulator] @@ -302,23 +320,26 @@ class ConfigsPerDevice(unittest.TestCase): empty, named = records self.assertEqual(empty["names"], []) self.assertTrue(empty["passes_to"].endswith("DSiNANDPath left empty")) + # melonDS opens the DSi BIOS and firmware keys only under + # DSiExternalBIOSEnable, which no shipped config sets. self.assertEqual( - named["names"], - [ - "bios9.bin", - "bios7.bin", - "firmware.bin", - "dsi_bios9.bin", - "dsi_bios7.bin", - "dsi_firmware.bin", - "dsi_nand.bin", - ], + named["names"], ["bios9.bin", "bios7.bin", "firmware.bin", "dsi_nand.bin"] ) self.assertEqual(named["search_dir"], "/storage/roms/bios") + self.assertEqual(named["installs"]["path"], "/storage/.config/melonDS/melonDS.ini") + self.assertIs(named["kept_if_present"], True) + self.assertEqual(named["overrides"], []) + + def test_a_config_setting_the_dsi_switch_keeps_the_dsi_keys(self): + tree = melonds_tree() + path = f"{SA}/melonds-sa/config/S922X/melonDS.ini" + tree._texts[path] = tree.read(path) + "DSiExternalBIOSEnable=1\n" + named = rl.melonds(tree)[1].entry() + self.assertIn("dsi_bios9.bin", named["names"]) def test_a_linked_device_directory_reads_its_target(self): named = rl.melonds(melonds_tree())[1].entry() - self.assertIn(f"{SA}/melonds-sa/config/InputPlumber/melonDS.ini:2-8", named["source_ref"]) + self.assertIn(f"{SA}/melonds-sa/config/InputPlumber/melonDS.ini:2-4, 8", named["source_ref"]) self.assertNotIn("config/RK3576", named["source_ref"]) def test_the_external_bios_switch_is_the_option(self): @@ -330,11 +351,14 @@ class ConfigsPerDevice(unittest.TestCase): def test_identical_configs_give_one_record_without_device(self): pkg = f"{SA}/dsperate-sa" - tree = RepoTree.from_files( + tree = rocknix_tree( { **devices("H700", "RK3576", "S922X"), f"{pkg}/package.mk": DSPERATE_MK, - f"{pkg}/scripts/start_dsperate.sh": '${EMUPERF} /usr/bin/dsperate --config "${DSPERATE_INI}" "${ROM}"\n', + f"{pkg}/scripts/start_dsperate.sh": ( + '${EMUPERF} /usr/bin/dsperate --config "${DSPERATE_INI}" "${ROM}"\n' + 'DSPERATE_INI="/storage/.config/dsperate/dsperate.ini"\n' + ), f"{pkg}/config/H700/dsperate.ini": DSPERATE_INI, f"{pkg}/config/InputPlumber/dsperate.ini": DSPERATE_INI, f"{pkg}/config/S922X/dsperate.ini": DSPERATE_INI, @@ -352,10 +376,11 @@ class ConfigsPerDevice(unittest.TestCase): def test_an_absent_key_and_an_empty_one_hand_over_the_same(self): pkg = f"{SA}/duckstation-sa" - tree = RepoTree.from_files( + tree = rocknix_tree( { **devices("AMD64", "S922X"), f"{pkg}/package.mk": DUCKSTATION_MK, + f"{pkg}/scripts/start_duckstation.sh": "#!/bin/bash\n", f"{pkg}/config/AMD64/settings.ini": "[BIOS]\nSearchDirectory = /storage/roms/bios\n", f"{pkg}/config/S922X/settings.ini": text( """ @@ -375,10 +400,11 @@ class ConfigsPerDevice(unittest.TestCase): def test_a_bios_written_into_a_path_key_is_an_unknown_shape(self): pkg = f"{SA}/duckstation-sa" - tree = RepoTree.from_files( + tree = rocknix_tree( { **devices("S922X"), f"{pkg}/package.mk": DUCKSTATION_MK, + f"{pkg}/scripts/start_duckstation.sh": "#!/bin/bash\n", f"{pkg}/config/S922X/settings.ini": "[BIOS]\nSearchDirectory = /storage/roms/bios\nPathNTSCU = scph5501.bin\n", } ) @@ -387,7 +413,7 @@ class ConfigsPerDevice(unittest.TestCase): def test_a_package_without_a_device_copy_raises(self): pkg = f"{SA}/duckstation-sa" - tree = RepoTree.from_files( + tree = rocknix_tree( { **devices("S922X"), f"{pkg}/package.mk": "makeinstall_target() {\n true\n}\n", @@ -402,7 +428,7 @@ class NamesTheConfigsFix(unittest.TestCase): def test_amiberry_machine_follows_the_game_name(self): pkg = f"{SA}/amiberry" uae = "amiberry.rom_path=/storage/.config/amiberry/kickstarts/\nkickstart_rom_file=@BIOS_PATH@/{}\nkickstart_ext_rom_file=\n" - tree = RepoTree.from_files( + tree = rocknix_tree( { f"{pkg}/scripts/start_amiberry.sh": AMIBERRY_SH, f"{pkg}/package.mk": AMIBERRY_MK, @@ -412,9 +438,15 @@ class NamesTheConfigsFix(unittest.TestCase): ) a1200, a500, link, entered, copied = (r.entry() for r in rl.amiberry(tree)) self.assertEqual((a1200["slot"], a1200["names"]), ("A1200", ["kick40068.A1200.rom"])) - self.assertEqual(a1200["depends_on_option"], "game name matches AGA|CD32") + self.assertEqual( + a1200["depends_on_option"], + "game name matches AGA|CD32, content not booted with --autoload, " + "a WHDLoad .lha or .zip (start_amiberry.sh:17)", + ) + self.assertEqual(a1200["installs"]["path"], "${AMIBERRY_TMP_CONFIG}") + self.assertIs(a1200["kept_if_present"], False) self.assertEqual((a500["slot"], a500["names"]), ("A500", ["kick34005.A500.rom"])) - self.assertEqual(a500["depends_on_option"], "game name does not match AGA|CD32") + self.assertTrue(a500["depends_on_option"].startswith("game name does not match AGA|CD32")) self.assertEqual(a500["search_dir"], "/storage/roms/bios") self.assertEqual( a1200["source_ref"], @@ -443,7 +475,7 @@ class NamesTheConfigsFix(unittest.TestCase): def test_mednafen_one_record_per_module(self): pkg = f"{SA}/mednafen" - tree = RepoTree.from_files( + tree = rocknix_tree( { f"{pkg}/config/common/mednafen.template": MEDNAFEN_TEMPLATE, f"{pkg}/scripts/start_mednafen.sh": MEDNAFEN_SH, @@ -461,7 +493,7 @@ class NamesTheConfigsFix(unittest.TestCase): def test_mednafen_without_a_firmware_directory_raises(self): pkg = f"{SA}/mednafen" - tree = RepoTree.from_files( + tree = rocknix_tree( { f"{pkg}/config/common/mednafen.template": "psx.bios_eu scph5502.bin\n", f"{pkg}/scripts/start_mednafen.sh": MEDNAFEN_SH, @@ -473,7 +505,7 @@ class NamesTheConfigsFix(unittest.TestCase): class SearchedNames(unittest.TestCase): def tree(self, script: str) -> RepoTree: - return RepoTree.from_files({f"{SA}/yabasanshiro-sa/scripts/start_yabasanshiro.sh": script}) + return rocknix_tree({f"{SA}/yabasanshiro-sa/scripts/start_yabasanshiro.sh": script}) def test_yabasanshiro_tries_its_names_in_order(self): (record,) = [r.entry() for r in rl.yabasanshiro(self.tree(YABASANSHIRO_SH))] @@ -514,7 +546,7 @@ class Links(unittest.TestCase): def tree(self) -> RepoTree: scripts = self.all_scripts() - return RepoTree.from_files({p: scripts[e] for e, p in rl.LINK_SCRIPTS}) + return rocknix_tree({p: scripts[e] for e, p in rl.LINK_SCRIPTS}) def emulator(self, name: str) -> list[dict]: return by([r.entry() for r in rl.links(self.tree())], name) @@ -566,7 +598,7 @@ class Links(unittest.TestCase): def test_a_script_without_a_bios_link_raises(self): scripts = self.all_scripts() scripts["flycast"] = 'ln -sf "/storage/.config/flycast" "/storage/.local/share/flycast"\n' - tree = RepoTree.from_files({p: scripts[e] for e, p in rl.LINK_SCRIPTS}) + tree = rocknix_tree({p: scripts[e] for e, p in rl.LINK_SCRIPTS}) with self.assertRaises(LauncherPatternMissing): rl.links(tree) @@ -603,7 +635,7 @@ class ProgramsRunOutsideTheBiosFolder(unittest.TestCase): PATH = f"{SA}/pico-8/scripts/start_pico8.sh" def test_pico8_runs_its_binary_from_roms(self): - (record,) = [r.entry() for r in rl.pico8(RepoTree.from_files({self.PATH: START_PICO8}))] + (record,) = [r.entry() for r in rl.pico8(rocknix_tree({self.PATH: START_PICO8}))] self.assertEqual( (record["emulator"], record["kind"], record["names"], record["search_dir"], record["passes_to"]), @@ -615,7 +647,7 @@ class ProgramsRunOutsideTheBiosFolder(unittest.TestCase): def test_a_script_without_its_binary_choice_raises(self): broken = START_PICO8.replace('STATIC_BIN="pico8_dyn"', "STATIC_BIN=$OTHER") with self.assertRaises(LauncherPatternMissing): - rl.pico8(RepoTree.from_files({self.PATH: broken})) + rl.pico8(rocknix_tree({self.PATH: broken})) class Revision(unittest.TestCase): diff --git a/wiki/adding-a-platform.md b/wiki/adding-a-platform.md index 446d020bf..2e559804e 100644 --- a/wiki/adding-a-platform.md +++ b/wiki/adding-a-platform.md @@ -445,6 +445,10 @@ launchers: | `fails_scope` | `emulator` when the failure needs every record of the emulator to come up empty | | `depends_on_option` | Options or conditions that decide whether the record applies | | `device` | Devices whose shipped config the record reads, set only where a package ships configs that differ between devices | +| `installs` | For a record citing a config: `path` where the platform's code puts it and the `source_ref` of that code | +| `kept_if_present` | Whether that install leaves a file already there (a copy only if absent, rsync `--ignore-existing`) | +| `overrides` | Launch code rewriting the record's keys or replacing the config, empty when none does | +| `choices_from` | The feature or option file listing the values the option takes (es_features, a core `.opt`), never the record's evidence | | `source_ref` | `path:lines` in the repository and at the revision `source` pins, which the site links | Batocera's generators are read with `ast`, RetroBat's C# generators as text. @@ -453,18 +457,24 @@ package, so `scripts/scraper/shell_launchers.py` reads assignments, `for` loops, `ln -s` commands and `key = value` lines. A ROCKNIX package can ship one config per device: the configs are grouped by the value read, and `device` appears only when there is more than one group. melonDS writes its -seven BIOS paths on every device but AMD64, where the keys are left empty: +BIOS paths on every device but AMD64, where the keys are left empty: ```yaml - emulator: melonds build: standalone kind: fixed_name names: [] - passes_to: melonDS.ini BIOS9Path, BIOS7Path, FirmwarePath, DSiBIOS9Path, - DSiBIOS7Path, DSiFirmwarePath, DSiNANDPath left empty + passes_to: melonDS.ini BIOS9Path, BIOS7Path, FirmwarePath, DSiNANDPath left empty device: AMD64 ``` +A config file is proof only when the code installing it and the code reading +it are shown. A record citing a config carries `installs` and `overrides`, and +the emulator's profile says, in `config_reads`, where the build it ships loads +that file and reads each key. A record whose chain does not hold is listed as +unproven by `verify.py` and `cross_reference.py`, and no pack or manifest +places a file from it. + An extractor that no longer recognises the shape of the code raises: an empty list would read as a launcher that looks for nothing.