24 Commits

Author SHA1 Message Date
Abdessamad Derraz
74f48b8881 Add emulator profiles and update docs
Add new emulator profiles (ares, BigPEmu, Eden, Model2, PrimeHack, Supermodel, Suyu, Xenia Canary, Yuzu) under emulators/*.yml with metadata, notes, file lists and exclusion/hle details. Update README counts (emulators/systems/files summary and auto-generated timestamp) and bump generated_at in database.json. Adjust mkdocs.yml navigation counts and add Yuzu entry to the Emulators nav.
2026-03-26 13:22:11 +01:00
Abdessamad Derraz
0a1880f606 fix: filter baseline by platform-scoped cores, include retroarch cores in emudeck targets 2026-03-26 10:20:43 +01:00
Abdessamad Derraz
6402b77374 fix: filter baseline systems by target-available cores 2026-03-26 09:54:28 +01:00
Abdessamad Derraz
a3de47dd88 fix: normalize core names in emudeck and retropie scrapers 2026-03-26 09:44:11 +01:00
Abdessamad Derraz
7e8491fdf7 fix: resolve upstream core names to profile keys via reverse index 2026-03-26 09:42:08 +01:00
Abdessamad Derraz
bccea60f8d fix: add vita via recipe, rewrite retropie scraper via github api 2026-03-26 09:32:22 +01:00
Abdessamad Derraz
16b4dcc270 feat: rewrite retropie scraper using scriptmodules 2026-03-26 09:31:37 +01:00
Abdessamad Derraz
15e8c0eccb feat: add Vita target via libretro-super recipes 2026-03-26 09:31:34 +01:00
Abdessamad Derraz
14eaff73f7 feat: add retroarch target aliases to overrides 2026-03-26 09:25:39 +01:00
Abdessamad Derraz
dfb7d9a25a fix: correct scraper paths and patterns, populate target files 2026-03-26 09:18:39 +01:00
Abdessamad Derraz
03a9fa3276 fix: batocera targets scraper es_systems path and condition parser
- Fix es_systems.yml URL (batocera-emulationstation -> batocera-es-system)
- Replace if/endif block parser with select-if condition parser that
  matches the actual Config.in structure (select PACKAGE if CONDITION)
- Add Kconfig boolean condition checker (handles &&, ||, !, parentheses)
- Add meta-flag expansion (X86_64_ANY, GLES3, ROCKCHIP_GLES3, etc.)
  iterated to fixpoint for chained derivations
- Fix es_systems.yml parser for the actual dict format with requireAnyOf
2026-03-26 09:16:06 +01:00
Abdessamad Derraz
2980100fba feat: add static target files for retrobat, retrodeck, romm 2026-03-26 08:56:00 +01:00
Abdessamad Derraz
6b86c543af feat: add emudeck and retropie target scrapers 2026-03-26 08:55:58 +01:00
Abdessamad Derraz
8e91552b16 feat: add batocera per-board emulator target scraper 2026-03-26 08:55:54 +01:00
Abdessamad Derraz
c6ab8e9c3a feat: add target scraper infra and retroarch buildbot scraper 2026-03-26 08:55:50 +01:00
Abdessamad Derraz
ac66f0b73b feat: add target fields to registry, create targets directory 2026-03-26 08:51:54 +01:00
Abdessamad Derraz
de58f3f28e feat: add --platform and --target to cross_reference.py 2026-03-26 08:48:41 +01:00
Abdessamad Derraz
89054084b7 feat: target-aware fingerprint in group_identical_platforms 2026-03-26 08:48:40 +01:00
Abdessamad Derraz
ea9cd93e83 feat: propagate --target through pipeline.py 2026-03-26 08:48:34 +01:00
Abdessamad Derraz
5ac14079d6 feat: add --target and --list-targets to generate_pack.py 2026-03-26 08:48:31 +01:00
Abdessamad Derraz
1e939f1470 feat: add --target and --list-targets to verify.py 2026-03-26 08:48:29 +01:00
Abdessamad Derraz
1c34790737 feat: propagate target_cores through find_undeclared_files, find_exclusion_notes, verify_platform, _collect_emulator_extras 2026-03-26 08:44:44 +01:00
Abdessamad Derraz
e17d771710 feat: add target_cores filter to resolve_platform_cores
Optional target_cores parameter intersects the resolved core set,
enabling per-target filtering without changing existing call sites.
Includes 2 E2E tests covering intersection and None pass-through.
2026-03-26 08:42:08 +01:00
Abdessamad Derraz
0549b8945e feat: add load_target_config and list_available_targets to common.py
Loads per-platform target YAML files from platforms/targets/,
resolves aliases and applies add_cores/remove_cores from _overrides.yml.
Includes 7 E2E tests covering alias resolution, overrides, and error paths.
2026-03-26 08:41:37 +01:00
32 changed files with 12994 additions and 46 deletions

View File

@@ -2,7 +2,7 @@
Complete BIOS and firmware packs for Batocera, EmuDeck, Lakka, Recalbox, RetroArch, RetroBat, RetroDECK, RetroPie, and RomM.
**6,748** verified files across **322** systems, ready to extract into your emulator's BIOS directory.
**6,748** verified files across **352** systems, ready to extract into your emulator's BIOS directory.
## Download BIOS packs
@@ -26,14 +26,14 @@ BIOS, firmware, and system files for consoles from Atari to PlayStation 3.
Each file is checked against the emulator's source code to match what the code actually loads at runtime.
- **9 platforms** supported with platform-specific verification
- **319 emulators** profiled from source (RetroArch cores + standalone)
- **322 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **328 emulators** profiled from source (RetroArch cores + standalone)
- **352 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **6,748 files** verified with MD5, SHA1, CRC32 checksums
- **5251 MB** total collection size
## Supported systems
NES, SNES, Nintendo 64, GameCube, Wii, Game Boy, Game Boy Advance, Nintendo DS, Nintendo 3DS, Switch, PlayStation, PlayStation 2, PlayStation 3, PSP, PS Vita, Mega Drive, Saturn, Dreamcast, Game Gear, Master System, Neo Geo, Atari 2600, Atari 7800, Atari Lynx, Atari ST, MSX, PC Engine, TurboGrafx-16, ColecoVision, Intellivision, Commodore 64, Amiga, ZX Spectrum, Arcade (MAME), and 288+ more.
NES, SNES, Nintendo 64, GameCube, Wii, Game Boy, Game Boy Advance, Nintendo DS, Nintendo 3DS, Switch, PlayStation, PlayStation 2, PlayStation 3, PSP, PS Vita, Mega Drive, Saturn, Dreamcast, Game Gear, Master System, Neo Geo, Atari 2600, Atari 7800, Atari Lynx, Atari ST, MSX, PC Engine, TurboGrafx-16, ColecoVision, Intellivision, Commodore 64, Amiga, ZX Spectrum, Arcade (MAME), and 318+ more.
Full list with per-file details: **[https://abdess.github.io/retrobios/](https://abdess.github.io/retrobios/)**
@@ -84,7 +84,7 @@ The [documentation site](https://abdess.github.io/retrobios/) provides:
- **Per-emulator profiles** with source code references for every file
- **Per-system pages** showing which emulators and platforms cover each console
- **Gap analysis** identifying missing files and undeclared core requirements
- **Cross-reference** mapping files across 9 platforms and 319 emulators
- **Cross-reference** mapping files across 9 platforms and 328 emulators
## How it works
@@ -110,4 +110,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
This repository provides BIOS files for personal backup and archival purposes.
*Auto-generated on 2026-03-26T06:14:11Z*
*Auto-generated on 2026-03-26T12:17:35Z*

View File

@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-26T06:09:06Z",
"generated_at": "2026-03-26T12:17:17Z",
"total_files": 6748,
"total_size": 5505760050,
"files": {

441
emulators/ares.yml Normal file
View File

@@ -0,0 +1,441 @@
emulator: ares
type: standalone
source: "https://github.com/ares-emulator/ares"
upstream: "https://github.com/ares-emulator/ares"
profiled_date: "2026-03-26"
core_version: "v147"
display_name: "ares"
cores: [ares]
mode: standalone
systems:
- arcade
- atari-2600
- colecovision
- myvision
- famicom
- famicom-disk-system
- game-boy
- game-boy-color
- game-boy-advance
- master-system
- game-gear
- sg-1000
- sc-3000
- mega-drive
- sega-32x
- mega-cd
- mega-cd-32x
- mega-ld
- msx
- msx2
- neo-geo
- neo-geo-pocket
- neo-geo-pocket-color
- nintendo-64
- nintendo-64dd
- pc-engine
- pc-engine-cd
- pc-engine-ld
- supergrafx
- supergrafx-cd
- sony-playstation
- saturn
- super-famicom
- wonderswan
- wonderswan-color
- pocket-challenge-v2
- zx-spectrum
- zx-spectrum-128
notes: |
Multi-system emulator by Near (byuu), successor to higan/bsnes. Focuses on
accuracy and preservation. 38 systems emulated.
Firmware identification uses SHA256 hashes. Filenames are user-defined; ares
scans a configured directory and matches by hash. EmuDeck installs ares as
Flatpak (dev.ares.ares), configures biosPath in settings.bml.
ref: desktop-ui/settings/firmware.cpp:116-136
Firmware path resolution: settings.paths.firmware (user override) or
{userData}/ares/Firmware/ (default). Per-system firmware assigned in
Settings > Firmware dialog or via scan-by-hash.
ref: desktop-ui/settings/settings.hpp:90-102, desktop-ui/desktop-ui.cpp:68-75
Systems with fully embedded firmware (no user files needed):
Game Boy (boot.rom), Game Boy Color (boot.rom), Mega Drive (TMSS),
Sega 32X (TMSS + vector + SH2 boot M/S), Nintendo 64 (PIF NTSC/PAL/SM5 +
CIC 6101/6102/6105/7101), Super Famicom (IPL + all coprocessor firmware:
DSP1-4, CX4, ST010, ST011, ST018, SGB1/2 boot ROMs), WonderSwan (boot.rom),
WonderSwan Color (boot.rom), Pocket Challenge V2 (boot.rom),
ZX Spectrum (BIOS), ZX Spectrum 128 (BIOS + sub ROM).
ref: mia/resource/resource.bml, mia/Firmware/
SFC coprocessor firmware auto-injected if missing from ROM image.
ref: mia/medium/super-famicom.cpp:42-62
Neo Geo BIOS: accepts ZIP archive (neogeo.zip) or direct binary. AES looks
for "neo-epo.bin" inside ZIP, MVS looks for "sp-45.sp1". Both apply byte-swap
after loading.
ref: mia/system/neo-geo-aes.cpp:12-30, mia/system/neo-geo-mvs.cpp:12-30
SFC subsystem ROMs (SuFami Turbo, Satellaview, Super Game Boy): loaded as
game cartridges via CLI args, not through the firmware system. EmuDeck creates
launcher shortcuts that pass these alongside game ROMs.
ref: emuDeckares.sh (EmuDeck repo)
Systems with no firmware: Arcade, Atari 2600, Famicom, MyVision, PC Engine,
SuperGrafx, SG-1000, SC-3000.
files:
# --- ColecoVision ---
- name: colecovision.rom
system: colecovision
required: true
mode: standalone
sha256: "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad"
validation: [sha256]
description: "ColecoVision BIOS"
source_ref: "desktop-ui/emulator/colecovision.cpp:12"
# --- Famicom Disk System ---
- name: disksys.rom
system: famicom-disk-system
required: true
mode: standalone
sha256: "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72"
validation: [sha256]
description: "Famicom Disk System BIOS"
source_ref: "desktop-ui/emulator/famicom-disk-system.cpp:19"
# --- Game Boy Advance ---
- name: gba_bios.bin
system: game-boy-advance
required: true
mode: standalone
sha256: "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570"
validation: [sha256]
description: "GBA BIOS"
source_ref: "desktop-ui/emulator/game-boy-advance.cpp:14"
# --- Game Gear ---
- name: bios.gg
system: game-gear
required: false
mode: standalone
sha256: "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2"
validation: [sha256]
description: "Game Gear BIOS"
source_ref: "desktop-ui/emulator/game-gear.cpp:12, mia/system/game-gear.cpp:8 (//optional)"
# --- Master System ---
- name: bios_U.sms
system: master-system
required: false
mode: standalone
sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50"
validation: [sha256]
description: "Master System BIOS (US)"
source_ref: "desktop-ui/emulator/master-system.cpp:14, mia/system/master-system.cpp:8 (//optional)"
- name: bios_J.sms
system: master-system
required: false
mode: standalone
sha256: "67846e26764bd862f19179294347f7353a4166b62ac4198a5ec32933b7da486e"
validation: [sha256]
description: "Master System BIOS (Japan)"
source_ref: "desktop-ui/emulator/master-system.cpp:15"
- name: bios_E.sms
system: master-system
required: false
mode: standalone
sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50"
validation: [sha256]
description: "Master System BIOS (Europe), same binary as US"
source_ref: "desktop-ui/emulator/master-system.cpp:16"
# --- Mega CD ---
# Also used by Mega CD 32X (desktop-ui/emulator/mega-cd-32x.cpp)
- name: bios_CD_U.bin
system: mega-cd
required: true
mode: standalone
sha256: "fb477cdbf94c84424c2feca4fe40656d85393fe7b7b401911b45ad2eb991258c"
validation: [sha256]
description: "Mega CD / Sega CD BIOS (US)"
source_ref: "desktop-ui/emulator/mega-cd.cpp:17"
- name: bios_CD_J.bin
system: mega-cd
required: true
mode: standalone
sha256: "7133fc2dd2fe5b7d0acd53a5f10f3d00b5d31270239ad20d74ef32393e24af88"
validation: [sha256]
description: "Mega CD BIOS (Japan)"
source_ref: "desktop-ui/emulator/mega-cd.cpp:18"
- name: bios_CD_E.bin
system: mega-cd
required: true
mode: standalone
sha256: "fe608a2a07676a23ab5fd5eee2f53c9e2526d69a28aa16ccd85c0ec42e6933cb"
validation: [sha256]
description: "Mega CD BIOS (Europe)"
source_ref: "desktop-ui/emulator/mega-cd.cpp:19"
# --- Mega LD (LaserActive SEGA PAC) ---
- name: mega_ld_bios_U.bin
system: mega-ld
required: true
mode: standalone
description: "LaserActive SEGA PAC BIOS (US)"
source_ref: "desktop-ui/emulator/mega-ld.cpp:18"
- name: mega_ld_bios_J.bin
system: mega-ld
required: true
mode: standalone
description: "LaserActive SEGA PAC BIOS (Japan)"
source_ref: "desktop-ui/emulator/mega-ld.cpp:19"
# --- MSX ---
- name: MSX.ROM
system: msx
required: true
mode: standalone
sha256: "413a2b601a94b3792e054be2439cc77a1819cceadbfa9542f88d51c7480f2ef0"
validation: [sha256]
description: "MSX BIOS ROM (Japan)"
source_ref: "desktop-ui/emulator/msx.cpp:15"
# --- MSX2 ---
- name: MSX2.ROM
system: msx2
required: true
mode: standalone
sha256: "0c672d86ead61a97f49a583b88b7c1905da120645cd44f0c9f2baf4f4631e0b1"
validation: [sha256]
description: "MSX2 main BIOS ROM (Japan)"
source_ref: "desktop-ui/emulator/msx2.cpp:15"
- name: MSX2EXT.ROM
system: msx2
required: true
mode: standalone
sha256: "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300"
validation: [sha256]
description: "MSX2 sub ROM (Japan)"
source_ref: "desktop-ui/emulator/msx2.cpp:16"
# --- Neo Geo AES ---
- name: neo-epo.bin
system: neo-geo
required: true
mode: standalone
description: "Neo Geo AES BIOS. Accepts neogeo.zip (extracts neo-epo.bin) or direct file. Byte-swapped on load."
source_ref: "desktop-ui/emulator/neo-geo-aes.cpp:14, mia/system/neo-geo-aes.cpp:16"
# --- Neo Geo MVS ---
- name: sp-45.sp1
system: neo-geo
required: true
mode: standalone
description: "Neo Geo MVS BIOS. Accepts neogeo.zip (extracts sp-45.sp1) or direct file. Byte-swapped on load."
source_ref: "desktop-ui/emulator/neo-geo-mvs.cpp:15, mia/system/neo-geo-mvs.cpp:16"
# --- Neo Geo Pocket ---
- name: ngp_bios.rom
system: neo-geo-pocket
required: true
mode: standalone
sha256: "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7"
validation: [sha256]
description: "Neo Geo Pocket BIOS"
source_ref: "desktop-ui/emulator/neo-geo-pocket.cpp:12"
# --- Neo Geo Pocket Color ---
- name: ngpc_bios.rom
system: neo-geo-pocket-color
required: true
mode: standalone
sha256: "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d"
validation: [sha256]
description: "Neo Geo Pocket Color BIOS"
source_ref: "desktop-ui/emulator/neo-geo-pocket-color.cpp:12"
# --- Nintendo 64DD ---
- name: 64dd_ipl_J.bin
system: nintendo-64dd
required: true
mode: standalone
sha256: "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b"
validation: [sha256]
description: "Nintendo 64DD IPL ROM (Japan retail)"
source_ref: "desktop-ui/emulator/nintendo-64dd.cpp:17"
- name: 64dd_ipl_U.bin
system: nintendo-64dd
required: true
mode: standalone
sha256: "e9fec87a45fba02399e88064b9e2f8cf0f2106e351c58279a87f05da5bc984ad"
validation: [sha256]
description: "Nintendo 64DD IPL ROM (US dev kit)"
source_ref: "desktop-ui/emulator/nintendo-64dd.cpp:18"
- name: 64dd_ipl_DEV.bin
system: nintendo-64dd
required: true
mode: standalone
sha256: "9c2962a8b994a29e4cd04b3a6e4ed730a751414655ab6a9799ebf5fc08b79d44"
validation: [sha256]
description: "Nintendo 64DD IPL ROM (development)"
source_ref: "desktop-ui/emulator/nintendo-64dd.cpp:19"
# --- PC Engine CD ---
- name: syscard1.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "afe9f27f91ac918348555b86298b4f984643eafa2773196f2c5441ea84f0c3bb"
validation: [sha256]
description: "PC Engine CD-ROM2 System Card v1.0 (Japan). Also used by PC Engine LD."
source_ref: "desktop-ui/emulator/pc-engine-cd.cpp:15"
- name: syscard3.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce"
validation: [sha256]
description: "Super CD-ROM2 System Card v3.0 (Japan). Also used by SuperGrafx CD as Arcade Card."
source_ref: "desktop-ui/emulator/pc-engine-cd.cpp:16, desktop-ui/emulator/supergrafx-cd.cpp:15"
- name: syscard3u.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "cadac2725711b3c442bcf237b02f5a5210c96f17625c35fa58f009e0ed39e4db"
validation: [sha256]
description: "TurboGrafx-CD Super System Card v3.0 (US)"
source_ref: "desktop-ui/emulator/pc-engine-cd.cpp:17"
- name: games_express.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d"
validation: [sha256]
description: "Games Express CD Card (Japan). Also used by PC Engine LD."
source_ref: "desktop-ui/emulator/pc-engine-cd.cpp:18"
# --- PC Engine LD (LaserActive NEC PAC) ---
- name: pac-n10.pce
system: pc-engine-ld
required: true
mode: standalone
sha256: "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4"
validation: [sha256]
description: "NEC PAC-N10 LaserActive module (US)"
source_ref: "desktop-ui/emulator/pc-engine-ld.cpp:20"
- name: pac-n1.pce
system: pc-engine-ld
required: true
mode: standalone
sha256: "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f"
validation: [sha256]
description: "NEC PAC-N1 LaserActive module (Japan)"
source_ref: "desktop-ui/emulator/pc-engine-ld.cpp:21"
- name: pce-lp1.pce
system: pc-engine-ld
required: true
mode: standalone
sha256: "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0"
validation: [sha256]
description: "NEC PCE-LP1 LaserActive module (Japan)"
source_ref: "desktop-ui/emulator/pc-engine-ld.cpp:22"
# --- PlayStation ---
- name: scph5501.bin
system: sony-playstation
required: true
mode: standalone
sha256: "11052b6499e466bbf0a709b1f9cb6834a9418e66680387912451e971cf8a1fef"
validation: [sha256]
description: "PlayStation BIOS (US)"
source_ref: "desktop-ui/emulator/playstation.cpp:18"
- name: scph5500.bin
system: sony-playstation
required: true
mode: standalone
sha256: "9c0421858e217805f4abe18698afea8d5aa36ff0727eb8484944e00eb5e7eadb"
validation: [sha256]
description: "PlayStation BIOS (Japan)"
source_ref: "desktop-ui/emulator/playstation.cpp:19"
- name: scph5502.bin
system: sony-playstation
required: true
mode: standalone
sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09"
validation: [sha256]
description: "PlayStation BIOS (Europe)"
source_ref: "desktop-ui/emulator/playstation.cpp:20"
# --- Saturn ---
- name: saturn_bios_U.bin
system: saturn
required: true
mode: standalone
description: "Sega Saturn BIOS (US)"
source_ref: "desktop-ui/emulator/saturn.cpp:14"
- name: saturn_bios_J.bin
system: saturn
required: true
mode: standalone
description: "Sega Saturn BIOS (Japan)"
source_ref: "desktop-ui/emulator/saturn.cpp:15"
- name: saturn_bios_E.bin
system: saturn
required: true
mode: standalone
description: "Sega Saturn BIOS (Europe)"
source_ref: "desktop-ui/emulator/saturn.cpp:16"
# --- SFC subsystem ROMs (loaded as game cartridges, not firmware) ---
- name: SGB1.sfc
system: super-famicom
required: false
mode: standalone
category: game_data
description: "Super Game Boy cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
- name: SGB2.sfc
system: super-famicom
required: false
mode: standalone
category: game_data
description: "Super Game Boy 2 cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
- name: STBIOS.bin
system: super-famicom
required: false
mode: standalone
category: game_data
description: "SuFami Turbo adapter BIOS. Loaded via CLI as SFC cartridge alongside SuFami games."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
- name: BS-X.bin
system: super-famicom
required: false
mode: standalone
category: game_data
description: "Satellaview BS-X Town cartridge ROM. Loaded via CLI as SFC cartridge for BS games."
source_ref: "EmuDeck emuDeckares.sh (launcher)"

23
emulators/bigpemu.yml Normal file
View File

@@ -0,0 +1,23 @@
emulator: BigPEmu
type: standalone
source: "https://www.richwhitehouse.com/jaguar/"
upstream: "https://www.richwhitehouse.com/jaguar/"
profiled_date: "2026-03-26"
core_version: "1.12"
display_name: "BigPEmu (Atari Jaguar)"
cores:
- bigpemu
systems:
- atari-jaguar
- atari-jaguarcd
mode: standalone
notes: |
Closed-source Atari Jaguar and Jaguar CD emulator by Rich Whitehouse.
Built-in boot ROM emulation for both cartridge and CD. No external
BIOS or firmware files required. The emulator supports optional
"custom boot ROM images" via the UI (loading mode for specific
homebrew demos), but no fixed file path or file name is expected.
EmuDeck confirms: "No BIOS are required to play BigPEmu."
files: []

55
emulators/eden.yml Normal file
View File

@@ -0,0 +1,55 @@
emulator: Eden
type: standalone
core_classification: community_fork
source: "https://git.eden-emu.dev/eden-emu/eden"
upstream: "https://git.eden-emu.dev/eden-emu/eden"
profiled_date: "2026-03-26"
core_version: "0.1.1"
display_name: "Eden (Nintendo Switch)"
systems: [nintendo-switch]
analysis_date: "2026-03-26"
analysis_commit: "9347202 (depth=1)"
mode: standalone
# Eden is a community fork of yuzu, started by Camille LaVey.
# Key files loaded from <data_dir>/keys/ (fs_paths.h:20, path_util.cpp:158).
# On Linux: $XDG_DATA_HOME/eden/keys/
# On Windows: %APPDATA%/eden/keys/
# Firmware NCAs are installed via UI (Tools > Install Firmware), not placed as files.
# HLE fallbacks exist for fonts, timezone, system version, mii model, ng word lists.
files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:652-653"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:655-656"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:657-658"
- name: "key_retail.bin"
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:281-302"
notes: |
Eden is a standalone Nintendo Switch emulator, community fork of yuzu by Camille LaVey.
dev.keys can be used instead of prod.keys when use_dev_keys is enabled (for Switch dev units).
Firmware (system NCAs) must be installed through Eden's UI from a firmware ZIP or NCA folder.
Required for commercial games. Homebrew (.nro, .nso) can run without keys or firmware.

37
emulators/model2.yml Normal file
View File

@@ -0,0 +1,37 @@
emulator: Model 2 Emulator
type: standalone
source: closed-source
upstream: closed-source
profiled_date: "2026-03-26"
core_version: "1.1a"
display_name: "Model 2 Emulator (Sega Model 2)"
cores:
- model2
systems:
- sega-model2
mode: standalone
notes: |
Closed-source Sega Model 2 arcade emulator by ElSemi, formerly Nebula Model 2.
Emulates Model 2, 2A, 2B, and 2C hardware. Windows-only, runs via Proton on
Linux (EmuDeck setup). Last official release 1.1a (2014-01-02).
Uses MAME-format merged ROM sets. Each game ZIP contains all required ROMs
(program, geometry, texture, sound, protection chips). No shared parent BIOS
set exists for Sega Model 2 hardware; each game board has its own ROM chips.
EmuDeck packages version 1.1c from SeongGino/edc-repo0004. Installs to
romsPath/model2/ with emulator_multicpu.exe as main binary. Ships with
Lua scripts for game-specific patches (widescreen, fixes). No checkBIOS
entry in EmuDeck; EMULATOR.INI references only a roms/ subdirectory.
Source code never published. Original distribution via nebula.emulatronia.com
(archived). PhilrocWP/Model2 GitHub repo no longer exists.
files: []
exclusion_note: >
Uses MAME-format merged ROM sets where all required ROMs are contained within
each game's ZIP file. No standalone BIOS or firmware files are loaded by the
emulator. The Sega Model 2 hardware has no shared system BIOS; each game board
has its own set of program, geometry, texture, and sound ROMs.

329
emulators/primehack.yml Normal file
View File

@@ -0,0 +1,329 @@
emulator: PrimeHack
type: standalone
core_classification: enhanced_fork
source: "https://github.com/shiiion/dolphin"
upstream: "https://github.com/dolphin-emu/dolphin"
profiled_date: "2026-03-26"
core_version: "1.0.8"
display_name: "PrimeHack (Dolphin fork for Metroid Prime)"
systems: [nintendo-gamecube, nintendo-wii]
analysis_date: "2026-03-26"
analysis_commit: "b10f147 (depth=1)"
mode: standalone
# PrimeHack is a Dolphin fork by shiiion adding mouselook controls for Metroid Prime Trilogy.
# BIOS loading code is identical to upstream Dolphin.
# File paths relative to Dolphin User directory:
# Standalone: User/GC/ and User/Wii/
# Sys/ is checked as fallback when not found in User/.
pack_structure:
standalone: ""
data_directories:
- ref: dolphin-sys
destination: "Sys"
source_ref: "Source/Core/Common/CommonPaths.h:128-141"
files:
# -- GameCube IPL (Boot ROM) --
# Region-specific, placed in GC/<region>/IPL.bin
# Checked in User/GC/<region>/ then Sys/GC/<region>/
- name: "IPL.bin"
path: "GC/USA/IPL.bin"
size: 2097152
required: false
mode: standalone
hle_fallback: true
note: "GameCube NTSC-U boot ROM. HLE available but real IPL needed for GC menu boot and accurate fonts"
source_ref: "Source/Core/Common/CommonPaths.h:138, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:109-110"
- name: "IPL.bin"
path: "GC/EUR/IPL.bin"
size: 2097152
required: false
mode: standalone
hle_fallback: true
note: "GameCube PAL boot ROM"
source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:184"
- name: "IPL.bin"
path: "GC/JAP/IPL.bin"
size: 2097152
required: false
mode: standalone
hle_fallback: true
note: "GameCube NTSC-J boot ROM. JAP is the legacy directory name"
source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:186"
# -- DSP ROMs --
# Used by DSP LLE for accurate audio.
# PrimeHack ships free replacement ROMs, but real dumps improve accuracy.
# Searched in: User/GC/ then Sys/GC/
- name: "dsp_rom.bin"
path: "GC/dsp_rom.bin"
size: 8192
required: false
mode: standalone
hle_fallback: true
validation: [size]
known_hash_adler32: "0x66f334fe"
note: "DSP instruction ROM for LLE audio. Free replacement (v0.4) included"
source_ref: "Source/Core/Common/CommonPaths.h:135, Source/Core/Core/HW/DSPLLE/DSPLLE.cpp:87-117"
- name: "dsp_coef.bin"
path: "GC/dsp_coef.bin"
size: 4096
required: false
mode: standalone
hle_fallback: true
validation: [size]
known_hash_adler32: "0xf3b93527"
note: "DSP coefficient ROM for LLE audio. Free replacement included"
source_ref: "Source/Core/Common/CommonPaths.h:136, Source/Core/Core/DSP/DSPCore.cpp:32-38"
# -- GameCube Fonts --
# Bundled free alternatives exist but have padding differences causing misplaced text.
# If IPL dump is present, fonts are extracted from it instead (preferred).
# Searched in: Sys/GC/
- name: "font_western.bin"
path: "GC/font_western.bin"
size: 9589
required: false
mode: standalone
hle_fallback: true
note: "Windows-1252 font for GC/Wii text rendering. Free alternative bundled, real one from IPL dump preferred"
source_ref: "Source/Core/Common/CommonPaths.h:132, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:131,203-218"
- name: "font_japanese.bin"
path: "GC/font_japanese.bin"
size: 303693
required: false
mode: standalone
hle_fallback: true
note: "Shift-JIS font for Japanese text. Free alternative bundled, real one from IPL dump preferred"
source_ref: "Source/Core/Common/CommonPaths.h:133, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:130"
# -- GBA BIOS (for GC-GBA link) --
# Used by integrated mGBA core for GameCube-GBA connectivity.
# Loaded from User/GBA/gba_bios.bin.
- name: "gba_bios.bin"
path: "GBA/gba_bios.bin"
required: false
mode: standalone
hle_fallback: true
note: "GBA BIOS for GC-GBA link feature (uses integrated mGBA). Path configurable in settings"
source_ref: "Source/Core/Common/CommonPaths.h:144, Source/Core/Core/HW/GBACore.cpp:344-361"
# -- Wii System Files --
- name: "SYSCONF"
path: "Wii/shared2/sys/SYSCONF"
required: false
mode: standalone
hle_fallback: true
note: "Wii system configuration. Auto-generated by PrimeHack, can be imported from NAND backup"
source_ref: "Source/Core/Common/CommonPaths.h:117, Source/Core/Core/WiiRoot.cpp:266"
- name: "setting.txt"
path: "Wii/title/00000001/00000002/data/setting.txt"
size: 256
required: false
mode: standalone
hle_fallback: true
note: "Wii region/language settings. Auto-generated during Wii boot emulation"
source_ref: "Source/Core/Common/CommonPaths.h:152, Source/Core/Core/Boot/Boot_BS2Emu.cpp:377-454"
# -- Wii NAND Backup (BootMii) --
- name: "nand.bin"
path: null
required: false
mode: standalone
hle_fallback: true
note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:26-89"
- name: "keys.bin"
path: null
size: 1024
required: false
mode: standalone
hle_fallback: true
note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:19,76-88"
# -- Wii SD Card Image --
- name: "WiiSD.raw"
path: "Load/WiiSD.raw"
required: false
mode: standalone
hle_fallback: true
note: "Virtual SD card image for Wii homebrew. Auto-created, supports SD/SDHC up to 4GB"
source_ref: "Source/Core/Common/CommonPaths.h:149"
# -- Gecko Code Handler --
- name: "codehandler.bin"
path: null
required: false
mode: standalone
hle_fallback: true
note: "Gecko/Ocarina cheat code handler. Shipped with PrimeHack in Sys/"
source_ref: "Source/Core/Common/CommonPaths.h:154, Source/Core/Core/GeckoCode.cpp:121"
# -- Wii System Menu (WAD) --
- name: "Wii System Menu"
path: null
required: false
mode: standalone
hle_fallback: true
note: "Wii System Menu WAD. Installed to NAND via Tools > Install WAD, needed for Wii Menu boot"
source_ref: "Source/Core/DolphinQt/MenuBar.cpp:323,1219-1227"
# -- NAND Certificates (auto-extracted) --
- name: "clientca.pem"
path: "Wii/clientca.pem"
required: false
mode: standalone
hle_fallback: true
note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:201-285"
- name: "clientcakey.pem"
path: "Wii/clientcakey.pem"
required: false
mode: standalone
hle_fallback: true
note: "SSL client private key. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:237"
- name: "rootca.pem"
path: "Wii/rootca.pem"
required: false
mode: standalone
hle_fallback: true
note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:238"
# -- Realtek Bluetooth firmware (Wiimote passthrough) --
# Required for real Wiimote connectivity via USB Bluetooth adapters with Realtek chipsets.
# PrimeHack can auto-download these from gitlab.com/kernel-firmware/linux-firmware.
# Placed in Load/Firmware/rtl_bt/.
# ref: Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:373-416
- name: "rtl8723a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723a_fw.bin"
required: false
mode: standalone
note: "Realtek 8723A BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:379"
- name: "rtl8723b_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723b_fw.bin"
required: false
mode: standalone
note: "Realtek 8723B BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:381"
- name: "rtl8723d_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723d_fw.bin"
required: false
mode: standalone
note: "Realtek 8723D BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:383"
- name: "rtl8761a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8761a_fw.bin"
required: false
mode: standalone
note: "Realtek 8761A BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:385"
- name: "rtl8761bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8761bu_fw.bin"
required: false
mode: standalone
note: "Realtek 8761BU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:387"
- name: "rtl8821a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8821a_fw.bin"
required: false
mode: standalone
note: "Realtek 8821A BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:389"
- name: "rtl8821c_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8821c_fw.bin"
required: false
mode: standalone
note: "Realtek 8821C BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:391"
- name: "rtl8822b_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8822b_fw.bin"
required: false
mode: standalone
note: "Realtek 8822B BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:395"
- name: "rtl8822cu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8822cu_fw.bin"
required: false
mode: standalone
note: "Realtek 8822CU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:393"
- name: "rtl8851bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8851bu_fw.bin"
required: false
mode: standalone
note: "Realtek 8851BU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:397"
- name: "rtl8852au_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852au_fw.bin"
required: false
mode: standalone
note: "Realtek 8852AU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:399"
- name: "rtl8852bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852bu_fw.bin"
required: false
mode: standalone
note: "Realtek 8852BU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:401"
- name: "rtl8852cu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852cu_fw.bin"
required: false
mode: standalone
note: "Realtek 8852CU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:403"
- name: "rtl8852btu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852btu_fw.bin"
required: false
mode: standalone
note: "Realtek 8852BT/8852BE-VT BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:405"
- name: "rtl8922au_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8922au_fw.bin"
required: false
mode: standalone
note: "Realtek 8922AU BT firmware for Wiimote passthrough"
source_ref: "RealtekFirmwareLoader.cpp:407"
notes:
hle_available: true
hle_note: >
PrimeHack provides HLE for GameCube IPL (boot ROM), DSP, and Wii system functions
(same as Dolphin). No BIOS files are strictly required for most games.
ipl_regions: ["USA", "EUR", "JAP", "JPN", "DEV"]
dsp_rom_note: >
DSP ROMs are verified at load time via Adler32 hash.
Official Nintendo hashes: irom=0x66f334fe, drom=0xf3b93527.
bt_passthrough_note: >
Bluetooth passthrough allows connecting real Wiimotes via USB Bluetooth adapters.
Realtek chipsets require firmware files in Load/Firmware/rtl_bt/.
PrimeHack can auto-download these from kernel-firmware/linux-firmware on GitLab.

36
emulators/supermodel.yml Normal file
View File

@@ -0,0 +1,36 @@
emulator: Supermodel
type: standalone
source: https://github.com/trzy/Supermodel
upstream: https://github.com/trzy/Supermodel
profiled_date: "2026-03-26"
core_version: "0.3a"
display_name: "Supermodel (Sega Model 3)"
cores:
- supermodel
systems:
- sega-model3
mode: standalone
notes: |
Open-source Sega Model 3 arcade emulator by Bart Trzynadlowski (trzy) and
contributors, under GPL-3.0. Emulates all three Model 3 steppings (1.0, 1.5,
2.0/2.1) including Real3D graphics, SCSP sound, DSB1/DSB2 MPEG music boards,
drive boards, and net board. Cross-platform (Windows, Linux, macOS).
Uses MAME-compatible ROM sets loaded from ZIP archives. GameLoader identifies
games by CRC32 checksums defined in Config/Games.xml. Parent/child ROM set
relationships exist between regional variants of the same game, not as shared
BIOS sets. No standalone BIOS or firmware files are loaded outside of game ZIPs.
EmuDeck installs the Flatpak package (com.supermodel3.Supermodel). Config
stored in ~/.supermodel/Config/. No checkBIOS entry in EmuDeck.
files: []
exclusion_note: >
The Sega Model 3 arcade hardware has no shared system BIOS. Each game board
contains its own program ROMs (CROM), video ROMs (VROM), sound program, sound
samples, and optional DSB/drive board ROMs, all contained within per-game
MAME-format ZIP archives. Supermodel loads all required data from these game
ZIPs via CRC32-based identification defined in Games.xml. No standalone BIOS
or firmware files are referenced by the emulator source code.

57
emulators/suyu.yml Normal file
View File

@@ -0,0 +1,57 @@
emulator: Suyu
type: standalone
core_classification: community_fork
source: "https://github.com/suyu-emu/suyu"
upstream: "https://github.com/suyu-emu/suyu"
profiled_date: "2026-03-26"
core_version: "Yuzu EA 4176 based"
display_name: "Suyu (Nintendo Switch)"
systems: [nintendo-switch]
analysis_date: "2026-03-26"
analysis_commit: "c096d16 (Hengle/suyu-emu mirror, depth=1)"
mode: standalone
# Suyu is a community fork of yuzu, started after yuzu's legal takedown.
# Key files loaded from <data_dir>/keys/ (fs_paths.h:20, path_util.cpp:124).
# On Linux: $XDG_DATA_HOME/suyu/keys/
# On Windows: %APPDATA%/suyu/keys/
# Firmware NCAs are installed via UI, not placed as files.
# HLE fallbacks exist for fonts, timezone, system version, mii model, ng word lists.
# Original repos (GitHub + GitLab) are DMCA'd. Analysis from community mirror.
files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:654"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:657"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:658"
- name: "key_retail.bin"
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294"
notes: |
Suyu is a standalone Nintendo Switch emulator, community fork of yuzu after legal takedown.
dev.keys can be used instead of prod.keys when use_dev_keys is enabled (for Switch dev units).
Firmware (system NCAs) must be installed through Suyu's UI from a firmware ZIP or NCA folder.
Required for commercial games. Homebrew (.nro, .nso) can run without keys or firmware.
Original repos DMCA'd by Nintendo. Analysis performed from community mirror (Hengle/suyu-emu).

38
emulators/xenia.yml Normal file
View File

@@ -0,0 +1,38 @@
emulator: Xenia Canary
type: standalone
core_classification: community_fork
source: "https://github.com/xenia-canary/xenia-canary"
upstream: "https://github.com/xenia-canary/xenia-canary"
profiled_date: "2026-03-26"
core_version: "canary"
display_name: "Xenia Canary (Xbox 360)"
cores:
- xenia
systems:
- microsoft-xbox-360
analysis_date: "2026-03-26"
analysis_commit: "3efc88a (depth=1)"
mode: standalone
notes: |
Xenia Canary is an experimental open-source Xbox 360 emulator (BSD license)
originally by Ben Vanik, now maintained by the xenia-canary community. Pure
HLE: the Xbox 360 kernel (xboxkrnl), XAM, and XBDM modules are fully
reimplemented in C++. XConfig system settings are hardcoded with user-
configurable overrides (language, region, audio, video). No BIOS, firmware,
NAND dump, flash dump, or keyvault file is loaded at any point.
EmuDeck installs xenia_canary.exe into the xbox360 ROMs folder. Config via
xenia-canary.config.toml. Game patches downloaded separately into patches/.
files: []
exclusion_note: >
Xenia is a pure high-level emulation (HLE) Xbox 360 emulator. The entire
Xbox 360 operating system (kernel, XAM dashboard services, XBDM debug
manager) is reimplemented in C++ as HLE modules loaded at startup
(emulator.cc:316-318). System configuration (XConfig) returns hardcoded
values from xboxkrnl_xconfig.cc. Memory, CPU (JIT), GPU, APU, and HID
subsystems are all software-emulated. No external BIOS, firmware, NAND
flash dump, or system files are loaded from disk. The wiki confirms:
"Xenia doesn't require any Xbox 360 system files."

59
emulators/yuzu.yml Normal file
View File

@@ -0,0 +1,59 @@
emulator: Yuzu
type: standalone
core_classification: official_port
source: "https://github.com/yuzu-emu-mirror/yuzu"
upstream: "https://github.com/yuzu-emu/yuzu"
profiled_date: "2026-03-26"
core_version: "EA 4176"
display_name: "Yuzu (Nintendo Switch)"
systems: [nintendo-switch]
analysis_date: "2026-03-26"
analysis_commit: "15e6e48 (yuzu-emu-mirror, depth=1)"
mode: standalone
# Yuzu is the original Nintendo Switch emulator by bunnei and the yuzu team.
# Legally shut down by Nintendo in March 2024. Citron, Suyu, Eden are community forks.
# Key files loaded from <data_dir>/keys/ (fs_paths.h:18, path_util.cpp:124).
# On Linux: $XDG_DATA_HOME/yuzu/keys/
# On Windows: %APPDATA%/yuzu/keys/
# Firmware NCAs are installed via UI (main.cpp:4160), not placed as files.
# HLE fallbacks exist for fonts, timezone, system version, mii model, ng word lists.
# Original repo DMCA'd by Nintendo. Analysis from community mirror (yuzu-emu-mirror).
files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:655-656"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:659-660"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:661-662"
- name: "key_retail.bin"
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294"
notes: |
Yuzu is the original standalone Nintendo Switch emulator, created by bunnei (author of Citra).
Legally shut down by Nintendo in March 2024 via lawsuit settlement. Permanently frozen.
dev.keys can be used instead of prod.keys when use_dev_keys is enabled (for Switch dev units).
Firmware (system NCAs) must be installed through Yuzu's UI from a firmware directory.
Required for commercial games. Homebrew (.nro, .nso) can run without keys or firmware.
Community forks: Citron, Suyu, Eden. Original GitHub repos DMCA'd.

View File

@@ -128,7 +128,7 @@ nav:
- xrick: systems/xrick.md
- Emulators:
- Overview: emulators/index.md
- Official ports (60):
- Official ports (61):
- amiarcadia: emulators/amiarcadia.md
- Amiberry: emulators/amiberry.md
- Ardens: emulators/ardens.md
@@ -189,7 +189,8 @@ nav:
- Vircon32: emulators/vircon32.md
- vitaQuakeII: emulators/vitaquake2.md
- yabasanshiro: emulators/yabasanshiro.md
- Community forks (105):
- Yuzu: emulators/yuzu.md
- Community forks (108):
- EightyOne: emulators/81.md
- a5200: emulators/a5200.md
- Anarch: emulators/anarch.md
@@ -220,6 +221,7 @@ nav:
- DOSBox-core: emulators/dosbox_core.md
- DOSBox-SVN: emulators/dosbox_svn.md
- DOSBox-SVN CE: emulators/dosbox_svn_ce.md
- Eden: emulators/eden.md
- EmuSCV: emulators/emuscv.md
- ep128emu_core: emulators/ep128emu_core.md
- FCEUmm: emulators/fceumm.md
@@ -269,6 +271,7 @@ nav:
- RetroDream: emulators/retrodream.md
- SAME CDi: emulators/same_cdi.md
- SimCoupe: emulators/simcp.md
- Suyu: emulators/suyu.md
- swanstation: emulators/swanstation.md
- Syobon Action: emulators/syobonaction.md
- TamaLIBretro: emulators/tamalibretro.md
@@ -293,6 +296,7 @@ nav:
- vitavoyager: emulators/vitavoyager.md
- X Millennium: emulators/x1.md
- x64sdl: emulators/x64sdl.md
- Xenia Canary: emulators/xenia.md
- yabause: emulators/yabause.md
- ymir: emulators/ymir.md
- Pure libretro (29):
@@ -351,7 +355,7 @@ nav:
- WASM-4: emulators/wasm4.md
- XRick: emulators/xrick.md
- Zelda Classic v2.10: emulators/zc210.md
- Enhanced forks (12):
- Enhanced forks (13):
- bsnes-hd beta: emulators/bsnes_hd_beta.md
- bsnes-mercury: emulators/bsnes_mercury.md
- DOSBox Pure: emulators/dosbox_pure.md
@@ -363,6 +367,7 @@ nav:
- MAME 2003-Plus: emulators/mame2003_plus.md
- Mupen64Plus-Next: emulators/mupen64plus_next.md
- NP2kai: emulators/np2kai.md
- PrimeHack: emulators/primehack.md
- SMS Plus GX: emulators/smsplus.md
- Frozen snapshots (32):
- bnes: emulators/bnes.md
@@ -401,8 +406,10 @@ nav:
- PCSX-ReARMed: emulators/pcsx_rearmed.md
- Launchers (1):
- Dolphin Launcher: emulators/dolphin_launcher.md
- Other (21):
- Other (25):
- ares: emulators/ares.md
- Beetle GBA (Mednafen): emulators/beetle_gba.md
- BigPEmu: emulators/bigpemu.md
- Cemu: emulators/cemu.md
- Clock Signal (CLK): emulators/clk.md
- Demul: emulators/demul.md
@@ -410,12 +417,14 @@ nav:
- ep128emu-core: emulators/ep128emu.md
- GSplus: emulators/gsplus.md
- Lexaloffle: emulators/lexaloffle.md
- Model 2 Emulator: emulators/model2.md
- openMSX: emulators/openmsx.md
- PCSX2: emulators/pcsx2.md
- Redream: emulators/redream.md
- RPCS3: emulators/rpcs3.md
- Ryujinx: emulators/ryujinx.md
- shadps4: emulators/shadps4.md
- Supermodel: emulators/supermodel.md
- tsugaru: emulators/tsugaru.md
- VBA-M: emulators/vba_m.md
- VICE: emulators/vice.md

View File

@@ -17,6 +17,8 @@ platforms:
hash_type: sha1
schedule: weekly
cores: all_libretro
target_scraper: retroarch_targets
target_source: "https://buildbot.libretro.com/nightly/"
batocera:
config: batocera.yml
@@ -28,6 +30,8 @@ platforms:
hash_type: md5
schedule: weekly
cores: [81, a5200, abuse, arduous, atari800, azahar, bennugd, bk, bluemsx, bsnes, bstone, cannonball, cap32, catacombgl, cdogs, cemu, cgenius, citron, clk, corsixth, demul, devilutionx, dhewm3, dice, dolphin, dosbox_pure, dxx-rebirth, easyrpg, ecwolf, eduke32, eka2l1, emuscv, etlegacy, fake08, fallout1-ce, fallout2-ce, fbneo, fceumm, flatpak, flycast, freechaf, freeintv, fury, fuse, gambatte, gearsystem, genesisplusgx, glide64mk2, gong, gsplus, gw, gzdoom, hatari, hcl, hurrican, hypseus-singe, ikemen, ioquake3, iortcw, jazz2-native, lindbergh-loader, lowresnx, lutro, mame, mame078plus, mednafen_lynx, mednafen_ngp, mednafen_supergrafx, mednafen_wswan, melonds, mgba, minivmac, model2emu, moonlight, mrboom, neocd, np2kai, nxengine, o2em, odcommander, openbor6412, openjazz, openjk, openjkdf2, openmohaa, opera, pce_fast, pcfx, pcsx2, pcsx_rearmed, pd777, picodrive, play, pokemini, potator, ppsspp, prboom, prosystem, puae, px68k, pygame, pyxel, quasi88, raze, reminiscence, rpcs3, ruffle, samcoupe, sameduck, scummvm, sdlpop, sh, shadps4, snes9x, solarus, sonic2013, sonic3-air, sonic-mania, steam, stella, superbroswar, supermodel, taradino, tgbdual, theforceengine, theodore, thextech, tic80, tr1x, tr2x, tsugaru, tyrian, tyrquake, uqm, uzem, vb, vecx, vice_x64, vircon32, virtualjaguar, vita3k, vox_official, vpinball, wasm4, wine-tkg, x1, x128, x16emu, xash3d_fwgs, xemu, xenia-canary, xpet, xplus4, xrick, xvic, yabasanshiro, yquake2, zc210]
target_scraper: batocera_targets
target_source: "https://github.com/batocera-linux/batocera.linux"
recalbox:
config: recalbox.yml
@@ -38,6 +42,8 @@ platforms:
source_format: xml
hash_type: md5
schedule: monthly
target_scraper: null
target_source: null
cores: ["2048", 81, a5200, advancemame, amiberry, applewin, arduous, atari800, b2, beebem, bk, bluemsx, boom3, bsnes, bsneshd, cannonball, cap32, cdi2015, corsixth, craft, crocods, daphne, desmume, dice, dinothawr, dirksimple, dolphin, dolphin-gui, dosbox, dosbox_pure, duckstation, easyrpg, ecwolf, emuscv, fake08, fba2x, fbneo, fceumm, flycast, flycast-next, fmsx, freechaf, freeintv, frotz, fuse, gambatte, gearcoleco, geargrafx, gearsystem, genesisplusgx, genesisplusgx_ex, genesisplusgxwide, geolith, glide64mk2, gliden64, gliden64_20, gong, gpsp, gsplus, gw, handy, hatari, hatarib, holani, imageviewer, julius, kronos, lowresnx, lutro, mame0258, mame0278, mame2000, mame2003, mame2003_plus, mame2010, mame2015, mame2016, mednafen_lynx, mednafen_ngp, mednafen_pce_fast, mednafen_pcfx, mednafen_psx, mednafen_psx_hw, mednafen_saturn, mednafen_supafaust, mednafen_supergrafx, mednafen_vb, mednafen_wswan, melonds, mesen, mesen_s, meteor, mgba, minivmac, mojozork, moonlight, mrboom, mu, mupen64plus, mupen64plus_next, n64_gles2, neocd, nestopia, np2kai, nxengine, o2em, openbor, openlara, opera, oricutron, parallel_n64, pcsx2, pcsx_rearmed, pico8, picodrive, pisnes, pokemini, potator, ppsspp, prboom, prosystem, ps2, puae, px68k, quasi88, quicknes, race, rb5000, reicast, reminiscence, retro8, retrodream, rice, rice_gles2, sameboy, same_cdi, sameduck, scummvm, sdlpop, simcoupe, snes9x, snes9x2002, snes9x2005, snes9x2010, solarus, stella, stella2014, stonesoup, supermodel, swanstation, tamalibretro, tgbdual, theodore, thepowdertoy, ti99sim, tic80, tyrquake, uae4all, uae4arm, uzem, vecx, vice_x128, vice_x64, vice_x64sc, vice_xcbm2, vice_xcbm5x0, vice_xpet, vice_xplus4, vice_xscpu64, vice_xvic, virtualjaguar, vitaquake2, vitaquake3, vitavoyager, vpinball, vvvvvv, wasm4, x1, x128, x64, x64sx, xcbm2, xcbm5x0, xemu, xpet, xplus4, xrick, xroar, xscpu64, xvic, yabasanshiro, yabause]
retrobat:
@@ -50,6 +56,8 @@ platforms:
hash_type: md5
schedule: weekly
cores: [81, a5200, abuse, arduous, atari800, azahar, bennugd, bk, bluemsx, bsnes, bstone, cannonball, cap32, catacombgl, cdogs, cemu, cgenius, citron, clk, corsixth, demul, devilutionx, dhewm3, dice, dolphin, dosbox_pure, dxx-rebirth, easyrpg, ecwolf, eduke32, eka2l1, emuscv, etlegacy, fake08, fallout1-ce, fallout2-ce, fbneo, fceumm, flatpak, flycast, freechaf, freeintv, fury, fuse, gambatte, gearsystem, genesisplusgx, glide64mk2, gong, gsplus, gw, gzdoom, hatari, hcl, hurrican, hypseus-singe, ikemen, ioquake3, iortcw, jazz2-native, lindbergh-loader, lowresnx, lutro, mame, mame078plus, mednafen_lynx, mednafen_ngp, mednafen_supergrafx, mednafen_wswan, melonds, mgba, minivmac, model2emu, moonlight, mrboom, neocd, np2kai, nxengine, o2em, odcommander, openbor6412, openjazz, openjk, openjkdf2, openmohaa, opera, pce_fast, pcfx, pcsx2, pcsx_rearmed, pd777, picodrive, play, pokemini, potator, ppsspp, prboom, prosystem, puae, px68k, pygame, pyxel, quasi88, raze, reminiscence, rpcs3, ruffle, samcoupe, sameduck, scummvm, sdlpop, sh, shadps4, snes9x, solarus, sonic2013, sonic3-air, sonic-mania, steam, stella, superbroswar, supermodel, taradino, tgbdual, theforceengine, theodore, thextech, tic80, tr1x, tr2x, tsugaru, tyrian, tyrquake, uqm, uzem, vb, vecx, vice_x64, vircon32, virtualjaguar, vita3k, vox_official, vpinball, wasm4, wine-tkg, x1, x128, x16emu, xash3d_fwgs, xemu, xenia-canary, xpet, xplus4, xrick, xvic, yabasanshiro, yquake2, zc210]
target_scraper: null
target_source: null
emudeck:
config: emudeck.yml
@@ -61,6 +69,8 @@ platforms:
source_format: bash_script+csv
hash_type: md5
schedule: weekly
target_scraper: emudeck_targets
target_source: "https://github.com/dragoonDorise/EmuDeck"
# dragoonDorise/EmuDeck = official repo (creator's account, 3.4k stars)
# EmuDeck/emudeck.github.io = official wiki (org account)
@@ -72,6 +82,8 @@ platforms:
inherits_from: retroarch
cores: all_libretro
schedule: weekly
target_scraper: lakka_targets
target_source: "https://buildbot.libretro.com/nightly/"
retrodeck:
config: retrodeck.yml
@@ -83,6 +95,8 @@ platforms:
hash_type: md5
schedule: monthly
cores: [azahar, cemu, dolphin, duckstation, gzdoom, mame, melonds, openbor, pcsx2, pico-8, ppsspp, primehack, retroarch, rpcs3, ruffle, solarus, vita3k, xemu, xroar]
target_scraper: null
target_source: null
# Each component/<name>/component_manifest.json declares BIOS requirements
# Scraper enumerates top-level dirs via GitHub API, fetches each manifest directly
@@ -96,6 +110,8 @@ platforms:
hash_type: sha1
schedule: monthly
inherits_from: emulatorjs # cores inherited from emulatorjs.yml
target_scraper: null
target_source: null
retropie:
config: retropie.yml
@@ -104,3 +120,5 @@ platforms:
scraper: null
cores: all_libretro
schedule: null
target_scraper: retropie_targets
target_source: "https://retropie.org.uk/stats/pkgflags/"

View File

@@ -0,0 +1,22 @@
# Manual target overrides.
# This file is NEVER overwritten by scrapers.
# Use it to add aliases, add/remove cores per target.
#
# Format:
# platform_name:
# targets:
# target-name:
# aliases: [alias1, alias2]
# add_cores: [core_to_add]
# remove_cores: [core_to_remove]
retroarch:
targets:
nintendo-switch:
aliases: [switch, nx]
linux-x86_64:
aliases: [pc, linux, x86_64, desktop]
playstation-ps2:
aliases: [ps2]
playstation-psp:
aliases: [psp]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,461 @@
platform: emudeck
source: https://github.com/dragoonDorise/EmuDeck
scraped_at: '2026-03-26T09:14:02Z'
targets:
steamos:
architecture: x86_64
cores:
- '2048'
- 3dengine
- '81'
- DoubleCherryGB
- a5200
- amiarcadia
- anarch
- applewin
- ardens
- arduous
- ares
- atari800
- azahar
- b2
- bigpemu
- bk
- blastem
- bluemsx
- boom3
- boom3_xp
- bsnes
- bsnes-jg
- bsnes2014_accuracy
- bsnes2014_balanced
- bsnes2014_performance
- bsnes_cplusplus98
- bsnes_hd_beta
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- cannonball
- cap32
- cdi2015
- cemu
- chailove
- citra
- citra2018
- citron
- clownmdemu
- craft
- crocods
- desmume
- desmume2015
- dice
- dinothawr
- dirksimple
- dolphin
- dosbox_core
- dosbox_pure
- dosbox_svn
- doukutsu_rs
- duckstation
- easyrpg
- ecwolf
- eden
- ep128emu_core
- fbalpha
- fbalpha2012
- fbalpha2012_cps1
- fbalpha2012_cps2
- fbalpha2012_cps3
- fbalpha2012_neogeo
- fbneo
- fceumm
- fixgb
- fixnes
- flycast
- fmsx
- freechaf
- freeintv
- frodo
- fuse
- galaksija
- gam4980
- gambatte
- gearboy
- gearcoleco
- geargrafx
- gearlynx
- gearsystem
- genesis_plus_gx
- genesis_plus_gx_wide
- geolith
- gme
- gong
- gpsp
- gw
- handy
- hatari
- holani
- jaxe
- jollycv
- jumpnbump
- kronos
- lowresnx
- lutro
- m2000
- mame
- mame2000
- mame2003
- mame2003_midway
- mame2003_plus
- mame2010
- mcsoftserve
- mednafen_gba
- mednafen_lynx
- mednafen_ngp
- mednafen_pce
- mednafen_pce_fast
- mednafen_pcfx
- mednafen_psx
- mednafen_psx_hw
- mednafen_saturn
- mednafen_snes
- mednafen_supafaust
- mednafen_supergrafx
- mednafen_vb
- mednafen_wswan
- melonds
- melondsds
- mesen
- mesen-s
- meteor
- mgba
- minivmac
- model2
- mojozork
- mrboom
- mu
- mupen64plus_next
- nekop2
- neocd
- nestopia
- noods
- np2kai
- numero
- nxengine
- o2em
- oberon
- openlara
- opera
- panda3ds
- parallel_n64
- pcsx2
- pcsx_rearmed
- pd777
- picodrive
- play
- pocketcdg
- pokemini
- potator
- ppsspp
- prboom
- primehack
- prosystem
- puae
- puae2021
- px68k
- qemu
- quasi88
- quicknes
- race
- reminiscence
- retro8
- romcleaner
- rpcs3
- ryujinx
- same_cdi
- sameboy
- sameduck
- scummvm
- shadps4
- skyemu
- smsplus
- snes9x
- snes9x2002
- snes9x2005
- snes9x2005_plus
- snes9x2010
- squirreljme
- stella
- stella2014
- stella2023
- superbroswar
- supermodel
- suyu
- swanstation
- tamalibretro
- tgbdual
- theodore
- thepowdertoy
- tic80
- tyrquake
- uw8
- uzem
- vaporspec
- vba_next
- vbam
- vecx
- vemulator
- vice_x128
- vice_x64
- vice_x64sc
- vice_xcbm2
- vice_xcbm5x0
- vice_xpet
- vice_xplus4
- vice_xscpu64
- vice_xvic
- vircon32
- virtualjaguar
- virtualxt
- vita3k
- vitaquake2
- vitaquake2-rogue
- vitaquake2-xatrix
- vitaquake2-zaero
- vitaquake3
- wasm4
- x1
- xemu
- xenia
- xrick
- yabasanshiro
- yabause
- yuzu
windows:
architecture: x86_64
cores:
- '2048'
- 3dengine
- '81'
- DoubleCherryGB
- a5200
- amiarcadia
- anarch
- applewin
- ardens
- arduous
- atari800
- azahar
- b2
- bigpemu
- bk
- blastem
- bluemsx
- boom3
- boom3_xp
- bsnes
- bsnes-jg
- bsnes2014_accuracy
- bsnes2014_balanced
- bsnes2014_performance
- bsnes_cplusplus98
- bsnes_hd_beta
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- cannonball
- cap32
- cdi2015
- cemu
- chailove
- citra
- citra2018
- citron
- clownmdemu
- craft
- crocods
- desmume
- desmume2015
- dice
- dinothawr
- dirksimple
- dolphin
- dosbox_core
- dosbox_pure
- dosbox_svn
- doukutsu_rs
- duckstation
- easyrpg
- ecwolf
- eden
- ep128emu_core
- fbalpha
- fbalpha2012
- fbalpha2012_cps1
- fbalpha2012_cps2
- fbalpha2012_cps3
- fbalpha2012_neogeo
- fbneo
- fceumm
- fixgb
- fixnes
- flycast
- fmsx
- freechaf
- freeintv
- frodo
- fuse
- galaksija
- gam4980
- gambatte
- gearboy
- gearcoleco
- geargrafx
- gearlynx
- gearsystem
- genesis_plus_gx
- genesis_plus_gx_wide
- geolith
- gme
- gong
- gpsp
- gw
- handy
- hatari
- holani
- jaxe
- jollycv
- jumpnbump
- kronos
- lowresnx
- lutro
- m2000
- mame
- mame2000
- mame2003
- mame2003_midway
- mame2003_plus
- mame2010
- mcsoftserve
- mednafen_gba
- mednafen_lynx
- mednafen_ngp
- mednafen_pce
- mednafen_pce_fast
- mednafen_pcfx
- mednafen_psx
- mednafen_psx_hw
- mednafen_saturn
- mednafen_snes
- mednafen_supafaust
- mednafen_supergrafx
- mednafen_vb
- mednafen_wswan
- melonds
- melondsds
- mesen
- mesen-s
- meteor
- mgba
- minivmac
- model2
- mojozork
- mrboom
- mu
- mupen64plus_next
- nekop2
- neocd
- nestopia
- noods
- np2kai
- numero
- nxengine
- o2em
- oberon
- openlara
- opera
- panda3ds
- parallel_n64
- pcsx2
- pcsx_rearmed
- pd777
- picodrive
- play
- pocketcdg
- pokemini
- potator
- ppsspp
- prboom
- primehack
- prosystem
- puae
- puae2021
- px68k
- qemu
- quasi88
- quicknes
- race
- reminiscence
- retro8
- romcleaner
- rpcs3
- ryujinx
- same_cdi
- sameboy
- sameduck
- scummvm
- shadps4
- skyemu
- smsplus
- snes9x
- snes9x2002
- snes9x2005
- snes9x2005_plus
- snes9x2010
- squirreljme
- stella
- stella2014
- stella2023
- superbroswar
- supermodel
- swanstation
- tamalibretro
- template
- tgbdual
- theodore
- thepowdertoy
- tic80
- tyrquake
- uw8
- uzem
- vaporspec
- vba_next
- vbam
- vecx
- vemulator
- vice_x128
- vice_x64
- vice_x64sc
- vice_xcbm2
- vice_xcbm5x0
- vice_xpet
- vice_xplus4
- vice_xscpu64
- vice_xvic
- vircon32
- virtualjaguar
- virtualxt
- vita3k
- vitaquake2
- vitaquake2-rogue
- vitaquake2-xatrix
- vitaquake2-zaero
- vitaquake3
- wasm4
- x1
- xemu
- xenia
- xrick
- yabasanshiro
- yabause
- yuzu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
platform: retrobat
source: static
scraped_at: "2026-03-26T00:00:00Z"
targets:
windows:
architecture: x86_64
cores: [81, a5200, abuse, arduous, atari800, azahar, bennugd, bk, bluemsx, bsnes,
bstone, cannonball, cap32, catacombgl, cdogs, cemu, cgenius, citron, clk, corsixth,
demul, devilutionx, dhewm3, dice, dolphin, dosbox_pure, dxx-rebirth, easyrpg,
ecwolf, eduke32, eka2l1, emuscv, etlegacy, fake08, fallout1-ce, fallout2-ce,
fbneo, fceumm, flatpak, flycast, freechaf, freeintv, fury, fuse, gambatte,
gearsystem, genesisplusgx, glide64mk2, gong, gsplus, gw, gzdoom, hatari, hcl,
hurrican, hypseus-singe, ikemen, ioquake3, iortcw, jazz2-native, lindbergh-loader,
lowresnx, lutro, mame, mame078plus, mednafen_lynx, mednafen_ngp, mednafen_supergrafx,
mednafen_wswan, melonds, mgba, minivmac, model2emu, moonlight, mrboom, neocd,
np2kai, nxengine, o2em, odcommander, openbor6412, openjazz, openjk, openjkdf2,
openmohaa, opera, pce_fast, pcfx, pcsx2, pcsx_rearmed, pd777, picodrive, play,
pokemini, potator, ppsspp, prboom, prosystem, puae, px68k, pygame, pyxel, quasi88,
raze, reminiscence, rpcs3, ruffle, samcoupe, sameduck, scummvm, sdlpop, sh,
shadps4, snes9x, solarus, sonic2013, sonic3-air, sonic-mania, steam, stella,
superbroswar, supermodel, taradino, tgbdual, theforceengine, theodore, thextech,
tic80, tr1x, tr2x, tsugaru, tyrian, tyrquake, uqm, uzem, vb, vecx, vice_x64,
vircon32, virtualjaguar, vita3k, vox_official, vpinball, wasm4, wine-tkg, x1,
x128, x16emu, xash3d_fwgs, xemu, xenia-canary, xpet, xplus4, xrick, xvic,
yabasanshiro, yquake2, zc210]

View File

@@ -0,0 +1,9 @@
platform: retrodeck
source: static
scraped_at: "2026-03-26T00:00:00Z"
targets:
x86_64-linux:
architecture: x86_64
cores: [azahar, cemu, dolphin, duckstation, gzdoom, mame, melonds, openbor, pcsx2,
pico-8, ppsspp, primehack, retroarch, rpcs3, ruffle, solarus, vita3k, xemu,
xroar]

View File

@@ -0,0 +1,662 @@
platform: retropie
source: https://api.github.com/repos/RetroPie/RetroPie-Setup/contents/scriptmodules/libretrocores
scraped_at: '2026-03-26T08:43:13Z'
targets:
rpi1:
architecture: armv6
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce_fast
- beetle_pcfx
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2002
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vecx
- vice
- x1
- xrick
rpi2:
architecture: armv7
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_saturn
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- bsnes
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- flycast
- flycast_dev
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mesen
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2002
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vba_next
- vecx
- vice
- virtualjaguar
- x1
- xrick
- yabause
rpi3:
architecture: armv7
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_saturn
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- bsnes
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- flycast
- flycast_dev
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mesen
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2002
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vba_next
- vecx
- vice
- virtualjaguar
- x1
- xrick
- yabause
rpi4:
architecture: aarch64
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_saturn
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- bsnes
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- flycast
- flycast_dev
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mesen
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2002
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vba_next
- vecx
- vice
- virtualjaguar
- x1
- xrick
- yabause
rpi5:
architecture: aarch64
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_saturn
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- bsnes
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- flycast
- flycast_dev
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mesen
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2002
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vba_next
- vecx
- vice
- virtualjaguar
- x1
- xrick
- yabause
x86:
architecture: x86
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_psx
- beetle_saturn
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- bsnes
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- flycast
- flycast_dev
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- kronos
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mesen
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vba_next
- vecx
- vice
- virtualjaguar
- x1
- xrick
- yabause
x86_64:
architecture: x86_64
cores:
- '81'
- atari800
- beetle_lynx
- beetle_ngp
- beetle_pce
- beetle_pce_fast
- beetle_pcfx
- beetle_psx
- beetle_saturn
- beetle_supergrafx
- beetle_vb
- beetle_wswan
- bennugd
- bluemsx
- bsnes
- caprice32
- desmume
- desmume2015
- dinothawr
- dirksimple
- dosbox
- dosbox_pure
- ep128emu
- fbalpha2012
- fbneo
- fceumm
- flycast
- flycast_dev
- fmsx
- freechaf
- freeintv
- fuse
- gambatte
- geargrafx
- gearsystem
- genesis_plus_gx
- gpsp
- gw
- handy
- hatari
- kronos
- mame
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mesen
- mess
- mess2016
- mgba
- mrboom
- mupen64plus
- mupen64plus_next
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- opera
- parallel_n64
- pcsx_rearmed
- picodrive
- pokemini
- ppsspp
- prboom
- prosystem
- puae
- puae2021
- px68k
- quasi88
- quicknes
- retro8
- scummvm
- smsplus_gx
- snes9x
- snes9x2005
- snes9x2010
- stella
- stella2014
- superflappybirds
- tgbdual
- theodore
- tic80
- tyrquake
- vba_next
- vecx
- vice
- virtualjaguar
- x1
- xrick
- yabause

View File

@@ -0,0 +1,7 @@
platform: romm
source: static
scraped_at: "2026-03-26T00:00:00Z"
targets:
browser:
architecture: wasm
cores: []

View File

@@ -192,6 +192,100 @@ def list_registered_platforms(
return platforms
def load_target_config(
platform_name: str,
target: str,
platforms_dir: str = "platforms",
) -> set[str]:
"""Load target config and return the set of core names for the given target.
Resolves aliases from _overrides.yml, applies add_cores/remove_cores.
Raises ValueError if target is unknown (with list of available targets).
Raises FileNotFoundError if no target file exists for the platform.
"""
targets_dir = os.path.join(platforms_dir, "targets")
target_file = os.path.join(targets_dir, f"{platform_name}.yml")
if not os.path.exists(target_file):
raise FileNotFoundError(
f"No target config for platform '{platform_name}': {target_file}"
)
with open(target_file) as f:
data = yaml.safe_load(f) or {}
targets = data.get("targets", {})
overrides_file = os.path.join(targets_dir, "_overrides.yml")
overrides = {}
if os.path.exists(overrides_file):
with open(overrides_file) as f:
all_overrides = yaml.safe_load(f) or {}
overrides = all_overrides.get(platform_name, {}).get("targets", {})
alias_index: dict[str, str] = {}
for tname in targets:
alias_index[tname] = tname
for alias in overrides.get(tname, {}).get("aliases", []):
alias_index[alias] = tname
canonical = alias_index.get(target)
if canonical is None:
available = sorted(targets.keys())
aliases = []
for tname, ovr in overrides.items():
for a in ovr.get("aliases", []):
aliases.append(f"{a} -> {tname}")
msg = f"Unknown target '{target}' for platform '{platform_name}'.\n"
msg += f"Available targets: {', '.join(available)}"
if aliases:
msg += f"\nAliases: {', '.join(sorted(aliases))}"
raise ValueError(msg)
cores = set(str(c) for c in targets[canonical].get("cores", []))
ovr = overrides.get(canonical, {})
for c in ovr.get("add_cores", []):
cores.add(str(c))
for c in ovr.get("remove_cores", []):
cores.discard(str(c))
return cores
def list_available_targets(
platform_name: str,
platforms_dir: str = "platforms",
) -> list[dict]:
"""List available targets for a platform with their aliases.
Returns list of dicts with keys: name, architecture, core_count, aliases.
Returns empty list if no target file exists.
"""
targets_dir = os.path.join(platforms_dir, "targets")
target_file = os.path.join(targets_dir, f"{platform_name}.yml")
if not os.path.exists(target_file):
return []
with open(target_file) as f:
data = yaml.safe_load(f) or {}
overrides_file = os.path.join(targets_dir, "_overrides.yml")
overrides = {}
if os.path.exists(overrides_file):
with open(overrides_file) as f:
all_overrides = yaml.safe_load(f) or {}
overrides = all_overrides.get(platform_name, {}).get("targets", {})
result = []
for tname, tdata in sorted(data.get("targets", {}).items()):
aliases = overrides.get(tname, {}).get("aliases", [])
result.append({
"name": tname,
"architecture": tdata.get("architecture", ""),
"core_count": len(tdata.get("cores", [])),
"aliases": aliases,
})
return result
def resolve_local_file(
file_entry: dict,
db: dict,
@@ -439,6 +533,7 @@ def load_emulator_profiles(
def group_identical_platforms(
platforms: list[str], platforms_dir: str,
target_cores_cache: dict[str, set[str] | None] | None = None,
) -> list[tuple[list[str], str]]:
"""Group platforms that produce identical packs (same files + base_destination).
@@ -473,6 +568,11 @@ def group_identical_platforms(
entries.append(f"{full_dest}|{sha1}|{md5}")
fp = hashlib.sha1("|".join(sorted(entries)).encode()).hexdigest()
if target_cores_cache:
tc = target_cores_cache.get(platform)
if tc is not None:
tc_str = "|".join(sorted(tc))
fp = hashlib.sha1(f"{fp}|{tc_str}".encode()).hexdigest()
fingerprints.setdefault(fp, []).append(platform)
# Prefer the root platform (no inherits) as representative
if fp not in representatives or (not inherits[platform] and inherits.get(representatives[fp], False)):
@@ -488,29 +588,28 @@ def group_identical_platforms(
def resolve_platform_cores(
config: dict, profiles: dict[str, dict],
target_cores: set[str] | None = None,
) -> set[str]:
"""Resolve which emulator profiles are relevant for a platform.
Resolution strategies (by priority):
1. cores: "all_libretro" all profiles with libretro in type
2. cores: [list] profiles whose dict key matches a core name
3. cores: absent fallback to systems intersection
1. cores: "all_libretro" -- all profiles with libretro in type
2. cores: [list] -- profiles whose dict key matches a core name
3. cores: absent -- fallback to systems intersection
Alias profiles are always excluded (they point to another profile).
If target_cores is provided, result is intersected with it.
"""
cores_config = config.get("cores")
if cores_config == "all_libretro":
return {
result = {
name for name, p in profiles.items()
if "libretro" in p.get("type", "")
and p.get("type") != "alias"
}
if isinstance(cores_config, list):
elif isinstance(cores_config, list):
core_set = {str(c) for c in cores_config}
# Build reverse index: platform core name -> profile name
# Uses profile filename (dict key) + all names in cores: field
core_to_profile: dict[str, str] = {}
for name, p in profiles.items():
if p.get("type") == "alias":
@@ -518,19 +617,82 @@ def resolve_platform_cores(
core_to_profile[name] = name
for core_name in p.get("cores", []):
core_to_profile[str(core_name)] = name
return {
result = {
core_to_profile[c]
for c in core_set
if c in core_to_profile
}
else:
platform_systems = set(config.get("systems", {}).keys())
result = {
name for name, p in profiles.items()
if set(p.get("systems", [])) & platform_systems
and p.get("type") != "alias"
}
# Fallback: system ID intersection
platform_systems = set(config.get("systems", {}).keys())
return {
name for name, p in profiles.items()
if set(p.get("systems", [])) & platform_systems
and p.get("type") != "alias"
}
if target_cores is not None:
# Build reverse index: upstream name -> profile key
# Upstream sources (buildbot, es_systems) may use different names
# than our profile keys (e.g., mednafen_psx vs beetle_psx).
# The profiles' cores: field lists these alternate names.
upstream_to_profile: dict[str, str] = {}
for name, p in profiles.items():
upstream_to_profile[name] = name
for alias in p.get("cores", []):
upstream_to_profile[str(alias)] = name
# Expand target_cores to profile keys
expanded = {upstream_to_profile.get(c, c) for c in target_cores}
result = result & expanded
return result
def filter_systems_by_target(
systems: dict[str, dict],
profiles: dict[str, dict],
target_cores: set[str] | None,
platform_cores: set[str] | None = None,
) -> dict[str, dict]:
"""Filter platform systems to only those reachable by target cores.
A system is reachable if at least one core that emulates it is available
on the target. Only considers cores relevant to the platform (from
platform_cores). Systems whose cores are all outside the platform's
scope are kept (no information to exclude them).
Returns the filtered systems dict (or all if no target).
"""
if target_cores is None:
return systems
# Build reverse index for target core name resolution
upstream_to_profile: dict[str, str] = {}
for name, p in profiles.items():
upstream_to_profile[name] = name
for alias in p.get("cores", []):
upstream_to_profile[str(alias)] = name
expanded_target = {upstream_to_profile.get(c, c) for c in target_cores}
# Build system -> profile keys mapping (only platform-relevant cores)
system_to_cores: dict[str, set[str]] = {}
for name, p in profiles.items():
if p.get("type") == "alias":
continue
if platform_cores is not None and name not in platform_cores:
continue
for sid in p.get("systems", []):
system_to_cores.setdefault(sid, set()).add(name)
filtered = {}
for sys_id, sys_data in systems.items():
cores_for_system = system_to_cores.get(sys_id, set())
if not cores_for_system:
# No platform-relevant core maps to this system — keep it
filtered[sys_id] = sys_data
elif cores_for_system & expanded_target:
# At least one core for this system is on the target
filtered[sys_id] = sys_data
# else: all platform cores for this system are off-target — exclude
return filtered
def _parse_validation(validation: list | dict | None) -> list[str]:

View File

@@ -181,6 +181,8 @@ def main():
parser.add_argument("--platforms-dir", default=DEFAULT_PLATFORMS_DIR)
parser.add_argument("--db", default=DEFAULT_DB)
parser.add_argument("--emulator", "-e", help="Analyze single emulator")
parser.add_argument("--platform", "-p", help="Platform name (required for --target)")
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
parser.add_argument("--json", action="store_true", help="JSON output")
args = parser.parse_args()
@@ -188,6 +190,15 @@ def main():
if args.emulator:
profiles = {k: v for k, v in profiles.items() if k == args.emulator}
if args.target:
if not args.platform:
parser.error("--target requires --platform")
from common import load_target_config, resolve_platform_cores
target_cores = load_target_config(args.platform, args.target, args.platforms_dir)
config = load_platform_config(args.platform, args.platforms_dir)
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
profiles = {k: v for k, v in profiles.items() if k in relevant}
if not profiles:
print("No emulator profiles found.", file=sys.stderr)
return

View File

@@ -28,9 +28,9 @@ from common import (
_build_validation_index, build_zip_contents_index, check_file_validation,
check_inside_zip, compute_hashes, fetch_large_file, filter_files_by_mode,
group_identical_platforms, list_emulator_profiles, list_registered_platforms,
list_system_ids, load_database, load_data_dir_registry,
load_emulator_profiles, load_platform_config, md5_composite,
resolve_local_file,
filter_systems_by_target, list_system_ids, load_database,
load_data_dir_registry, load_emulator_profiles, load_platform_config,
md5_composite, resolve_local_file,
)
from deterministic_zip import rebuild_zip_deterministic
@@ -185,6 +185,7 @@ def _collect_emulator_extras(
seen: set,
base_dest: str,
emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
) -> list[dict]:
"""Collect core requirement files from emulator profiles not in the platform pack.
@@ -198,7 +199,7 @@ def _collect_emulator_extras(
"""
from verify import find_undeclared_files
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles)
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles, target_cores=target_cores)
extras = []
for u in undeclared:
if not u["in_repo"]:
@@ -229,6 +230,7 @@ def generate_pack(
zip_contents: dict | None = None,
data_registry: dict | None = None,
emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
) -> str | None:
"""Generate a ZIP pack for a platform.
@@ -262,8 +264,18 @@ def generate_pack(
if emu_profiles:
validation_index = _build_validation_index(emu_profiles)
# Filter systems by target if specified
from common import resolve_platform_cores
plat_cores = resolve_platform_cores(config, emu_profiles or {}) if target_cores else None
pack_systems = filter_systems_by_target(
config.get("systems", {}),
emu_profiles or {},
target_cores,
platform_cores=plat_cores,
)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
for sys_id, system in sorted(config.get("systems", {}).items()):
for sys_id, system in sorted(pack_systems.items()):
for file_entry in system.get("files", []):
dest = _sanitize_path(file_entry.get("destination", file_entry["name"]))
if not dest:
@@ -405,7 +417,7 @@ def generate_pack(
emu_profiles = load_emulator_profiles(emulators_dir)
core_files = _collect_emulator_extras(
config, emulators_dir, db,
seen_destinations, base_dest, emu_profiles,
seen_destinations, base_dest, emu_profiles, target_cores=target_cores,
)
core_count = 0
for fe in core_files:
@@ -442,7 +454,7 @@ def generate_pack(
total_files += 1
# Data directories from _data_dirs.yml
for sys_id, system in sorted(config.get("systems", {}).items()):
for sys_id, system in sorted(pack_systems.items()):
for dd in system.get("data_directories", []):
ref_key = dd.get("ref", "")
if not ref_key or not data_registry or ref_key not in data_registry:
@@ -847,6 +859,8 @@ def main():
parser.add_argument("--refresh-data", action="store_true",
help="Force re-download all data directories")
parser.add_argument("--list", action="store_true", help="List available platforms")
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
parser.add_argument("--list-targets", action="store_true", help="List available targets for the platform")
args = parser.parse_args()
if args.list:
@@ -860,6 +874,18 @@ def main():
if args.list_systems:
list_system_ids(args.emulators_dir)
return
if args.list_targets:
if not args.platform:
parser.error("--list-targets requires --platform")
from common import list_available_targets
targets = list_available_targets(args.platform, args.platforms_dir)
if not targets:
print(f"No targets configured for platform '{args.platform}'")
return
for t in targets:
aliases = f" (aliases: {', '.join(t['aliases'])})" if t['aliases'] else ""
print(f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}")
return
# Mutual exclusion
modes = sum(1 for x in (args.platform, args.all, args.emulator, args.system) if x)
@@ -869,6 +895,10 @@ def main():
parser.error("--platform, --all, --emulator, and --system are mutually exclusive")
if args.standalone and not (args.emulator or args.system):
parser.error("--standalone requires --emulator or --system")
if args.target and not (args.platform or args.all):
parser.error("--target requires --platform or --all")
if args.target and (args.emulator or args.system):
parser.error("--target is incompatible with --emulator and --system")
db = load_database(args.db)
zip_contents = build_zip_contents_index(db)
@@ -916,7 +946,31 @@ def main():
print(f"Refreshed {updated} data director{'ies' if updated > 1 else 'y'}")
emu_profiles = load_emulator_profiles(args.emulators_dir)
groups = group_identical_platforms(platforms, args.platforms_dir)
target_cores_cache: dict[str, set[str] | None] = {}
if args.target:
from common import load_target_config
skip = []
for p in platforms:
try:
target_cores_cache[p] = load_target_config(p, args.target, args.platforms_dir)
except FileNotFoundError:
if args.all:
target_cores_cache[p] = None
else:
print(f"ERROR: No target config for platform '{p}'", file=sys.stderr)
sys.exit(1)
except ValueError as e:
if args.all:
print(f"INFO: Skipping {p}: {e}")
skip.append(p)
else:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
platforms = [p for p in platforms if p not in skip]
groups = group_identical_platforms(platforms, args.platforms_dir,
target_cores_cache if args.target else None)
for group_platforms, representative in groups:
variants = [p for p in group_platforms if p != representative]
@@ -928,11 +982,12 @@ def main():
print(f"\nGenerating pack for {representative}...")
try:
tc = target_cores_cache.get(representative) if args.target else None
zip_path = generate_pack(
representative, args.platforms_dir, db, args.bios_dir, args.output_dir,
include_extras=args.include_extras, emulators_dir=args.emulators_dir,
zip_contents=zip_contents, data_registry=data_registry,
emu_profiles=emu_profiles,
emu_profiles=emu_profiles, target_cores=tc,
)
if zip_path and variants:
rep_cfg = load_platform_config(representative, args.platforms_dir)

View File

@@ -140,6 +140,7 @@ def main():
# --include-extras is now a no-op: core requirements are always included
parser.add_argument("--include-extras", action="store_true",
help="(no-op) Core requirements are always included")
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
args = parser.parse_args()
results = {}
@@ -172,6 +173,8 @@ def main():
verify_cmd = [sys.executable, "scripts/verify.py", "--all"]
if args.include_archived:
verify_cmd.append("--include-archived")
if args.target:
verify_cmd.extend(["--target", args.target])
ok, verify_output = run(verify_cmd, "3/7 verify all platforms")
results["verify"] = ok
all_ok = all_ok and ok
@@ -189,6 +192,8 @@ def main():
pack_cmd.append("--offline")
if args.include_extras:
pack_cmd.append("--include-extras")
if args.target:
pack_cmd.extend(["--target", args.target])
ok, pack_output = run(pack_cmd, "4/7 generate packs")
results["generate_packs"] = ok
all_ok = all_ok and ok

View File

@@ -0,0 +1,48 @@
"""Target scraper plugin discovery module.
Auto-detects *_targets_scraper.py files and exposes their scrapers.
"""
from __future__ import annotations
import importlib
import pkgutil
from pathlib import Path
class BaseTargetScraper:
"""Base class for target scrapers."""
def __init__(self, url: str = ""):
self.url = url
def fetch_targets(self) -> dict:
"""Fetch targets and their core lists. Returns dict matching target YAML format."""
raise NotImplementedError
def write_output(self, data: dict, output_path: str) -> None:
"""Write target data to YAML file."""
try:
import yaml
except ImportError:
raise ImportError("PyYAML required: pip install pyyaml")
with open(output_path, "w") as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
_scrapers: dict[str, type] = {}
def discover_target_scrapers() -> dict[str, type]:
"""Auto-discover all *_targets_scraper.py modules."""
if _scrapers:
return _scrapers
package_dir = Path(__file__).parent
for finder, name, ispkg in pkgutil.iter_modules([str(package_dir)]):
if not name.endswith("_targets_scraper"):
continue
module = importlib.import_module(f".{name}", package=__package__)
platform_name = getattr(module, "PLATFORM_NAME", None)
scraper_class = getattr(module, "Scraper", None)
if platform_name and scraper_class:
_scrapers[platform_name] = scraper_class
return _scrapers

View File

@@ -0,0 +1,370 @@
"""Scraper for Batocera per-board emulator availability.
Sources (batocera-linux/batocera.linux):
- configs/batocera-*.board -- board definitions, each sets BR2_PACKAGE_BATOCERA_TARGET_*
- package/batocera/core/batocera-system/Config.in -- select PACKAGE if CONDITION lines
- package/batocera/emulationstation/batocera-es-system/es_systems.yml
-- emulator requireAnyOf flag mapping
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.error
import urllib.request
from datetime import datetime, timezone
import yaml
from . import BaseTargetScraper
PLATFORM_NAME = "batocera"
GITHUB_API = "https://api.github.com/repos/batocera-linux/batocera.linux/contents"
RAW_BASE = "https://raw.githubusercontent.com/batocera-linux/batocera.linux/master"
CONFIG_IN_URL = f"{RAW_BASE}/package/batocera/core/batocera-system/Config.in"
ES_SYSTEMS_URL = (
f"{RAW_BASE}/package/batocera/emulationstation/batocera-es-system/es_systems.yml"
)
_HEADERS = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
_TARGET_FLAG_RE = re.compile(r'^(BR2_PACKAGE_BATOCERA_TARGET_\w+)=y', re.MULTILINE)
# Matches: select BR2_PACKAGE_FOO (optional: if CONDITION)
# Condition may span multiple lines (backslash continuation)
_SELECT_RE = re.compile(
r'^\s+select\s+(BR2_PACKAGE_\w+)' # package being selected
r'(?:\s+if\s+((?:[^\n]|\\\n)+?))?' # optional "if CONDITION" (may continue with \)
r'(?:\s*#[^\n]*)?$', # optional trailing comment
re.MULTILINE,
)
# Meta-flag definition: "if COND\n\tconfig DERIVED_FLAG\n\t...\nendif"
_META_BLOCK_RE = re.compile(
r'^if\s+((?:[^\n]|\\\n)+?)\n' # condition (may span lines via \)
r'(?:.*?\n)*?' # optional lines before the config
r'\s+config\s+(BR2_PACKAGE_\w+)' # derived flag name
r'.*?^endif', # end of block
re.MULTILINE | re.DOTALL,
)
def _fetch(url: str, headers: dict | None = None) -> str | None:
h = headers or {"User-Agent": "retrobios-scraper/1.0"}
try:
req = urllib.request.Request(url, headers=h)
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
def _fetch_json(url: str) -> list | dict | None:
text = _fetch(url, headers=_HEADERS)
if text is None:
return None
try:
return json.loads(text)
except json.JSONDecodeError as e:
print(f" json parse error {url}: {e}", file=sys.stderr)
return None
def _normalise_condition(raw: str) -> str:
"""Strip backslash-continuations and collapse whitespace."""
return re.sub(r'\\\n\s*', ' ', raw).strip()
def _tokenise(condition: str) -> list[str]:
"""Split a Kconfig condition into tokens: flags, !, &&, ||, (, )."""
token_re = re.compile(r'&&|\|\||!|\(|\)|BR2_\w+|"[^"]*"')
return token_re.findall(condition)
def _check_condition(tokens: list[str], pos: int, active: frozenset[str]) -> tuple[bool, int]:
"""Recursive descent check of a Kconfig boolean expression."""
return _check_or(tokens, pos, active)
def _check_or(tokens: list[str], pos: int, active: frozenset[str]) -> tuple[bool, int]:
left, pos = _check_and(tokens, pos, active)
while pos < len(tokens) and tokens[pos] == '||':
pos += 1
right, pos = _check_and(tokens, pos, active)
left = left or right
return left, pos
def _check_and(tokens: list[str], pos: int, active: frozenset[str]) -> tuple[bool, int]:
left, pos = _check_not(tokens, pos, active)
while pos < len(tokens) and tokens[pos] == '&&':
pos += 1
right, pos = _check_not(tokens, pos, active)
left = left and right
return left, pos
def _check_not(tokens: list[str], pos: int, active: frozenset[str]) -> tuple[bool, int]:
if pos < len(tokens) and tokens[pos] == '!':
pos += 1
val, pos = _check_atom(tokens, pos, active)
return not val, pos
return _check_atom(tokens, pos, active)
def _check_atom(tokens: list[str], pos: int, active: frozenset[str]) -> tuple[bool, int]:
if pos >= len(tokens):
return True, pos
tok = tokens[pos]
if tok == '(':
pos += 1
val, pos = _check_or(tokens, pos, active)
if pos < len(tokens) and tokens[pos] == ')':
pos += 1
return val, pos
if tok.startswith('BR2_'):
pos += 1
return tok in active, pos
if tok.startswith('"'):
pos += 1
return True, pos
# Unknown token — treat as true to avoid false negatives
pos += 1
return True, pos
def _condition_holds(condition: str, active: frozenset[str]) -> bool:
"""Return True if a Kconfig boolean condition holds for the given active flags."""
if not condition:
return True
norm = _normalise_condition(condition)
tokens = _tokenise(norm)
if not tokens:
return True
try:
result, _ = _check_condition(tokens, 0, active)
return result
except Exception:
return True # conservative: include on parse failure
def _parse_meta_flags(text: str) -> list[tuple[str, str]]:
"""Return [(derived_flag, condition_str)] from top-level if/endif blocks.
These define derived flags like BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY,
BR2_PACKAGE_BATOCERA_GLES3, etc.
"""
results: list[tuple[str, str]] = []
for m in _META_BLOCK_RE.finditer(text):
cond = _normalise_condition(m.group(1))
flag = m.group(2)
results.append((flag, cond))
return results
def _expand_flags(primary_flag: str, meta_rules: list[tuple[str, str]]) -> frozenset[str]:
"""Given a board's primary flag, expand to all active derived flags.
Iterates until stable (handles chained derivations like X86_64_ANY -> X86_ANY).
"""
active: set[str] = {primary_flag}
changed = True
while changed:
changed = False
for derived, cond in meta_rules:
if derived not in active and _condition_holds(cond, frozenset(active)):
active.add(derived)
changed = True
return frozenset(active)
def _parse_selects(text: str) -> list[tuple[str, str]]:
"""Parse all 'select PACKAGE [if CONDITION]' lines from Config.in.
Returns [(package, condition)] where condition is '' if unconditional.
"""
results: list[tuple[str, str]] = []
for m in _SELECT_RE.finditer(text):
pkg = m.group(1)
cond = _normalise_condition(m.group(2) or '')
results.append((pkg, cond))
return results
def _parse_es_systems(text: str) -> dict[str, list[str]]:
"""Parse es_systems.yml: map BR2_PACKAGE_* flag -> list of emulator names.
The file is a dict keyed by system name. Each system has:
emulators:
<emulator_group>:
<core_name>: {requireAnyOf: [BR2_PACKAGE_FOO]}
"""
try:
data = yaml.safe_load(text)
except yaml.YAMLError:
return {}
if not isinstance(data, dict):
return {}
package_to_emulators: dict[str, list[str]] = {}
for _system_name, system_data in data.items():
if not isinstance(system_data, dict):
continue
emulators = system_data.get("emulators")
if not isinstance(emulators, dict):
continue
for _group_name, group_data in emulators.items():
if not isinstance(group_data, dict):
continue
for core_name, core_data in group_data.items():
if not isinstance(core_data, dict):
continue
require = core_data.get("requireAnyOf", [])
if not isinstance(require, list):
continue
for pkg_flag in require:
if isinstance(pkg_flag, str):
package_to_emulators.setdefault(pkg_flag, []).append(core_name)
return package_to_emulators
def _arch_from_flag(flag: str) -> str:
"""Guess architecture from board flag name."""
low = flag.lower()
if "x86_64" in low or "zen3" in low:
return "x86_64"
if "x86" in low:
return "x86"
return "aarch64"
class Scraper(BaseTargetScraper):
"""Cross-references Batocera boards, Config.in, and es_systems to build target lists."""
def __init__(self, url: str = "https://github.com/batocera-linux/batocera.linux"):
super().__init__(url=url)
def _list_boards(self) -> list[str]:
"""List batocera-*.board files from configs/ via GitHub API."""
data = _fetch_json(f"{GITHUB_API}/configs")
if not data or not isinstance(data, list):
return []
return [
item["name"] for item in data
if isinstance(item, dict)
and item.get("name", "").startswith("batocera-")
and item.get("name", "").endswith(".board")
]
def _fetch_board_flag(self, board_name: str) -> str | None:
"""Fetch a board file and extract its BR2_PACKAGE_BATOCERA_TARGET_* flag."""
url = f"{RAW_BASE}/configs/{board_name}"
text = _fetch(url)
if text is None:
return None
m = _TARGET_FLAG_RE.search(text)
return m.group(1) if m else None
def fetch_targets(self) -> dict:
"""Build per-board emulator availability map."""
print(" fetching board list...", file=sys.stderr)
boards = self._list_boards()
if not boards:
print(" warning: no boards found", file=sys.stderr)
print(" fetching Config.in...", file=sys.stderr)
config_in_text = _fetch(CONFIG_IN_URL) or ""
meta_rules = _parse_meta_flags(config_in_text)
selects = _parse_selects(config_in_text)
print(
f" parsed {len(meta_rules)} meta-flag rules, {len(selects)} select lines",
file=sys.stderr,
)
print(" fetching es_systems.yml...", file=sys.stderr)
es_text = _fetch(ES_SYSTEMS_URL) or ""
package_to_emulators = _parse_es_systems(es_text)
print(
f" parsed {len(package_to_emulators)} package->emulator mappings",
file=sys.stderr,
)
targets: dict[str, dict] = {}
for board_name in sorted(boards):
target_key = board_name.removeprefix("batocera-").removesuffix(".board")
print(f" processing {target_key}...", file=sys.stderr)
primary_flag = self._fetch_board_flag(board_name)
if primary_flag is None:
print(f" no target flag found in {board_name}", file=sys.stderr)
continue
active = _expand_flags(primary_flag, meta_rules)
# Determine which packages are selected for this board
selected_packages: set[str] = set()
for pkg, cond in selects:
if _condition_holds(cond, active):
selected_packages.add(pkg)
# Map selected packages to emulator names via es_systems.yml
emulators: set[str] = set()
for pkg in selected_packages:
for emu in package_to_emulators.get(pkg, []):
emulators.add(emu)
arch = _arch_from_flag(primary_flag)
targets[target_key] = {
"architecture": arch,
"cores": sorted(emulators),
}
print(
f" {len(emulators)} emulators ({len(selected_packages)} packages selected)",
file=sys.stderr,
)
return {
"platform": "batocera",
"source": self.url,
"scraped_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"targets": targets,
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Scrape Batocera per-board emulator targets"
)
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'])} emulators")
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()

View File

@@ -0,0 +1,184 @@
"""Scraper for EmuDeck emulator targets.
Sources:
SteamOS: dragoonDorise/EmuDeck — functions/EmuScripts/*.sh
Windows: EmuDeck/emudeck-we — functions/EmuScripts/*.ps1
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.error
import urllib.request
from datetime import datetime, timezone
import yaml
from . import BaseTargetScraper
PLATFORM_NAME = "emudeck"
STEAMOS_API = "https://api.github.com/repos/dragoonDorise/EmuDeck/contents/functions/EmuScripts"
WINDOWS_API = "https://api.github.com/repos/EmuDeck/emudeck-we/contents/functions/EmuScripts"
# Map EmuDeck script names to emulator profile keys
# Script naming: emuDeckDolphin.sh -> dolphin
# Some need explicit mapping when names differ
_NAME_OVERRIDES: dict[str, str] = {
"pcsx2qt": "pcsx2",
"rpcs3legacy": "rpcs3",
"cemuproton": "cemu",
"rmg": "mupen64plus_next",
"bigpemu": "bigpemu",
"eden": "eden",
"suyu": "suyu",
"ares": "ares",
}
# Scripts that are not emulators (config helpers, etc.)
_SKIP = {"retroarch_maincfg", "retroarch"}
def _fetch(url: str) -> str | None:
try:
req = urllib.request.Request(
url, headers={"User-Agent": "retrobios-scraper/1.0"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
def _list_emuscripts(api_url: str) -> list[str]:
"""List emulator script filenames from GitHub API."""
raw = _fetch(api_url)
if not raw:
return []
entries = json.loads(raw)
names = []
for e in entries:
name = e.get("name", "")
if name.endswith(".sh") or name.endswith(".ps1"):
names.append(name)
return names
def _script_to_core(filename: str) -> str | None:
"""Convert EmuScripts filename to core profile key."""
# Strip extension and emuDeck prefix
name = re.sub(r'\.(sh|ps1)$', '', filename, flags=re.IGNORECASE)
name = re.sub(r'^emuDeck', '', name, flags=re.IGNORECASE)
if not name:
return None
key = name.lower()
if key in _SKIP:
return None
return _NAME_OVERRIDES.get(key, key)
class Scraper(BaseTargetScraper):
"""Fetches emulator lists for EmuDeck SteamOS and Windows targets."""
def __init__(self, url: str = "https://github.com/dragoonDorise/EmuDeck"):
super().__init__(url=url)
def _fetch_cores_for_target(self, api_url: str, label: str,
arch: str = "x86_64") -> list[str]:
print(f" fetching {label} EmuScripts...", file=sys.stderr)
scripts = _list_emuscripts(api_url)
cores: list[str] = []
seen: set[str] = set()
has_retroarch = False
for script in scripts:
core = _script_to_core(script)
if core and core not in seen:
seen.add(core)
cores.append(core)
# Detect RetroArch presence (provides all libretro cores)
name = re.sub(r'\.(sh|ps1)$', '', script, flags=re.IGNORECASE)
if name.lower() in ("emudeckretroarch", "retroarch_maincfg"):
has_retroarch = True
standalone_count = len(cores)
# EmuDeck ships RetroArch = all its libretro cores are available
if has_retroarch:
ra_cores = self._load_retroarch_cores(arch)
for c in ra_cores:
if c not in seen:
seen.add(c)
cores.append(c)
print(f" {label}: {standalone_count} standalone + "
f"{len(cores) - standalone_count} via RetroArch = {len(cores)} total",
file=sys.stderr)
return sorted(cores)
@staticmethod
def _load_retroarch_cores(arch: str) -> list[str]:
"""Load RetroArch target cores for given architecture."""
import os
target_path = os.path.join("platforms", "targets", "retroarch.yml")
if not os.path.exists(target_path):
return []
with open(target_path) as f:
data = yaml.safe_load(f) or {}
# Find a target matching the architecture
for tname, tinfo in data.get("targets", {}).items():
if tinfo.get("architecture") == arch:
return tinfo.get("cores", [])
return []
def fetch_targets(self) -> dict:
steamos_cores = self._fetch_cores_for_target(STEAMOS_API, "SteamOS")
windows_cores = self._fetch_cores_for_target(WINDOWS_API, "Windows")
targets: dict[str, dict] = {}
if steamos_cores:
targets["steamos"] = {
"architecture": "x86_64",
"cores": steamos_cores,
}
if windows_cores:
targets["windows"] = {
"architecture": "x86_64",
"cores": windows_cores,
}
return {
"platform": "emudeck",
"source": self.url,
"scraped_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"targets": targets,
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Scrape EmuDeck emulator targets"
)
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'])} emulators")
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()

View File

@@ -0,0 +1,200 @@
"""Scraper for RetroArch buildbot nightly targets.
Source: https://buildbot.libretro.com/nightly/
Fetches directory listings per target to determine available cores.
Buildbot structure varies by platform:
- linux: {path}/latest/ -> *_libretro.so.zip
- windows: {path}/latest/ -> *_libretro.dll.zip
- apple/osx: {path}/latest/ -> *_libretro.dylib.zip
- android: android/latest/{arch}/ -> *_libretro_android.so.zip
- switch: nintendo/switch/libnx/latest/ -> *_libretro_libnx.nro.zip
- 3ds: nintendo/3ds/latest/3dsx/ -> *_libretro.3dsx.zip
- wii/ngc: {path}/latest/ -> *_libretro_{plat}.dol.zip
- wiiu: nintendo/wiiu/latest/ -> *_libretro.rpx.zip
- psp: playstation/psp/latest/ -> *_libretro_psp.PBP.zip
- ps2: playstation/ps2/latest/ -> *_libretro_ps2.elf.zip
- vita: bundles only (VPK) - no individual cores
"""
from __future__ import annotations
import argparse
import re
import sys
import urllib.error
import urllib.request
from datetime import datetime, timezone
import yaml
from . import BaseTargetScraper
PLATFORM_NAME = "retroarch"
BUILDBOT_URL = "https://buildbot.libretro.com/nightly/"
# (url_path_under_nightly, target_name, architecture)
# url_path must end at the directory containing core files
TARGETS: list[tuple[str, str, str]] = [
("linux/x86_64/latest", "linux-x86_64", "x86_64"),
("linux/armhf/latest", "linux-armhf", "armhf"),
("linux/armv7-neon-hf/latest", "linux-armv7-neon-hf", "armv7"),
("windows/x86_64/latest", "windows-x86_64", "x86_64"),
("windows/x86/latest", "windows-x86", "x86"),
("android/latest/arm64-v8a", "android-arm64-v8a", "aarch64"),
("android/latest/armeabi-v7a", "android-armeabi-v7a", "armv7"),
("android/latest/x86_64", "android-x86_64", "x86_64"),
("android/latest/x86", "android-x86", "x86"),
("apple/osx/x86_64/latest", "osx-x86_64", "x86_64"),
("apple/osx/arm64/latest", "osx-arm64", "aarch64"),
("apple/ios-arm64/latest", "ios-arm64", "aarch64"),
("apple/tvos-arm64/latest", "tvos-arm64", "aarch64"),
("nintendo/switch/libnx/latest", "nintendo-switch", "aarch64"),
("nintendo/3ds/latest/3dsx", "nintendo-3ds", "arm"),
("nintendo/ngc/latest", "nintendo-gamecube", "ppc"),
("nintendo/wii/latest", "nintendo-wii", "ppc"),
("nintendo/wiiu/latest", "nintendo-wiiu", "ppc"),
("playstation/ps2/latest", "playstation-ps2", "mips"),
("playstation/psp/latest", "playstation-psp", "mips"),
# vita: only VPK bundles on buildbot — cores listed via libretro-super recipes
]
# Recipe-based targets: (recipe_path_under_RECIPE_BASE_URL, target_name, architecture)
RECIPE_TARGETS: list[tuple[str, str, str]] = [
("playstation/vita", "playstation-vita", "armv7"),
]
RECIPE_BASE_URL = "https://raw.githubusercontent.com/libretro/libretro-super/master/recipes/"
# Match any href containing _libretro followed by a platform-specific extension
# Covers: .so.zip, .dll.zip, .dylib.zip, .nro.zip, .dol.zip, .rpx.zip,
# .3dsx.zip, .PBP.zip, .elf.zip, _android.so.zip
_HREF_RE = re.compile(
r'href="([^"]*?(\w+)_libretro[^"]*?\.zip)"',
re.IGNORECASE,
)
# Extract core name: everything before _libretro
_CORE_NAME_RE = re.compile(r'^(.+?)_libretro')
class Scraper(BaseTargetScraper):
"""Fetches core lists per target from RetroArch buildbot nightly."""
def __init__(self, url: str = BUILDBOT_URL):
super().__init__(url=url)
def _fetch_url(self, url: str) -> str | None:
try:
req = urllib.request.Request(
url, headers={"User-Agent": "retrobios-scraper/1.0"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
def _fetch_cores_for_target(self, path: str) -> list[str]:
url = f"{self.url}{path}/"
html = self._fetch_url(url)
if html is None:
return []
cores: list[str] = []
seen: set[str] = set()
for match in _HREF_RE.finditer(html):
href = match.group(1)
filename = href.split("/")[-1]
m = _CORE_NAME_RE.match(filename)
if m:
core = m.group(1)
if core not in seen:
seen.add(core)
cores.append(core)
return sorted(cores)
def _parse_recipe_cores(self, text: str) -> list[str]:
cores: list[str] = []
seen: set[str] = set()
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if not parts:
continue
core = parts[0]
if core not in seen:
seen.add(core)
cores.append(core)
return sorted(cores)
def _fetch_cores_for_recipe(self, recipe_path: str) -> list[str]:
url = f"{RECIPE_BASE_URL}{recipe_path}"
text = self._fetch_url(url)
if text is None:
return []
return self._parse_recipe_cores(text)
def fetch_targets(self) -> dict:
targets: dict[str, dict] = {}
for path, target_name, arch in TARGETS:
print(f" fetching {target_name}...", file=sys.stderr)
cores = self._fetch_cores_for_target(path)
if not cores:
print(f" warning: no cores found for {target_name}", file=sys.stderr)
continue
targets[target_name] = {
"architecture": arch,
"cores": cores,
}
print(f" {target_name}: {len(cores)} cores", file=sys.stderr)
for recipe_path, target_name, arch in RECIPE_TARGETS:
print(f" fetching {target_name} (recipe)...", file=sys.stderr)
cores = self._fetch_cores_for_recipe(recipe_path)
if not cores:
print(f" warning: no cores found for {target_name}", file=sys.stderr)
continue
targets[target_name] = {
"architecture": arch,
"cores": cores,
}
print(f" {target_name}: {len(cores)} cores", file=sys.stderr)
return {
"platform": "retroarch",
"source": self.url,
"scraped_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"targets": targets,
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Scrape RetroArch buildbot nightly targets"
)
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()
total_cores = sum(len(t["cores"]) for t in data["targets"].values())
print(f"\n{len(data['targets'])} targets, {total_cores} total core entries",
file=sys.stderr)
if args.dry_run:
for name, info in sorted(data["targets"].items()):
print(f" {name:30s} {info['architecture']:10s} {len(info['cores']):>4d} 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()

View File

@@ -0,0 +1,194 @@
"""Scraper for RetroPie libretro core availability per platform.
Source: https://github.com/RetroPie/RetroPie-Setup/tree/master/scriptmodules/libretrocores
Parses rp_module_id and rp_module_flags from each scriptmodule to determine
which platforms each core supports.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.error
import urllib.request
from datetime import datetime, timezone
import yaml
from . import BaseTargetScraper
PLATFORM_NAME = "retropie"
GITHUB_API_URL = (
"https://api.github.com/repos/RetroPie/RetroPie-Setup/contents"
"/scriptmodules/libretrocores"
)
RAW_BASE_URL = (
"https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master"
"/scriptmodules/libretrocores/"
)
# Platform flag sets: flags that the platform possesses
PLATFORM_FLAGS: dict[str, set[str]] = {
"rpi1": {"arm", "armv6", "rpi", "gles"},
"rpi2": {"arm", "armv7", "neon", "rpi", "gles"},
"rpi3": {"arm", "armv8", "neon", "rpi", "gles"},
"rpi4": {"arm", "armv8", "neon", "rpi", "gles", "gles3", "gles31"},
"rpi5": {"arm", "armv8", "neon", "rpi", "gles", "gles3", "gles31"},
"x86": {"x86"},
"x86_64": {"x86"},
}
ARCH_MAP: dict[str, str] = {
"rpi1": "armv6",
"rpi2": "armv7",
"rpi3": "armv7",
"rpi4": "aarch64",
"rpi5": "aarch64",
"x86": "x86",
"x86_64": "x86_64",
}
# Flags that are build directives, not platform restrictions
_BUILD_FLAGS = {"nodistcc"}
_MODULE_ID_RE = re.compile(r'rp_module_id\s*=\s*["\']([^"\']+)["\']')
_MODULE_FLAGS_RE = re.compile(r'rp_module_flags\s*=\s*["\']([^"\']*)["\']')
def _fetch(url: str, accept: str = "text/plain") -> str | None:
try:
req = urllib.request.Request(
url,
headers={"User-Agent": "retrobios-scraper/1.0", "Accept": accept},
)
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
def _is_available(flags_str: str, platform: str) -> bool:
"""Return True if the core is available on the given platform."""
platform_has = PLATFORM_FLAGS.get(platform, set())
tokens = flags_str.split() if flags_str.strip() else []
for token in tokens:
if token in _BUILD_FLAGS:
continue
if token.startswith("!"):
# Exclusion: if platform has this flag, core is excluded
flag = token[1:]
if flag in platform_has:
return False
else:
# Requirement: platform must have this flag
if token not in platform_has:
return False
return True
def _parse_module(content: str) -> tuple[str | None, str]:
"""Return (module_id, flags_string) from a scriptmodule file."""
id_match = _MODULE_ID_RE.search(content)
flags_match = _MODULE_FLAGS_RE.search(content)
module_id = id_match.group(1) if id_match else None
flags = flags_match.group(1) if flags_match else ""
return module_id, flags
class Scraper(BaseTargetScraper):
"""Fetches RetroPie libretro core availability by parsing scriptmodules."""
def __init__(self, url: str = GITHUB_API_URL):
super().__init__(url=url)
def _list_scriptmodules(self) -> list[str]:
"""Return list of .sh filenames from the libretrocores directory."""
raw = _fetch(self.url, accept="application/vnd.github+json")
if raw is None:
return []
try:
entries = json.loads(raw)
except json.JSONDecodeError as e:
print(f" JSON parse error: {e}", file=sys.stderr)
return []
return [e["name"] for e in entries if e.get("name", "").endswith(".sh")]
def _fetch_module(self, filename: str) -> str | None:
return _fetch(f"{RAW_BASE_URL}{filename}")
def fetch_targets(self) -> dict:
print(" listing RetroPie scriptmodules...", file=sys.stderr)
filenames = self._list_scriptmodules()
if not filenames:
print(" warning: no scriptmodules found", file=sys.stderr)
# {platform: [core_id, ...]}
platform_cores: dict[str, list[str]] = {p: [] for p in PLATFORM_FLAGS}
for filename in filenames:
content = self._fetch_module(filename)
if content is None:
continue
module_id, flags = _parse_module(content)
if not module_id:
print(f" warning: no rp_module_id in {filename}", file=sys.stderr)
continue
# Normalize: strip lr- prefix and convert hyphens to underscores
# to match emulator profile keys (lr-beetle-psx -> beetle_psx)
core_name = module_id
if core_name.startswith("lr-"):
core_name = core_name[3:]
core_name = core_name.replace("-", "_")
for platform in PLATFORM_FLAGS:
if _is_available(flags, platform):
platform_cores[platform].append(core_name)
print(f" parsed {len(filenames)} scriptmodules", file=sys.stderr)
targets: dict[str, dict] = {}
for platform, arch in ARCH_MAP.items():
cores = sorted(platform_cores.get(platform, []))
targets[platform] = {
"architecture": arch,
"cores": cores,
}
return {
"platform": "retropie",
"source": self.url,
"scraped_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"targets": targets,
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Scrape RetroPie libretro core targets from scriptmodules"
)
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()

View File

@@ -37,9 +37,10 @@ sys.path.insert(0, os.path.dirname(__file__))
from common import (
_build_validation_index, build_zip_contents_index, check_file_validation,
check_inside_zip, compute_hashes, filter_files_by_mode,
group_identical_platforms, list_emulator_profiles, list_system_ids,
load_data_dir_registry, load_emulator_profiles, load_platform_config,
md5sum, md5_composite, resolve_local_file, resolve_platform_cores,
filter_systems_by_target, group_identical_platforms, list_emulator_profiles,
list_system_ids, load_data_dir_registry, load_emulator_profiles,
load_platform_config, md5sum, md5_composite, resolve_local_file,
resolve_platform_cores,
)
DEFAULT_DB = "database.json"
DEFAULT_PLATFORMS_DIR = "platforms"
@@ -205,6 +206,7 @@ def find_undeclared_files(
emulators_dir: str,
db: dict,
emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
) -> list[dict]:
"""Find files needed by cores but not declared in platform config."""
# Collect all filenames declared by this platform
@@ -226,7 +228,7 @@ def find_undeclared_files(
by_name = db.get("indexes", {}).get("by_name", {})
profiles = emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir)
relevant = resolve_platform_cores(config, profiles)
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
standalone_set = set(str(c) for c in config.get("standalone_cores", []))
undeclared = []
seen = set()
@@ -278,6 +280,7 @@ def find_undeclared_files(
def find_exclusion_notes(
config: dict, emulators_dir: str, emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
) -> list[dict]:
"""Document why certain emulator files are intentionally excluded.
@@ -292,7 +295,7 @@ def find_exclusion_notes(
for sys_id in config.get("systems", {}):
platform_systems.add(sys_id)
relevant = resolve_platform_cores(config, profiles)
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
notes = []
for emu_name, profile in sorted(profiles.items()):
emu_systems = set(profile.get("systems", []))
@@ -368,6 +371,7 @@ def verify_platform(
config: dict, db: dict,
emulators_dir: str = DEFAULT_EMULATORS_DIR,
emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
) -> dict:
"""Verify all BIOS files for a platform, including cross-reference gaps."""
mode = config.get("verification_mode", "existence")
@@ -389,6 +393,13 @@ def verify_platform(
hle_index[f.get("name", "")] = True
validation_index = _build_validation_index(profiles)
# Filter systems by target
plat_cores = resolve_platform_cores(config, profiles) if target_cores else None
verify_systems = filter_systems_by_target(
config.get("systems", {}), profiles, target_cores,
platform_cores=plat_cores,
)
# Per-entry results
details = []
# Per-destination aggregation
@@ -396,7 +407,7 @@ def verify_platform(
file_required: dict[str, bool] = {}
file_severity: dict[str, str] = {}
for sys_id, system in config.get("systems", {}).items():
for sys_id, system in verify_systems.items():
for file_entry in system.get("files", []):
local_path, resolve_status = resolve_local_file(
file_entry, db, zip_contents,
@@ -452,8 +463,8 @@ def verify_platform(
status_counts[s] = status_counts.get(s, 0) + 1
# Cross-reference undeclared files
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles)
exclusions = find_exclusion_notes(config, emulators_dir, emu_profiles)
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles, target_cores=target_cores)
exclusions = find_exclusion_notes(config, emulators_dir, emu_profiles, target_cores=target_cores)
return {
"platform": platform,
@@ -866,6 +877,8 @@ def main():
parser.add_argument("--list-emulators", action="store_true", help="List available emulators")
parser.add_argument("--list-systems", action="store_true", help="List available systems")
parser.add_argument("--include-archived", action="store_true")
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
parser.add_argument("--list-targets", action="store_true", help="List available targets for the platform")
parser.add_argument("--db", default=DEFAULT_DB)
parser.add_argument("--platforms-dir", default=DEFAULT_PLATFORMS_DIR)
parser.add_argument("--emulators-dir", default=DEFAULT_EMULATORS_DIR)
@@ -879,6 +892,19 @@ def main():
list_system_ids(args.emulators_dir)
return
if args.list_targets:
if not args.platform:
parser.error("--list-targets requires --platform")
from common import list_available_targets
targets = list_available_targets(args.platform, args.platforms_dir)
if not targets:
print(f"No targets configured for platform '{args.platform}'")
return
for t in targets:
aliases = f" (aliases: {', '.join(t['aliases'])})" if t['aliases'] else ""
print(f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}")
return
# Mutual exclusion
modes = sum(1 for x in (args.platform, args.all, args.emulator, args.system) if x)
if modes == 0:
@@ -887,6 +913,10 @@ def main():
parser.error("--platform, --all, --emulator, and --system are mutually exclusive")
if args.standalone and not (args.emulator or args.system):
parser.error("--standalone requires --emulator or --system")
if args.target and not (args.platform or args.all):
parser.error("--target requires --platform or --all")
if args.target and (args.emulator or args.system):
parser.error("--target is incompatible with --emulator and --system")
with open(args.db) as f:
db = json.load(f)
@@ -926,13 +956,37 @@ def main():
# Load emulator profiles once for cross-reference (not per-platform)
emu_profiles = load_emulator_profiles(args.emulators_dir)
target_cores_cache: dict[str, set[str] | None] = {}
if args.target:
from common import load_target_config
skip = []
for p in platforms:
try:
target_cores_cache[p] = load_target_config(p, args.target, args.platforms_dir)
except FileNotFoundError:
if args.all:
target_cores_cache[p] = None
else:
print(f"ERROR: No target config for platform '{p}'", file=sys.stderr)
sys.exit(1)
except ValueError as e:
if args.all:
print(f"INFO: Skipping {p}: {e}")
skip.append(p)
else:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
platforms = [p for p in platforms if p not in skip]
# Group identical platforms (same function as generate_pack)
groups = group_identical_platforms(platforms, args.platforms_dir)
groups = group_identical_platforms(platforms, args.platforms_dir,
target_cores_cache if args.target else None)
all_results = {}
group_results: list[tuple[dict, list[str]]] = []
for group_platforms, representative in groups:
config = load_platform_config(representative, args.platforms_dir)
result = verify_platform(config, db, args.emulators_dir, emu_profiles)
tc = target_cores_cache.get(representative) if args.target else None
result = verify_platform(config, db, args.emulators_dir, emu_profiles, target_cores=tc)
names = [load_platform_config(p, args.platforms_dir).get("platform", p) for p in group_platforms]
group_results.append((result, names))
for p in group_platforms:

View File

@@ -1175,6 +1175,183 @@ class TestE2E(unittest.TestCase):
resolved = resolve_platform_cores(config, profiles)
self.assertIn("test_alias_core", resolved)
# ---------------------------------------------------------------
# Target config tests (Task 1)
# ---------------------------------------------------------------
def _write_target_fixtures(self):
"""Create target config fixtures for testing."""
targets_dir = os.path.join(self.platforms_dir, "targets")
os.makedirs(targets_dir, exist_ok=True)
target_config = {
"platform": "testplatform",
"source": "test",
"scraped_at": "2026-01-01T00:00:00Z",
"targets": {
"target-full": {
"architecture": "x86_64",
"cores": ["core_a", "core_b", "core_c"],
},
"target-minimal": {
"architecture": "armv7",
"cores": ["core_a"],
},
},
}
with open(os.path.join(targets_dir, "testplatform.yml"), "w") as f:
yaml.dump(target_config, f)
single_config = {
"platform": "singleplatform",
"source": "test",
"scraped_at": "2026-01-01T00:00:00Z",
"targets": {
"only-target": {
"architecture": "x86_64",
"cores": ["core_a", "core_b"],
},
},
}
with open(os.path.join(targets_dir, "singleplatform.yml"), "w") as f:
yaml.dump(single_config, f)
overrides = {
"testplatform": {
"targets": {
"target-full": {
"aliases": ["full", "pc", "desktop"],
"add_cores": ["core_d"],
"remove_cores": ["core_c"],
},
"target-minimal": {
"aliases": ["minimal", "arm"],
},
},
},
}
with open(os.path.join(targets_dir, "_overrides.yml"), "w") as f:
yaml.dump(overrides, f)
def test_load_target_config(self):
self._write_target_fixtures()
from common import load_target_config
cores = load_target_config("testplatform", "target-minimal", self.platforms_dir)
self.assertEqual(cores, {"core_a"})
def test_target_alias_resolution(self):
self._write_target_fixtures()
from common import load_target_config
cores = load_target_config("testplatform", "full", self.platforms_dir)
self.assertEqual(cores, {"core_a", "core_b", "core_d"})
def test_target_unknown_error(self):
self._write_target_fixtures()
from common import load_target_config
with self.assertRaises(ValueError) as ctx:
load_target_config("testplatform", "nonexistent", self.platforms_dir)
self.assertIn("target-full", str(ctx.exception))
self.assertIn("target-minimal", str(ctx.exception))
def test_target_override_add_remove(self):
self._write_target_fixtures()
from common import load_target_config
cores = load_target_config("testplatform", "full", self.platforms_dir)
self.assertIn("core_d", cores)
self.assertNotIn("core_c", cores)
self.assertIn("core_a", cores)
self.assertIn("core_b", cores)
def test_target_single_target_noop(self):
self._write_target_fixtures()
from common import load_target_config
cores = load_target_config("singleplatform", "only-target", self.platforms_dir)
self.assertEqual(cores, {"core_a", "core_b"})
def test_target_inherits(self):
self._write_target_fixtures()
targets_dir = os.path.join(self.platforms_dir, "targets")
child_config = {
"platform": "childplatform",
"source": "test",
"scraped_at": "2026-01-01T00:00:00Z",
"targets": {
"target-full": {
"architecture": "x86_64",
"cores": ["core_a"],
},
},
}
with open(os.path.join(targets_dir, "childplatform.yml"), "w") as f:
yaml.dump(child_config, f)
from common import load_target_config
parent = load_target_config("testplatform", "target-minimal", self.platforms_dir)
child = load_target_config("childplatform", "target-full", self.platforms_dir)
self.assertEqual(parent, {"core_a"})
self.assertEqual(child, {"core_a"})
self.assertNotEqual(
load_target_config("testplatform", "full", self.platforms_dir),
child,
)
# ---------------------------------------------------------------
# Target filtering in resolve_platform_cores (Task 2)
# ---------------------------------------------------------------
def test_target_core_intersection(self):
self._write_target_fixtures()
profiles = {
"core_a": {"type": "libretro", "systems": ["sys1"]},
"core_b": {"type": "libretro", "systems": ["sys1"]},
"core_c": {"type": "libretro", "systems": ["sys2"]},
"core_d": {"type": "libretro", "systems": ["sys2"]},
}
config = {"cores": "all_libretro"}
result = resolve_platform_cores(config, profiles)
self.assertEqual(result, {"core_a", "core_b", "core_c", "core_d"})
result = resolve_platform_cores(config, profiles, target_cores={"core_a", "core_b"})
self.assertEqual(result, {"core_a", "core_b"})
def test_target_none_no_filter(self):
profiles = {
"core_a": {"type": "libretro", "systems": ["sys1"]},
"core_b": {"type": "libretro", "systems": ["sys1"]},
}
config = {"cores": "all_libretro"}
result = resolve_platform_cores(config, profiles, target_cores=None)
self.assertEqual(result, {"core_a", "core_b"})
def test_verify_target_filtered(self):
"""Verify with target_cores only reports files from filtered cores."""
self._write_target_fixtures()
core_a_path = os.path.join(self.emulators_dir, "core_a.yml")
core_b_path = os.path.join(self.emulators_dir, "core_b.yml")
with open(core_a_path, "w") as f:
yaml.dump({
"emulator": "CoreA", "type": "libretro", "systems": ["sys1"],
"files": [{"name": "bios_a.bin", "required": True}],
}, f)
with open(core_b_path, "w") as f:
yaml.dump({
"emulator": "CoreB", "type": "libretro", "systems": ["sys1"],
"files": [{"name": "bios_b.bin", "required": True}],
}, f)
config = {"cores": "all_libretro", "systems": {"sys1": {"files": []}}}
profiles = load_emulator_profiles(self.emulators_dir)
# Without target: both cores' files are undeclared
undeclared = find_undeclared_files(config, self.emulators_dir, self.db, profiles)
names = {u["name"] for u in undeclared}
self.assertIn("bios_a.bin", names)
self.assertIn("bios_b.bin", names)
# With target filtering to core_a only
undeclared = find_undeclared_files(
config, self.emulators_dir, self.db, profiles,
target_cores={"core_a"},
)
names = {u["name"] for u in undeclared}
self.assertIn("bios_a.bin", names)
self.assertNotIn("bios_b.bin", names)
if __name__ == "__main__":
unittest.main()