feat: list recalbox cores per board

Recalbox builds seven board images with different emulators. The new
target scraper reads each board's defconfig against the package every
system.ini core names. The names that reached no profile are mapped to
the projects Recalbox builds or profiled from their source, and the
mupen64plus video plugins are set aside.
This commit is contained in:
Abdessamad Derraz committed 2026-10-17 15:51:39 +02:00
1 parent d9c74888e8
commit a8bfadd4c1
22 files changed
+1997 -24

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ core_version: "Git"
display_name: "Nintendo - GameCube / Wii (Dolphin)"
cores:
- dolphin
- "dolphin-gui"
systems: [nintendo-gamecube, nintendo-wii, sega-triforce]
# path: is where the libretro core reads a file, standalone_path: where the
+56
View File
@@ -0,0 +1,56 @@
emulator: EightyOne (Recalbox)
type: libretro
core_classification: community_fork
source: "https://gitlab.com/recalbox/packages/recalbox-cores/libretro-eightyone"
upstream: "https://github.com/charlierobson/EightyOne"
profiled_date: "2026-10-17"
source_commit: "39ea0cc26453f4026458cfff62f10223a6bb1724"
core_version: "v1.2"
display_name: "Sinclair - ZX81 / ZX Spectrum (EightyOne, Recalbox)"
cores: ["eightyone"]
systems:
- sinclair-zx81
- sinclair-zx80
- sinclair-zx-spectrum
- jupiter-ace
- timex-ts1500
- timex-tc2048
- timex-ts2068
- lambda-8300
- ringo-r470
- microdigital-tk85
notes: |
Recalbox port of EightyOne, vendored under 81/ and built with CMake.
Machines: ZX80, ZX81, ZX97LE, TS1000, TS1500, TK85, Lambda 8300,
Ringo R470, Spectrum 16K/48K/+/128K/+2/+2A/+3, TC2048, TC2068, TS2068,
Jupiter ACE.
ref: libretro/libretro_core.cpp:876-1074
The ROM directory is <system>/eightyone/ with ROMs/, FDCs/ and IDEs/
subfolders.
ref: libretro/libretro_core.cpp:2620-2631
86 machine, interface, IDE, graphics and speech ROMs are compiled in as
zlib-compressed arrays and looked up by basename.
ref: libretro/eo_roms.cpp:16-103, libretro/eo_roms.cpp:106-128
memory_load, memoryLoadToAddress and font_load query the embedded table
before opening any path, and every ROM name the core requests is in that
table, so no ROM is read from the system directory.
ref: 81/Source/zx81/snap.cpp:1189-1199, 81/Source/zx81/snap.cpp:1285-1299, 81/Source/zx81/snap.cpp:1317-1328, 81/Source/zx81config.cpp:161-210, 81/Source/zx81config.cpp:259-284
Didaktik D40/D80 tests for eightyone/FDCs/didaktik.gama.rom and stores its
full path in CurRom when present; memory_load reduces the path to its
basename and serves the embedded copy.
ref: libretro/libretro_core.cpp:2953-2968, 81/Source/Spectrum/spec48.cpp:361-362
ZXpand writes zxpand-eeprom.bin (256 bytes of 0xFF) under the ROM
directory when it is absent.
ref: 81/Source/zxpand/smbsd.cpp:75-98
Digitalker is a stub and its SSR ROM loader is not compiled; CapsImg is
linked statically.
ref: libretro/eo_stubs.cpp:67-68, CMakeLists.txt:267-291
files: []
+34
View File
@@ -0,0 +1,34 @@
emulator: Exel-Hence
type: libretro
core_classification: pure_libretro
source: "https://gitlab.com/recalbox/packages/recalbox-cores/libretro-exel-hence"
upstream: "https://gitlab.com/recalbox/packages/recalbox-cores/libretro-exel-hence"
profiled_date: "2026-10-17"
source_commit: "213328332cbfd8121e018493443e223a7a780961"
core_version: "v0.9"
display_name: "Exelvision - EXL 100 (Exel-Hence)"
cores:
- "exel-hence"
systems:
- "exelvision-exl100"
notes: |
Exelvision EXL 100 emulator written as a libretro core by Recalbox.
retro_load_game() asks for the system directory and hands it to
Exl100LoadRoms() (ref: src/libretro.c:710-734), which ignores it: the
main and sub CPU BIOS images are unpacked from arrays compiled into the
core (ref: src/exl100.c:1163-1181). The images are raw DEFLATE streams
XOR'd against a rotating key and inflated at load time
(ref: src/exl100_bios.c:56-101). Both arrays are present in the
shipped header (ref: src/exl100_bios.h:6703-6704). Exelmemoria, the
ExelBASIC and ExelBASIC+ cartridges and the ExelDisk DOS come from the same embedded
tables (ref: src/libretro.c:508, 533, 562).
The only file opened by name is a sidecar next to a .cram content file,
<basename>cram.txt or <basename>.txt, scanned for a CALL EXEC or RUN
line to auto-start the program (ref: src/libretro.c:614-686). It
belongs to the content.
exclusion_note: "BIOS, BASIC cartridges and ExelDisk DOS compiled into the core; the system directory is never read"
files: []
+1
View File
@@ -12,6 +12,7 @@ cores:
- flycast
- flycast_dev
- flycastvl
- "flycast-next"
systems:
- sega-dreamcast
- sega-naomi
+33
View File
@@ -0,0 +1,33 @@
emulator: Frotz
type: standalone
core_classification: game_engine
source: "https://gitlab.com/DavidGriffith/frotz"
upstream: "https://gitlab.com/DavidGriffith/frotz"
profiled_date: "2026-10-17"
source_commit: "2f17d64f23f3d6e68cd676d35c9d1789e17bc9fc"
core_version: "2.55pre"
display_name: "Infocom Z-Machine (SDL Frotz)"
cores:
- "frotz"
systems:
- "zmachine"
notes: |
Z-machine interpreter for Infocom and Inform story files (versions 1
to 8). Recalbox builds the SDL front end, sfrotz, and launches it with
a generated config file and the story file.
sf_initfonts() builds the text and fixed-width fonts from the bitmap
compiled into sf_deffont.c (ref: src/sdl/sf_deffont.c:22) and the
character graphics font from SF_font3 (ref: src/sdl/sf_fonts.c:979-989).
TrueType fonts are loaded only when the config file names them under
[Fonts] (ref: src/sdl/sf_resource.c:402-413), and a font that fails to
load is reported and skipped (ref: src/sdl/sf_fonts.c:959-973). No
default font path exists.
Pictures and sounds come from the story's Blorb data or from loose
resource files under the [Resources] Dir setting, "./" by default
(ref: src/sdl/sf_resource.c:75, 415). Both belong to the game.
exclusion_note: "fonts compiled into the binary; external TrueType fonts only when the user config names them"
files: []
+1
View File
@@ -12,6 +12,7 @@ cores:
- genesisplusgx
- genesisplusgx-expanded
- genplus
- "genesisplusgx_ex"
systems:
- sega-megadrive
- sega-megacd
+1
View File
@@ -10,6 +10,7 @@ display_name: "Arcade (Hypseus Singe)"
cores:
- hypseus-singe
- hypseus
- "hypseus_singe"
systems:
- arcade-laserdisc
mode: standalone
+108
View File
@@ -0,0 +1,108 @@
emulator: Julius
type: standalone
core_classification: game_engine
source: "https://github.com/bvschaik/julius"
upstream: "https://github.com/bvschaik/julius"
profiled_date: "2026-10-17"
source_commit: "33c777019b2640b847b73815c3b4344cf7bd9576"
core_version: "1.7.0"
display_name: "Caesar III (Julius)"
cores:
- "julius"
systems:
- "caesar3"
notes: |
Open-source reimplementation of Impressions Games' Caesar III engine.
It reads every graphic, text, sound and scenario from an installed copy
of the game; nothing is bundled.
The data directory is the command-line argument. pre_init() makes it
the base path and runs game_pre_init(); a missing directory shows
"Julius requires the original files from Caesar 3" and exits
(ref: src/platform/julius.c:488-501, 580-586). File lookups are
case-corrected inside that directory.
Startup loads, in order, the text and message files c3.eng and
c3_mm.eng (c3.rus and c3_mm.rus as the alternative pair)
(ref: src/game/game.c:48-63, src/core/lang.c:141-165), the central
climate graphics c3.sg2 and c3.555, the barbarian enemy graphics
goths.sg2 and goths.555, and c3_model.txt (ref: src/game/game.c:79-104).
Any of them missing aborts init. The_empire.555 is loaded with the
climate graphics; a wrong size is logged and the game continues
(ref: src/core/image.c:283-293, 323). The other climates, enemies,
sounds, videos and scenario maps are opened from the same directory on
demand.
Recalbox lists Caesar III under roms/ports/Caesar3 with c3.exe as the
entry and patches config and save paths into /recalbox/share.
files:
- name: "caesar3/"
type: directory
path: "caesar3/"
system: "caesar3"
category: game_data
required: true
description: "Caesar III install directory"
source_ref: "src/platform/julius.c:488-501"
note: "Given on the command line; every game file is opened by name inside it."
- name: "c3.eng"
path: "caesar3/c3.eng"
system: "caesar3"
category: game_data
required: true
description: "Caesar III interface text"
source_ref: "src/core/lang.c:23, 74-80, 141-165"
note: "Rejected when outside MIN_TEXT_SIZE..MAX_TEXT_SIZE. c3.rus replaces it on a Russian install."
- name: "c3_mm.eng"
path: "caesar3/c3_mm.eng"
system: "caesar3"
category: game_data
required: true
description: "Caesar III message text"
source_ref: "src/core/lang.c:24, 129-150, 160-165"
note: "Loaded as a pair with c3.eng; c3_mm.rus replaces it on a Russian install."
- name: "c3.sg2"
path: "caesar3/c3.sg2"
system: "caesar3"
category: game_data
required: true
description: "Main graphics index, central climate"
source_ref: "src/core/image.c:48-52, 295-306, src/game/game.c:83-86"
- name: "c3.555"
path: "caesar3/c3.555"
system: "caesar3"
category: game_data
required: true
description: "Main graphics bitmaps, central climate"
source_ref: "src/core/image.c:53-57, 314-317, src/game/game.c:83-86"
- name: "goths.sg2"
path: "caesar3/goths.sg2"
system: "caesar3"
category: game_data
required: true
description: "Barbarian enemy graphics index"
source_ref: "src/core/image.c:79, 667-675, src/game/game.c:87-90"
- name: "goths.555"
path: "caesar3/goths.555"
system: "caesar3"
category: game_data
required: true
description: "Barbarian enemy graphics bitmaps"
source_ref: "src/core/image.c:101, 681-684, src/game/game.c:87-90"
- name: "c3_model.txt"
path: "caesar3/c3_model.txt"
system: "caesar3"
category: game_data
required: true
description: "Building cost and desirability model"
source_ref: "src/building/model.c:83-88, src/game/game.c:101-104"
- name: "The_empire.555"
path: "caesar3/The_empire.555"
system: "caesar3"
category: game_data
required: false
description: "Empire map bitmap"
source_ref: "src/core/image.c:68, 283-293"
note: "A short read is logged and the empire map stays blank."
+1
View File
@@ -7,6 +7,7 @@ cores:
- lexaloffle
- "pico-8"
- vox_official
- "pico8"
systems:
- pico8
- voxatron
+1 -1
View File
@@ -12,7 +12,7 @@ profiled_date: "2026-03-25"
source_commit: "6eabf2242d451ee2e6d52b1e3f10efb77143ed05"
core_version: "Git"
display_name: "Sony - PlayStation 2 (LRPS2)"
cores: [lrps2, pcsx2]
cores: [lrps2, pcsx2, "ps2"]
systems: [sony-playstation-2]
bios_directory: "pcsx2/bios/"
+124
View File
@@ -0,0 +1,124 @@
emulator: Mednafen STV
type: libretro
core_classification: community_fork
source: "https://github.com/beudbeud/libretro-stv"
upstream: "https://mednafen.github.io/"
profiled_date: "2026-10-17"
source_commit: "2e42ef0c5d8238c170bcf182e68951c214fc6867"
core_version: "v1.2.1"
display_name: "Sega - ST-V (Mednafen STV)"
cores:
- "mednafen_stv"
systems:
- "sega-stv"
- "sega-saturn"
notes: |
Libretro core by beudbeud built on the Saturn/ST-V module of Mednafen
1.32.1, with an Asia ST-V BIOS setting and a stvbios.zip fallback added
to ss.cpp. Recalbox runs it for ST-V; the loader also accepts Saturn
disc images (cue, toc, ccd, chd, m3u).
retro_init() points filesys.path_firmware at the frontend system
directory and names the BIOS of every region
(ref: libretro/libretro.cpp:615-641). The ST-V BIOS is picked by SMPC
area: Japan, Asia NTSC, Europe PAL, and North America for every other
area (ref: mednafen/ss/ss.cpp:1436-1446, mednafen/ss/smpc.h:32-40).
A Saturn disc takes sega_101.bin for Japan and Asia NTSC and
mpr-17933.bin otherwise (ref: mednafen/ss/ss.cpp:1447-1453).
The loose file is tried first and must be exactly 524288 bytes; when it
is missing or the wrong size, the same name is looked up at the root of
stvbios.zip in the system directory, then without the leading slash
(ref: mednafen/ss/ss.cpp:1455-1491). With ss.bios_sanity on, the
default, a file named like one of the five Saturn BIOS images must
match its SHA-256; the ST-V images are not in that table and only the
size check applies (ref: mednafen/ss/ss.cpp:1493-1540, 2549).
Saturn games that need the KoF 95 or Ultraman ROM cartridge, and the
Action Replay 4M Plus cart type, open their ROM from the firmware
directory under the default names below
(ref: mednafen/ss/ss.cpp:1348-1350, 1404-1412, 2542-2544).
files:
- name: "epr-20091.ic8"
system: "sega-stv"
region: [japan]
required: true
size: 524288
validation: [size]
description: "ST-V BIOS (Japan)"
source_ref: "libretro/libretro.cpp:636, mednafen/ss/ss.cpp:1438-1439, 1462-1468"
- name: "epr-19854.ic8"
system: "sega-stv"
region: [asia-ntsc]
required: true
size: 524288
validation: [size]
description: "ST-V BIOS (Asia/Taiwan)"
source_ref: "libretro/libretro.cpp:637, mednafen/ss/ss.cpp:1440-1441, 1462-1468"
- name: "epr-17952a.ic8"
system: "sega-stv"
region: [north-america, latin-america, brazil, south-korea, asia-pal]
required: true
size: 524288
validation: [size]
description: "ST-V BIOS (North America)"
source_ref: "libretro/libretro.cpp:638, mednafen/ss/ss.cpp:1444-1445, 1462-1468"
note: "Used for every SMPC area other than Japan, Asia NTSC and Europe PAL."
- name: "epr-17954a.ic8"
system: "sega-stv"
region: [europe]
required: true
size: 524288
validation: [size]
description: "ST-V BIOS (Europe)"
source_ref: "libretro/libretro.cpp:639, mednafen/ss/ss.cpp:1442-1443, 1462-1468"
- name: "stvbios.zip"
system: "sega-stv"
category: bios_zip
required: false
description: "ST-V BIOS archive"
source_ref: "mednafen/ss/ss.cpp:1472-1491"
note: "Read only when the loose BIOS file is missing or not 524288 bytes. The member is found by the BIOS file name; its size is not checked."
contents:
- name: "epr-20091.ic8"
- name: "epr-19854.ic8"
- name: "epr-17952a.ic8"
- name: "epr-17954a.ic8"
- name: "sega_101.bin"
system: "sega-saturn"
region: [japan, asia-ntsc]
required: true
size: 524288
sha256: "dcfef4b99605f872b6c3b6d05c045385cdea3d1b702906a0ed930df7bcb7deac"
validation: [size, sha256]
description: "Saturn BIOS (Japan)"
source_ref: "libretro/libretro.cpp:640, mednafen/ss/ss.cpp:1449-1450, 1507, 1520-1521"
- name: "mpr-17933.bin"
system: "sega-saturn"
region: [north-america, latin-america, brazil, south-korea, asia-pal, europe]
required: true
size: 524288
sha256: "96e106f740ab448cf89f0dd49dfbac7fe5391cb6bd6e14ad5e3061c13330266f"
validation: [size, sha256]
description: "Saturn BIOS (North America/Europe)"
source_ref: "libretro/libretro.cpp:641, mednafen/ss/ss.cpp:1451-1452, 1509, 1520-1521"
- name: "mpr-18811-mx.ic1"
system: "sega-saturn"
required: false
description: "The King of Fighters '95 ROM cartridge"
source_ref: "mednafen/ss/ss.cpp:1348, 1404-1412, 2542"
note: "Opened only for the game that needs the cart; that game does not start without it."
- name: "mpr-19367-mx.ic1"
system: "sega-saturn"
required: false
description: "Ultraman ROM cartridge"
source_ref: "mednafen/ss/ss.cpp:1349, 1404-1412, 2543"
note: "Opened only for the game that needs the cart; that game does not start without it."
- name: "satar4mp.bin"
system: "sega-saturn"
required: false
description: "Action Replay 4M Plus firmware"
source_ref: "mednafen/ss/ss.cpp:1350, 1404-1412, 2544"
note: "Opened only when the Action Replay 4M Plus cart type is selected."
+41
View File
@@ -0,0 +1,41 @@
emulator: RB5000
type: standalone + libretro
core_classification: official_port
source:
standalone: "https://gitlab.com/recalbox/packages/standalone/rb5000"
libretro: "https://gitlab.com/recalbox/packages/recalbox-cores/libretro-rb5000"
upstream: "https://gitlab.com/recalbox/packages/standalone/rb5000"
profiled_date: "2026-10-17"
source_commit:
standalone: "8c75a4b48a5222b0332d0508600f8ddc7ac1191c"
libretro: "7464996aa0cb1be77abf495dc901117e1af36e02"
core_version:
standalone: "v1.1"
libretro: "v0.9"
display_name: "Philips - VG 5000 (RB5000)"
cores:
- "rb5000"
systems:
- "philips-vg5k"
notes: |
Philips VG 5000 emulator by Recalbox, a C++ fork of Daniel Coulom's
DCVG5K v2. The standalone build and the libretro core live in
two repositories and share the same Engine.
The 16 KB system ROM is not a file. It is encoded in the
Video::sPixels image table compiled into the binary, one ROM bit per
entry as bit 0 XOR bit 5 (ref: source/Data.cpp:2). The Engine
constructor copies that table into sMirror (ref: source/Engine.cpp:54)
and Z80ReadW() rebuilds each ROM byte from eight table entries the
first time the CPU reads it, caching it in mMemory
(ref: source/Engine.cpp:379-389). The libretro core carries the same
table and decoder.
The libretro core can map a user cartridge file over 0x0000-0x3FFF
when one is configured (ref: source/Engine.cpp:302-316); that file is
content. The on-screen display font is an Ubuntu TTF from the host
system fonts directory (ref: source/Video.cpp:90-92).
exclusion_note: "VG 5000 system ROM is encoded in a compiled-in image table and decoded on first access; no file is read"
files: []
+63
View File
@@ -0,0 +1,63 @@
emulator: Reicast
type: standalone
core_classification: community_fork
source: "https://github.com/reicast/reicast-emulator"
upstream: "https://github.com/reicast/reicast-emulator"
profiled_date: "2026-10-17"
source_commit: "ad61f95dd6b4c6218846a25de6194550311a5d28"
core_version: "Git"
display_name: "Sega - Dreamcast (Reicast)"
cores:
- "reicast"
systems:
- "sega-dreamcast"
notes: |
Dreamcast emulator descended from nullDC. The Linux build is compiled
for one platform; the default is dreamcast, which sets the ROM prefix
dc_, a 2 MB boot ROM, a 128 KB flash and NVR_OPTIONAL 0
(ref: core/types.h:209-217).
LoadRomFiles() tries dc_boot.bin, dc_boot.bin.bin, dc_bios.bin and
dc_bios.bin.bin for the boot ROM, then dc_nvmem.bin, dc_flash_wb.bin,
dc_flash.bin and dc_flash.bin.bin for the flash
(ref: core/hw/holly/sb_mem.cpp:30-51). Each candidate is accepted
when a full chip's worth of bytes can be read from it
(ref: core/hw/flashrom/flashrom.h:39-49, 61-99). No hash is checked.
With NVR_OPTIONAL 0 a missing flash fails LoadRomFiles() the same way
a missing boot ROM does.
When LoadRomFiles() fails, or the reios setting is on, the core starts
its HLE BIOS instead and loads only the flash, if present
(ref: core/nullDC.cpp:183-200, core/hw/holly/sb_mem.cpp:58-64).
Upstream resolves the directory as <data dir>/data/, the data dir
being $XDG_DATA_HOME/reicast or ~/.local/share/reicast, then each
$XDG_DATA_DIRS entry plus /reicast (ref: core/stdclass.cpp:83-100,
core/linux-dist/main.cpp:287-326, 355-379). Recalbox patches
nullDC.cpp to read and write both files in /recalbox/share/bios/dc/,
which is the path given below.
files:
- name: "dc_boot.bin"
path: "dc/dc_boot.bin"
aliases: ["dc_boot.bin.bin", "dc_bios.bin", "dc_bios.bin.bin"]
system: "sega-dreamcast"
required: false
hle_fallback: true
min_size: 2097152
validation: [size]
description: "Dreamcast boot ROM"
source_ref: "core/hw/holly/sb_mem.cpp:20, 30-36, core/types.h:211, 215"
note: "First of the four names that reads 2097152 bytes wins. Missing, the HLE BIOS runs."
- name: "dc_nvmem.bin"
path: "dc/dc_nvmem.bin"
aliases: ["dc_flash_wb.bin", "dc_flash.bin", "dc_flash.bin.bin"]
system: "sega-dreamcast"
required: false
hle_fallback: true
min_size: 131072
validation: [size]
description: "Dreamcast flash memory"
source_ref: "core/hw/holly/sb_mem.cpp:22-23, 37-48, 58-64, core/types.h:212, 217"
note: "Written back as dc_nvmem.bin on exit. Missing, LoadRomFiles() fails even with a boot ROM present and the HLE BIOS runs."
+36
View File
@@ -0,0 +1,36 @@
emulator: Sugarbox2
type: libretro
core_classification: community_fork
source: "https://gitlab.com/recalbox/packages/recalbox-cores/libretro-sugarbox2"
upstream: "https://github.com/Tom1975/SugarboxV2"
profiled_date: "2026-10-17"
source_commit: "6b565a43a25e4dfb69867f7e927a707e15ea460f"
core_version: "v0.9"
display_name: "Amstrad - CPC / GX4000 (Sugarbox2)"
cores:
- "sugarbox2"
systems:
- "amstrad-cpc"
notes: |
Libretro core by Recalbox wrapping Tom1975's SugarboxV2
(CPC 464, 664, 6128, 464+, 6128+ and GX-4000). Recalbox 10.1.1 builds
tag v1.5 from a token-gated copy of this repository; the public
repository carries v0.9 only, which is the revision read here.
Every ROM is compiled into the core as a zlib stream: OS and BASIC for
each model and language, AMSDOS, the Plus cartridge plus_en.cpr, the
CP/M Plus, FutureOS, SymbOS and utility expansion ROMs, 57 blobs in all
(ref: libretro/RomData.h:1-15).
rom_uncompress() inflates them when the machine is built
(ref: libretro/LibretroCore.cpp:1098-1107) and they are handed to the
memory map directly, never through a path
(ref: libretro/LibretroCore.cpp:1173-1174).
The keyboard map files the engine reads are written from embedded data
into the save directory, only when missing: CONF/KeyboardMaps.ini and
Keyboards/101_keyboard_linux
(ref: libretro/LibretroCore.cpp:1477-1502, 2169-2173).
exclusion_note: "all CPC, Plus and GX-4000 ROMs compiled into the core as zlib blobs; keyboard maps written by the core into the save directory"
files: []
+1 -1
View File
@@ -7,7 +7,7 @@ profiled_date: "2026-03-25"
source_commit: "f63b56688f3133a2bb17499db9eebb7ab81df5f5"
core_version: "3.10"
display_name: "Commodore - C64 (VICE x64sc, accurate)"
cores: [vice_x64sc]
cores: [vice_x64sc, "x64sx"]
systems: [commodore-c64]
notes: >
Cycle-accurate C64 emulator using the viciisc VIC-II implementation.
+50
View File
@@ -0,0 +1,50 @@
emulator: VVVVVV
type: standalone
core_classification: game_engine
source: "https://github.com/TerryCavanagh/VVVVVV"
upstream: "https://github.com/TerryCavanagh/VVVVVV"
profiled_date: "2026-10-17"
source_commit: "a0bd2f3da42da2f5249e2a4892e2b48b51b2fd86"
core_version: "v2.5"
display_name: "VVVVVV"
cores:
- "vvvvvv"
systems:
- "vvvvvv"
notes: |
Terry Cavanagh's VVVVVV game, desktop build. The source release holds
the engine, the level data, the language files and the fonts; graphics,
music and sounds are in data.zip, which is not in the repository.
FILESYSTEM_init() mounts data.zip from the -assets argument, or from
the binary's directory when no argument is given, and exits with a
"data.zip missing!" message box when the mount fails
(ref: desktop_version/src/FileSystemUtils.cpp:318-346). Recalbox
passes -assets /recalbox/share/roms/ports/vvvvvv/data.zip. The game
accepts the data.zip of the retail release or of the free Make and
Play Edition (ref: desktop_version/src/FileSystemUtils.cpp:332-335).
The lang/ and fonts/ directories are mounted ahead of data.zip from
the binary's directory (ref: desktop_version/src/FileSystemUtils.cpp:81-177,
312-315); Recalbox installs them from the source tree beside the
binary. gamecontrollerdb.txt is read from the binary's directory when
present (ref: desktop_version/src/FileSystemUtils.cpp:349-353).
files:
- name: "data.zip"
system: "vvvvvv"
category: game_data
path: "data.zip"
required: true
unsourceable: "game data the collection does not hold; a same-named archive of another game must not stand in"
description: "VVVVVV game assets (graphics, music, sounds)"
source_ref: "desktop_version/src/FileSystemUtils.cpp:318-346, desktop_version/src/main.cpp:433-438"
note: "Path given by -assets, otherwise data.zip next to the binary. Retail and Make and Play Edition archives are both accepted; the code checks neither size nor hash."
- name: "gamecontrollerdb.txt"
system: "vvvvvv"
path: "gamecontrollerdb.txt"
required: false
description: "SDL game controller mappings"
source_ref: "desktop_version/src/FileSystemUtils.cpp:349-353"
note: "Read from the binary's directory; a missing file is logged and ignored."
+2 -2
View File
@@ -211,8 +211,8 @@ platforms:
# Bios.cpp:64-68 hashes a .zip with Zip::Md5Composite, never its bytes
zip_digest: composite
schedule: monthly
target_scraper: null
target_source: null
target_scraper: recalbox_targets
target_source: https://gitlab.com/recalbox/recalbox/-/tree/master/configs
install:
detect:
- os: linux
+24
View File
@@ -128,3 +128,27 @@ rocknix:
- wine
amd64:
aliases: [x86_64, pc]
recalbox:
targets:
_default:
# system.ini names a mupen64plus video plugin as the core of the
# mupen64plus emulator; mupenGenerator.py loads it with --gfx
# mupen64plus-video-<core>.so. Plugins, not emulators.
remove_cores:
- glide64mk2 # mupen64plus-video-glide64mk2
- gliden64 # GLideN64 video plugin
- n64_gles2 # mupen64plus-video-gles2n64
- rice # mupen64plus-video-rice
- rice_gles2 # mupen64plus-video-gles2rice
# Recalbox names its boards by defconfig (configs/recalbox-<board>_defconfig).
rpi3:
aliases: [rpi3b]
rpi4_64:
aliases: [rpi4, rpi400]
rpi5_64:
aliases: [rpi5]
rpizero2:
aliases: [rpi-zero2]
x86_64:
aliases: [pc]
File diff suppressed because it is too large. Load diff
+55 -20
View File
@@ -117,18 +117,14 @@ BLOBS_QUERY = (
BLOBS_PER_QUERY = 50
def system_ini_cores(text: str) -> tuple[set[str], set[str]]:
"""The cores one Recalbox system.ini offers, and those run standalone.
def _ini_sections(text: str) -> dict[str, dict[str, str]]:
"""Sections and their key = value lines of one Recalbox system.ini.
Each ``[core.N]`` section (or its board override ``[core.N.<board>]``)
names an ``emulator`` and a ``core``: ``libretro`` with the core's name,
or a standalone emulator. es_bios.xml only names the cores that need a
BIOS, so read alone it left most of Recalbox's cores outside the pack.
Recalbox's .ini files are not strict INI (a value can run on a bare
line), so only sections and key = value lines are read.
"""
import re
# Recalbox's .ini files are not strict INI (a value can run on a bare
# line), so only sections and key = value lines are read.
sections: dict[str, dict[str, str]] = {}
current: dict[str, str] | None = None
for raw in text.splitlines():
@@ -142,25 +138,64 @@ def system_ini_cores(text: str) -> tuple[set[str], set[str]]:
if current is not None and "=" in line:
key, value = line.split("=", 1)
current[key.strip()] = value.strip().strip('"')
return sections
def _core_names(values: dict[str, str]) -> tuple[set[str], set[str]]:
"""(cores, standalone) one [core.N] section names."""
emulator = values.get("emulator", "")
core = values.get("core", "")
if emulator == "libretro":
return ({core} if core else set()), set()
if not emulator:
return set(), set()
names = {emulator} | ({core} if core and core != emulator else set())
return names, set(names)
def system_ini_cores(text: str) -> tuple[set[str], set[str]]:
"""The cores one Recalbox system.ini offers, and those run standalone.
Each ``[core.N]`` section (or its board override ``[core.N.<board>]``)
names an ``emulator`` and a ``core``: ``libretro`` with the core's name,
or a standalone emulator. es_bios.xml only names the cores that need a
BIOS, so read alone it left most of Recalbox's cores outside the pack.
"""
import re
cores: set[str] = set()
standalone: set[str] = set()
for section, values in sections.items():
for section, values in _ini_sections(text).items():
if not re.fullmatch(r"core\.\d+(\.[\w-]+)?", section):
continue
emulator = values.get("emulator", "")
core = values.get("core", "")
if emulator == "libretro":
if core:
cores.add(core)
elif emulator:
cores.add(emulator)
standalone.add(emulator)
if core and core != emulator:
cores.add(core)
standalone.add(core)
found, alone = _core_names(values)
cores |= found
standalone |= alone
return cores, standalone
def board_cores(text: str, board: str, enabled: set[str]) -> set[str]:
"""The cores of one system.ini a board's image builds.
A core is built when the Buildroot package its section names is enabled
in the board's defconfig. ``[core.N.<board>]`` replaces the section on
that board and ``package.<board>`` its package.
"""
import re
sections = _ini_sections(text)
cores: set[str] = set()
for section, values in sections.items():
match = re.fullmatch(r"core\.(\d+)", section)
if not match:
continue
values = sections.get(f"{section}.{board}", values)
package = values.get(f"package.{board}", values.get("package", ""))
if package in enabled:
cores |= _core_names(values)[0]
return cores
class Scraper(BaseScraper):
"""Scraper for Recalbox es_bios.xml."""
@@ -0,0 +1,126 @@
"""Scraper for Recalbox per-board emulator availability.
Sources (recalbox/recalbox on GitLab, at the tag platforms/recalbox.yml names):
- configs/recalbox-<board>_defconfig -- the Buildroot packages each image
enables, one `BR2_PACKAGE_<NAME>=y` line per emulator package
- package/recalbox-romfs2/systems/<system>/system.ini -- the cores of each
system, each `[core.N]` naming the package that builds it, with board
overrides (`package.<board>`, `[core.N.<board>]`)
Recalbox runs on Raspberry Pi 3 to 5, two handhelds and x86_64: without a
target list, a Pi 3 pack carried the firmware of emulators only x86_64 builds.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from datetime import datetime, timezone
import yaml
from . import BaseTargetScraper
PLATFORM_NAME = "recalbox"
CONFIGS_TREE_URL = (
"https://gitlab.com/api/v4/projects/recalbox%2Frecalbox/repository/tree"
"?path=configs&ref={tag}&per_page=100"
)
DEFCONFIG_URL = "https://gitlab.com/recalbox/recalbox/-/raw/{tag}/configs/{name}"
_DEFCONFIG_RE = re.compile(r"^recalbox-(\w+)_defconfig$")
_ENABLED_RE = re.compile(r"^(BR2_PACKAGE_\w+)=y\s*$", re.MULTILINE)
try:
from ..base_scraper import fetch_bytes
from ..recalbox_scraper import SYSTEMS_TREE_URL, Scraper as PlatformScraper, board_cores
except ImportError:
from scraper.base_scraper import fetch_bytes
from scraper.recalbox_scraper import SYSTEMS_TREE_URL, Scraper as PlatformScraper, board_cores
def enabled_packages(defconfig: str) -> set[str]:
"""The Buildroot packages a defconfig turns on."""
return set(_ENABLED_RE.findall(defconfig))
def defconfig_arch(defconfig: str) -> str:
"""The architecture a defconfig builds for, from its Buildroot arch symbol."""
for symbol, arch in (("BR2_x86_64", "x86_64"), ("BR2_aarch64", "aarch64"),
("BR2_arm", "armhf")):
if re.search(rf"^{symbol}=y\s*$", defconfig, re.MULTILINE):
return arch
raise RuntimeError("defconfig names no BR2_x86_64, BR2_aarch64 or BR2_arm")
class Scraper(BaseTargetScraper):
"""Per-board core lists from Recalbox's defconfigs and system.ini files."""
def __init__(self, url: str = "https://gitlab.com/recalbox/recalbox"):
super().__init__(url=url)
def fetch_targets(self) -> dict:
platform = PlatformScraper()
tag = platform.tag
listing = json.loads(fetch_bytes(CONFIGS_TREE_URL.format(tag=tag)))
boards = {
match.group(1): item["name"]
for item in listing
if (match := _DEFCONFIG_RE.match(item.get("name", "")))
}
if not boards:
raise RuntimeError(f"no recalbox-*_defconfig in configs/ at {tag}")
systems: list[str] = []
page = 1
while True:
entries = json.loads(fetch_bytes(SYSTEMS_TREE_URL.format(tag=tag, page=page)))
systems += [e["name"] for e in entries
if e.get("type") == "tree" and not e["name"].startswith(".")]
if len(entries) < 100:
break
page += 1
inis = platform._system_inis(systems)
targets: dict[str, dict] = {}
for board, name in sorted(boards.items()):
defconfig = fetch_bytes(DEFCONFIG_URL.format(tag=tag, name=name)).decode("utf-8")
enabled = enabled_packages(defconfig)
cores: set[str] = set()
for text in inis:
cores |= board_cores(text, board, enabled)
arch = defconfig_arch(defconfig)
targets[board] = {"architecture": arch, "cores": sorted(cores)}
print(f" {board} ({arch}): {len(cores)} cores", file=sys.stderr)
return {
"platform": PLATFORM_NAME,
"source": f"{self.url}/-/tree/{tag}/configs",
"scraped_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"targets": targets,
}
def main() -> None:
parser = argparse.ArgumentParser(description="Scrape Recalbox per-board cores")
parser.add_argument("--dry-run", action="store_true", help="Show target summary")
parser.add_argument("--output", "-o", help="Output YAML file")
args = parser.parse_args()
scraper = Scraper()
data = scraper.fetch_targets()
if args.dry_run:
for name, info in data["targets"].items():
print(f" {name} ({info['architecture']}): {len(info['cores'])} cores")
return
if args.output:
scraper.write_output(data, args.output)
print(f"Written to {args.output}")
return
print(yaml.dump(data, default_flow_style=False, sort_keys=False))
if __name__ == "__main__":
main()
+34
View File
@@ -233,6 +233,40 @@ class RocknixPerDevice(unittest.TestCase):
["ecwolf-lr", "prboom-lr", "vitaquake3-lr"])
class RecalboxPerBoard(unittest.TestCase):
"""Recalbox had no target list: a Raspberry Pi 3 pack carried the
firmware of emulators only its x86_64 image builds."""
SYSTEM_INI = (
"[core.0]\npackage = BR2_PACKAGE_PCSX2\nemulator = \"pcsx2\"\ncore = \"pcsx2\"\n"
"[core.1]\npackage = BR2_PACKAGE_LIBRETRO_PCSX_REARMED\nemulator = \"libretro\"\n"
"core = \"pcsx_rearmed\"\npackage.rpi3 = BR2_PACKAGE_LIBRETRO_PCSX_REARMED_NEON\n"
"[core.2]\npackage = BR2_PACKAGE_LIBRETRO_SWANSTATION\nemulator = \"libretro\"\n"
"core = \"swanstation\"\n"
"[core.2.rpi3]\npackage = BR2_PACKAGE_LIBRETRO_DUCKSTATION\nemulator = \"libretro\"\n"
"core = \"duckstation\"\n"
)
def test_a_board_gets_the_cores_its_defconfig_builds(self):
from scraper.recalbox_scraper import board_cores # noqa: PLC0415
from scraper.targets.recalbox_targets_scraper import ( # noqa: PLC0415
defconfig_arch,
enabled_packages,
)
x86 = enabled_packages("BR2_x86_64=y\nBR2_PACKAGE_PCSX2=y\n"
"BR2_PACKAGE_LIBRETRO_PCSX_REARMED=y\n"
"BR2_PACKAGE_LIBRETRO_SWANSTATION=y\n# BR2_PACKAGE_X is not set\n")
self.assertEqual(board_cores(self.SYSTEM_INI, "x86_64", x86),
{"pcsx2", "pcsx_rearmed", "swanstation"})
rpi3 = enabled_packages("BR2_arm=y\nBR2_PACKAGE_LIBRETRO_PCSX_REARMED_NEON=y\n"
"BR2_PACKAGE_LIBRETRO_DUCKSTATION=y\n")
self.assertEqual(board_cores(self.SYSTEM_INI, "rpi3", rpi3),
{"pcsx_rearmed", "duckstation"})
self.assertEqual(defconfig_arch("BR2_arm=y\n"), "armhf")
self.assertEqual(defconfig_arch("BR2_aarch64=y\n"), "aarch64")
class RetroPieModuleFlags(unittest.TestCase):
"""The RetroPie target list read flags as requirements and libretro alone.