feat: write config_reads into the profiles

Where each shipped build finds the config a platform installs and reads
every key it opens a file from, read at the build the platform ships:
duckstation (EmuELEC, ROCKNIX, RetroBat), xemu, armsx2, hatari, melonDS,
dsperate, NanoBoyAdvance, mednafen (ROCKNIX's sydarn fork) and ares,
gzdoom, xroar, lrps2 and the amiberry builds at their pins. pin_build
names the builds ROCKNIX, EmuELEC and AmberELEC compile, revision the
ones RetroBat and Flathub install unpinned.

tests/test_launcher_evidence.py covers the rule, the extractor helpers,
the invariants and the chains that move placements.
This commit is contained in:
Abdessamad Derraz committed 2026-11-11 11:11:50 +01:00
1 parent f950482567
commit a6716796a9
15 files changed
+743

No files matched your search

+7
View File
@@ -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
+9
View File
@@ -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,
+43
View File
@@ -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
+19
View File
@@ -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
+26
View File
@@ -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
+55
View File
@@ -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)
+11
View File
@@ -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
+39
View File
@@ -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
+7
View File
@@ -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
+55
View File
@@ -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
+52
View File
@@ -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.
+8
View File
@@ -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).
+31
View File
@@ -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
+51
View File
@@ -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).
+330
View File
@@ -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 = <value>":
("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/<value>"}
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 = <value>",
"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()