20 Commits

Author SHA1 Message Date
Abdessamad Derraz
caf6285a04 fix: skip entries without md5 in batocera and retrobat exports 2026-03-30 17:46:48 +02:00
Abdessamad Derraz
529cb8a915 fix: recalbox paths from scrape, batocera md5 fallback from scrape 2026-03-30 17:35:39 +02:00
Abdessamad Derraz
1146fdf177 fix: rewrite emudeck exporter to match exact checkBIOS.sh format 2026-03-30 17:21:56 +02:00
Abdessamad Derraz
4fbb3571f8 fix: exporters use _dest fallback, merge colliding systems, per-platform subdirs 2026-03-30 17:15:44 +02:00
Abdessamad Derraz
0be68edad0 feat: add exporters for lakka, retropie, emudeck, retrodeck, romm 2026-03-30 17:07:08 +02:00
Abdessamad Derraz
1ffc4f89ca refactor: registry merge is fully flexible, no hardcoded lists 2026-03-30 16:38:13 +02:00
Abdessamad Derraz
f1ebfff5bd refactor: registry merge uses exclusion list instead of hardcoded fields 2026-03-30 16:36:40 +02:00
Abdessamad Derraz
425ea064ae fix: scrapers merge into existing YAML instead of overwriting 2026-03-30 16:31:40 +02:00
Abdessamad Derraz
6818a18a42 feat: load_platform_config merges all metadata from registry 2026-03-30 16:24:40 +02:00
Abdessamad Derraz
c11de6dba6 fix: restore retroarch.yml fields lost by scraper regeneration 2026-03-30 16:22:16 +02:00
Abdessamad Derraz
c4f3192020 fix: system.dat rom quoting, native_ids, acronym display names 2026-03-30 16:17:50 +02:00
Abdessamad Derraz
e2d0510f4e fix: exporters match exact native formats with display names 2026-03-30 16:09:02 +02:00
Abdessamad Derraz
74269bab84 fix: rewrite exporters to match exact native formats 2026-03-30 15:49:33 +02:00
Abdessamad Derraz
1e6b499602 feat: add batocera, recalbox, retrobat native exporters 2026-03-30 15:31:44 +02:00
Abdessamad Derraz
9b785ec785 feat: add missing laseractive sega pac bios files
v1.05 japan and v1.01 japan from archive.org.
v1.04 us variant alias for pioneer-named file.
resolves retrobat laseractive missing files.
2026-03-30 15:16:23 +02:00
Abdessamad Derraz
d415777f2c feat: add PS3UPDAT.PUP to rpcs3 profile
storage: large_file, validated via SCEUF magic + HMAC-SHA1
per entry (PUP.cpp:23-77). also adds missing standard fields
(cores, core_classification, upstream), removes non-standard
firmware_file/validation/firmware_version fields.
2026-03-30 15:06:51 +02:00
Abdessamad Derraz
eafabd20f3 refactor: skip writing generated files when content unchanged
write_if_changed in common.py compares content after stripping
timestamps (generated_at, Auto-generated on, Generated on).
applied to generate_db, generate_readme, generate_site.
eliminates timestamp-only diffs in database.json, README.md,
mkdocs.yml, and 423 docs pages.
2026-03-30 14:33:44 +02:00
Abdessamad Derraz
2aca4927c0 chore: regenerate database, readme, manifests, site 2026-03-30 14:19:00 +02:00
Abdessamad Derraz
17777f315b feat: agnostic bios mode for filename-agnostic emulators
bios_mode: agnostic (profile) and agnostic: true (file) for
emulators that accept any valid BIOS without specific filename.
find_undeclared_files skips agnostic entries, pack extras scan
includes all matching DB files by path prefix + size criteria,
resolve_local_file has agnostic fallback with rename README.
applied to pcsx2, lrps2 (bios_mode), melonds dsi_nand (file).
2026-03-30 14:18:54 +02:00
Abdessamad Derraz
692484d32d refactor: remove false aliases from pcsx2 and melonds profiles
aliases must be same-SHA1 alternative names, not distinct files.
pcsx2: 164 different BIOS dumps are separate DB entries, not aliases.
melonds: 6 regional NAND dumps are separate DB entries, not aliases.
also cleans pcsx2 non-standard fields, fixes display_name.
2026-03-30 12:37:32 +02:00
43 changed files with 1887 additions and 339 deletions

View File

@@ -130,4 +130,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
This repository provides BIOS files for personal backup and archival purposes.
*Auto-generated on 2026-03-30T07:40:45Z*
*Auto-generated on 2026-03-30T12:09:51Z*

View File

@@ -1,7 +1,7 @@
{
"generated_at": "2026-03-30T07:24:43Z",
"total_files": 7239,
"total_size": 8539795099,
"generated_at": "2026-03-30T13:15:58Z",
"total_files": 7241,
"total_size": 8540057243,
"files": {
"520d3d1b5897800af47f92efd2444a26b7a7dead": {
"path": "bios/3DO Company/3DO/3do_arcade_saot.bin",
@@ -40843,16 +40843,6 @@
"crc32": "11647ca5",
"adler32": "1817f6f4"
},
"26237b333db4a4c6770297fa5e655ea95840d5d9": {
"path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin",
"name": "Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin",
"size": 131072,
"sha1": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"md5": "a5a2f9aae57d464bc66b80ee79c3da6e",
"sha256": "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94",
"crc32": "00eedb3a",
"adler32": "94a45a21"
},
"aa811861f8874775075bd3f53008c8aaf59b07db": {
"path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.04 (1993)(Pioneer - Sega)(US).bin",
"name": "Pioneer LaserActive Sega PAC Boot ROM v1.04 (1993)(Pioneer - Sega)(US).bin",
@@ -40863,6 +40853,16 @@
"crc32": "50cd3d23",
"adler32": "72a13133"
},
"26237b333db4a4c6770297fa5e655ea95840d5d9": {
"path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin",
"name": "Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin",
"size": 131072,
"sha1": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"md5": "a5a2f9aae57d464bc66b80ee79c3da6e",
"sha256": "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94",
"crc32": "00eedb3a",
"adler32": "94a45a21"
},
"6973e2593e66fd21627fedccec98d4a364afaaff": {
"path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin",
"name": "[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin",
@@ -40883,6 +40883,26 @@
"crc32": "01223dd5",
"adler32": "74e98625"
},
"bc746df0c5d2b779ca41a94954b60d6ac6a7c2a3": {
"path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-S1 (Japan) (v1.01).bin",
"name": "[BIOS] LaserActive PAC-S1 (Japan) (v1.01).bin",
"size": 131072,
"sha1": "bc746df0c5d2b779ca41a94954b60d6ac6a7c2a3",
"md5": "219dc2aa7cb8d1ad5142c86d50c2ffa5",
"sha256": "3300e3771b468b41818b90c2358ff288da69bada92b8247acd793a437b30731c",
"crc32": "24ad336e",
"adler32": "6993a1dd"
},
"f7101b40cae0484a0f43f3bbee2d55033ff1e3ec": {
"path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-S1 (Japan) (v1.05).bin",
"name": "[BIOS] LaserActive PAC-S1 (Japan) (v1.05).bin",
"size": 131072,
"sha1": "f7101b40cae0484a0f43f3bbee2d55033ff1e3ec",
"md5": "515636778430c8e01027234c38c4ddf8",
"sha256": "369c1e699abbaecd231a3ab7b98443d28097366ff3cdd978698417195d54ca3c",
"crc32": "1493522c",
"adler32": "9e972082"
},
"c1b9202cbe072db12114b223a9ba5374b30718fb": {
"path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PCE-LP1 (Japan) (v1.02).bin",
"name": "[BIOS] LaserActive PCE-LP1 (Japan) (v1.02).bin",
@@ -76480,10 +76500,12 @@
"c500ff71236068e0dc0d0603d265ae76": "5130243429b40b01a14e1304d0394b8459a6fbae",
"f1071cdb0b6b10dde94d3bc8a6146387": "a6120aed50831c9c0d95dbdf707820f601d9452e",
"279008e4a0db2dc5f1c048853b033828": "54b8d2c1317628de51a85fc1c424423a986775e4",
"a5a2f9aae57d464bc66b80ee79c3da6e": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"0e7393cd0951d6dde818fcd4cd819466": "aa811861f8874775075bd3f53008c8aaf59b07db",
"a5a2f9aae57d464bc66b80ee79c3da6e": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"f69f173b251d8bf7649b10a9167a10bf": "6973e2593e66fd21627fedccec98d4a364afaaff",
"f0fb8a4605ac7eefbafd4f2d5a793cc8": "f7412aa822d70a55b2ff3d7095137263dc54f6b6",
"219dc2aa7cb8d1ad5142c86d50c2ffa5": "bc746df0c5d2b779ca41a94954b60d6ac6a7c2a3",
"515636778430c8e01027234c38c4ddf8": "f7101b40cae0484a0f43f3bbee2d55033ff1e3ec",
"761fea207d0eafd4cfd78da7c44cac88": "c1b9202cbe072db12114b223a9ba5374b30718fb",
"69489153dde910a69d5ae6de5dd65323": "f2a9ce387019bf272c6e3459d961b30f28942ac5",
"3fd0d13282b031f4c017cd6bf6597183": "9dda4cbcc1d3f6d38c10fee3de53b9abd5e47ec0",
@@ -91151,18 +91173,24 @@
"jopac.bin": [
"54b8d2c1317628de51a85fc1c424423a986775e4"
],
"Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin": [
"26237b333db4a4c6770297fa5e655ea95840d5d9"
],
"Pioneer LaserActive Sega PAC Boot ROM v1.04 (1993)(Pioneer - Sega)(US).bin": [
"aa811861f8874775075bd3f53008c8aaf59b07db"
],
"Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin": [
"26237b333db4a4c6770297fa5e655ea95840d5d9"
],
"[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin": [
"6973e2593e66fd21627fedccec98d4a364afaaff"
],
"[BIOS] LaserActive PAC-N10 (US) (v1.02).bin": [
"f7412aa822d70a55b2ff3d7095137263dc54f6b6"
],
"[BIOS] LaserActive PAC-S1 (Japan) (v1.01).bin": [
"bc746df0c5d2b779ca41a94954b60d6ac6a7c2a3"
],
"[BIOS] LaserActive PAC-S1 (Japan) (v1.05).bin": [
"f7101b40cae0484a0f43f3bbee2d55033ff1e3ec"
],
"[BIOS] LaserActive PCE-LP1 (Japan) (v1.02).bin": [
"c1b9202cbe072db12114b223a9ba5374b30718fb"
],
@@ -101218,6 +101246,9 @@
"g7400.bin": [
"5130243429b40b01a14e1304d0394b8459a6fbae"
],
"[BIOS] LaserActive PAC-S10 (US) (v1.04).bin": [
"aa811861f8874775075bd3f53008c8aaf59b07db"
],
"Battle 1.mid": [
"dc5cc32fafa442b09ab2d814ed32074d02597234"
],
@@ -107490,10 +107521,12 @@
"e20a9f41": "5130243429b40b01a14e1304d0394b8459a6fbae",
"a318e8d6": "a6120aed50831c9c0d95dbdf707820f601d9452e",
"11647ca5": "54b8d2c1317628de51a85fc1c424423a986775e4",
"00eedb3a": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"50cd3d23": "aa811861f8874775075bd3f53008c8aaf59b07db",
"00eedb3a": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"a8cb694c": "6973e2593e66fd21627fedccec98d4a364afaaff",
"01223dd5": "f7412aa822d70a55b2ff3d7095137263dc54f6b6",
"24ad336e": "bc746df0c5d2b779ca41a94954b60d6ac6a7c2a3",
"1493522c": "f7101b40cae0484a0f43f3bbee2d55033ff1e3ec",
"76116a02": "c1b9202cbe072db12114b223a9ba5374b30718fb",
"4e70e3c0": "f2a9ce387019bf272c6e3459d961b30f28942ac5",
"92bcc762": "9dda4cbcc1d3f6d38c10fee3de53b9abd5e47ec0",
@@ -124965,6 +124998,9 @@
"roms/win486/ALI1429G.AMW": [
"72c60172fb1ba77c9b24b06b7755f0a16f0b3a13"
],
".variants/[BIOS] LaserActive PAC-S10 (US) (v1.04).bin": [
"aa811861f8874775075bd3f53008c8aaf59b07db"
],
"rtp/2003/Battle/Arrow.png": [
"7aa8d4c377efcea1c9fad01924da1ba7b8575e1e"
],

View File

@@ -5,6 +5,7 @@
emulator: LRPS2
type: libretro
core_classification: community_fork
bios_mode: agnostic
source: "https://github.com/libretro/ps2"
upstream: "https://github.com/PCSX2/pcsx2"
profiled_date: "2026-03-25"

View File

@@ -77,7 +77,7 @@ files:
source_ref: "src/SPI.cpp:197-211, src/frontend/Util_ROM.cpp:201-217"
- name: dsi_nand.bin
aliases: [DSi_Nand_USA.bin, DSi_Nand_EUR.bin, DSi_Nand_JPN.bin, DSi_Nand_AUS.bin, DSi_Nand_CHN.bin, DSi_Nand_KOR.bin]
agnostic: true
system: nintendo-dsi
description: "DSi NAND dump"
required: true

View File

@@ -1,219 +1,74 @@
# PCSX2 emulator BIOS profile
# Generated from source analysis of https://github.com/PCSX2/pcsx2
# Commit analyzed: HEAD as of 2026-03-17
emulator: PCSX2
type: standalone
core_classification: official_port
bios_mode: agnostic
source: "https://github.com/PCSX2/pcsx2"
logo: "https://raw.githubusercontent.com/PCSX2/pcsx2/master/pcsx2-qt/resources/icons/PCSX2logo.svg"
profiled_date: "2026-03-18"
upstream: "https://github.com/PCSX2/pcsx2"
cores:
- pcsx2
profiled_date: "2026-03-30"
core_version: "Git"
display_name: "Sony - PlayStation 2 (LRPS2)"
display_name: "Sony - PlayStation 2 (PCSX2)"
systems: [sony-playstation-2]
bios_directory: "bios/"
bios_detection: "romdir" # scans romdir structure inside binary, looks for RESET/ROMVER/EXTINFO entries
bios_selection: "automatic" # scans all files in bios dir matching 4-8 MB size, validates via romdir
validation:
method: "romdir_parse"
min_size: 4194304 # 4 MB (MIN_BIOS_SIZE = 4 * _1mb)
max_size: 8388608 # 8 MB (MAX_BIOS_SIZE = 8 * _1mb)
required_entries: ["RESET", "ROMVER"]
optional_entries: ["EXTINFO"]
note: "Any file in bios/ between 4-8 MB with valid romdir containing RESET+ROMVER is accepted"
regions:
J: {zone: "Japan", id: 0}
A: {zone: "USA", id: 1}
E: {zone: "Europe", id: 2}
H: {zone: "Asia", id: 4}
C: {zone: "China", id: 6}
T: {zone: "T10K/COH-H", id: 8}
X: {zone: "Test", id: 9}
P: {zone: "Free", id: 10}
notes: |
Filename-agnostic BIOS detection. Scans bios/ for any file between 4-8 MB
with valid romdir structure (RESET + ROMVER entries). No hash validation.
Companion files (.rom1, .rom2, .nvm, .mec) derive paths from selected BIOS.
ROM1 (DVD player) and ROM2 (Chinese extension) silently skipped if missing.
NVM and MEC auto-created with defaults if missing.
files:
# -- Main BIOS binary (required) --
- name: "<user-selected>.bin"
pattern: "*"
- name: ps2-0230a-20080220.bin
required: true
size_range: "4MB-8MB"
source_ref: "pcsx2/ps2/BiosTools.cpp:258-282"
note: >
PCSX2 does not mandate a specific filename. It scans the entire bios/ directory
for any file between 4-8 MB that contains a valid romdir structure (RESET + ROMVER entries).
Common filenames follow the SCPH-XXXXX_BIOS_VYYY_REGION_ZZZ.BIN convention but this is
not enforced. The file is loaded into the 4 MB ROM region of EE memory.
min_size: 4194304
max_size: 8388608
validation: [size]
source_ref: "pcsx2/ps2/BiosTools.cpp:258-362"
note: "Accepts any file 4-8 MB with valid romdir (RESET + ROMVER). Naming convention ps2-VVVVr-YYYYMMDD.bin (version, region, date)."
# -- ROM1 (optional, DVD player) --
- name: "<biosname>.rom1"
pattern: "{biosname}.rom1 or {biosbase}.rom1"
- name: rom1.bin
required: false
max_size: 4194304 # 4 MB (Ps2MemSize::Rom1)
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241"
note: >
DVD player ROM. Loaded via LoadExtraRom("rom1"). PCSX2 tries two naming patterns:
1) Full bios path + ".rom1" appended (e.g. scph70004.bin.rom1)
2) Bios path with extension replaced (e.g. scph70004.rom1)
Mapped to EE memory at ROM1 region (0x1FC00000 + 4MB offset).
Contains DVD player and region detection data (DVDID).
max_size: 4194304
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241,366"
note: "DVD player ROM. Tries {biospath}.rom1 then {biosbase}.rom1. Silently skipped if missing."
# -- ROM2 (optional, Chinese ROM extension) --
- name: "<biosname>.rom2"
pattern: "{biosname}.rom2 or {biosbase}.rom2"
- name: ROM2.BIN
required: false
max_size: 4194304 # 4 MB (Ps2MemSize::Rom2)
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241"
note: >
Chinese ROM extension. Loaded via LoadExtraRom("rom2"). Same naming convention
as rom1: tries appended extension first, then replaced extension.
Only present on Chinese region consoles.
max_size: 4194304
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241,367"
note: "Chinese ROM extension. Same naming convention as rom1. Only present on Chinese region consoles."
# -- NVM / NVRAM (optional, auto-created) --
- name: "<biosname>.nvm"
pattern: "{biosbase}.nvm"
- name: EROM.BIN
required: false
source_ref: "pcsx2/ps2/BiosTools.cpp"
note: "Extended ROM. Present in some BIOS dumps but not loaded by PCSX2 code via LoadExtraRom."
path: null
- name: eeprom.dat
required: false
hle_fallback: true
size: 1024 # NVRAM_SIZE = 1024 bytes
source_ref: "pcsx2/CDVD/CDVD.cpp:160-238"
note: >
EEPROM / NVRAM data. Path derived from BiosPath with extension replaced to ".nvm"
(cdvdGetNVRAMPath). Contains console configuration: language, timezone, iLink ID,
region parameters, OSD settings. Auto-created with defaults if missing.
Two NVM layouts exist: v0.00+ (biosVer 0x000) and v1.70+ (biosVer 0x146).
# -- MEC file (optional, auto-created) --
- name: "<biosname>.mec"
pattern: "{biosbase}.mec"
required: false
hle_fallback: true
size: 4 # u32 s_mecha_version
source_ref: "pcsx2/CDVD/CDVD.cpp:190-204"
note: >
Mechacon (mechanism controller) version file. 4 bytes containing the mecha version
as a u32 value. Auto-created with DEFAULT_MECHA_VERSION (0x00020603) if missing.
Path derived from BiosPath with extension replaced to ".mec".
# -- IRX override (optional, advanced) --
- name: "<custom>.irx"
pattern: "*.irx"
required: false
source_ref: "pcsx2/ps2/BiosTools.cpp:243-256,384-385"
note: >
Custom IOP Reboot eXecutable module. Loaded into ROM at offset 0x3C0000 if
EmuConfig.CurrentIRX is set (path length > 3). Injected at IOP reset (PC=0x1630).
Used for debugging/development, not needed for normal operation.
# -- DEV9 EEPROM (optional, network adapter) --
- name: "eeprom.dat"
required: false
hle_fallback: true
size: 64 # 64 bytes, mmap'd
size: 64
source_ref: "pcsx2/DEV9/DEV9.cpp:110-160"
note: >
DEV9 (network adapter / HDD expansion bay) EEPROM data. Fixed filename "eeprom.dat"
opened from working directory. Contains network adapter configuration.
Falls back to built-in defaults if file not found. Only relevant when using
DEV9 features (online play, HDD).
note: "DEV9 network adapter EEPROM. Falls back to built-in defaults if missing."
common_bios_filenames:
# Japan
- "SCPH-10000_BIOS_V1_JAP_100.BIN"
- "SCPH-15000_BIOS_V3_JAP_120.BIN"
- "SCPH-30000_BIOS_V4_JAP_150.BIN"
- "SCPH-30001R_BIOS_V7_JAP_160.BIN"
- "SCPH-30004R_BIOS_V7_JAP_160.BIN"
- "SCPH-35000_BIOS_V5_JAP_160.BIN"
- "SCPH-50000_BIOS_V9_JAP_170.BIN"
- "SCPH-50004_BIOS_V9_JAP_170.BIN"
- "SCPH-70000_BIOS_V12_JAP_200.BIN"
- "SCPH-75000_BIOS_V14_JAP_220.BIN"
- "SCPH-77000_BIOS_V14_JAP_220.BIN"
- "SCPH-90000_BIOS_V18_JAP_230.BIN"
# USA
- "SCPH-30001_BIOS_V4_USA_150.BIN"
- "SCPH-39001_BIOS_V6_USA_160.BIN"
- "SCPH-50001_BIOS_V9_USA_170.BIN"
- "SCPH-50003_BIOS_V9_USA_170.BIN"
- "SCPH-70002_BIOS_V12_USA_200.BIN"
- "SCPH-70004_BIOS_V12_USA_200.BIN"
- "SCPH-70012_BIOS_V12_USA_200.BIN"
- "SCPH-75001_BIOS_V14_USA_220.BIN"
- "SCPH-77001_BIOS_V14_USA_220.BIN"
- "SCPH-90001_BIOS_V18_USA_230.BIN"
# Europe
- "SCPH-30002_BIOS_V4_EUR_150.BIN"
- "SCPH-30003_BIOS_V4_EUR_150.BIN"
- "SCPH-30004_BIOS_V4_EUR_150.BIN"
- "SCPH-39002_BIOS_V6_EUR_160.BIN"
- "SCPH-39003_BIOS_V6_EUR_160.BIN"
- "SCPH-39004_BIOS_V6_EUR_160.BIN"
- "SCPH-50002_BIOS_V9_EUR_170.BIN"
- "SCPH-50004_BIOS_V9_EUR_170.BIN"
- "SCPH-70002_BIOS_V12_EUR_200.BIN"
- "SCPH-70003_BIOS_V12_EUR_200.BIN"
- "SCPH-70004_BIOS_V12_EUR_200.BIN"
- "SCPH-70008_BIOS_V12_EUR_200.BIN"
- "SCPH-75002_BIOS_V14_EUR_220.BIN"
- "SCPH-75003_BIOS_V14_EUR_220.BIN"
- "SCPH-75004_BIOS_V14_EUR_220.BIN"
- "SCPH-77002_BIOS_V14_EUR_220.BIN"
- "SCPH-77003_BIOS_V14_EUR_220.BIN"
- "SCPH-77004_BIOS_V14_EUR_220.BIN"
- "SCPH-90002_BIOS_V18_EUR_230.BIN"
- "SCPH-90003_BIOS_V18_EUR_230.BIN"
- "SCPH-90004_BIOS_V18_EUR_230.BIN"
# Asia
- "SCPH-50009_BIOS_V9_HK_170.BIN"
- "SCPH-70005_BIOS_V12_HK_200.BIN"
- "SCPH-70006_BIOS_V12_HK_200.BIN"
- "SCPH-70008_BIOS_V12_HK_200.BIN"
# China
- "SCPH-50009_BIOS_V9_CHN_170.BIN"
- "SCPH-70006_BIOS_V12_CHN_200.BIN"
- name: GameIndex.yaml
path: pcsx2/resources/GameIndex.yaml
required: false
mode: libretro
source_ref: "pcsx2/GameDatabase.cpp:48,880"
note: "Game compatibility database. OSD warning if missing."
memory_layout:
ROM: {offset: "0x1FC00000", size: "4 MB", purpose: "Main BIOS binary"}
ROM1: {offset: "ROM + 4MB", size: "4 MB", purpose: "DVD player"}
ROM2: {offset: "ROM + 8MB", size: "4 MB", purpose: "Chinese ROM extension"}
- name: cheats_ws.zip
path: pcsx2/resources/cheats_ws.zip
required: false
mode: libretro
source_ref: "pcsx2/VMManager.cpp:340-353"
note: "Widescreen patches archive."
nvm_layout:
format_0:
applies_to: "BIOS v0.00+"
biosVer: 0x000
config0: 0x280
config1: 0x300
config2: 0x200
consoleId: 0x1C8
ilinkId: 0x1C0
modelNum: 0x1A0
regparams: 0x180
mac: 0x198
format_1:
applies_to: "BIOS v1.70+"
biosVer: 0x146
config0: 0x270
config1: 0x2B0
config2: 0x200
consoleId: 0x1F0
ilinkId: 0x1E0
modelNum: 0x1B0
regparams: 0x180
mac: 0x198
notes: |
PCSX2 is filename-agnostic for the main BIOS. Detection relies on romdir structure
parsing inside the binary itself, not on filename or extension. Any file between 4-8 MB
with a valid romdir (containing at least RESET and ROMVER entries) is accepted.
The ROMVER entry encodes: version (2+2 digits), region letter, console/devel flag,
build date (YYYYMMDD), and is used to determine the BIOS description and region.
Companion files (.nvm, .mec) are auto-created with sane defaults if missing.
ROM1/ROM2 are silently skipped if not found - only the main BIOS binary is strictly required.
PCSX2 no longer ships as a libretro core in official builds. The standalone emulator
is the primary distribution channel.
Devel console BIOSes (< ~2.3 MB) lack the OSD and are handled with NoOSD=true flag.
- name: cheats_ni.zip
path: pcsx2/resources/cheats_ni.zip
required: false
mode: libretro
source_ref: "pcsx2/VMManager.cpp:375-388"
note: "No-interlacing patches archive."

View File

@@ -1,32 +1,21 @@
# RPCS3 emulator firmware profile
# Generated from source analysis of https://github.com/RPCS3/rpcs3
# Commit analyzed: HEAD as of 2026-03-17
emulator: RPCS3
type: standalone
core_classification: official_port
source: "https://github.com/RPCS3/rpcs3"
logo: "https://raw.githubusercontent.com/RPCS3/rpcs3/master/rpcs3/rpcs3.svg"
profiled_date: "2026-03-18"
upstream: "https://github.com/RPCS3/rpcs3"
cores:
- rpcs3
profiled_date: "2026-03-30"
core_version: "0.0.35"
display_name: "RPCS3 (PS3)"
display_name: "Sony - PlayStation 3 (RPCS3)"
systems: [sony-playstation-3]
firmware_file: "PS3UPDAT.PUP"
firmware_source: "https://www.playstation.com/en-us/support/hardware/ps3/system-software/"
firmware_detection: "pup_header" # validates PUP magic bytes, HMAC-SHA1 hash per entry
firmware_install: "extracts dev_flash_* TAR packages from PUP into dev_flash/"
validation:
method: "pup_object"
magic: "SCEUF"
hash_algo: "HMAC-SHA1"
source_ref: "rpcs3/Loader/PUP.cpp:8-114"
note: "PUP file is validated by magic header, file count, HMAC-SHA1 per entry against PUP_KEY"
firmware_version:
path: "dev_flash/vsh/etc/version.txt"
source_ref: "rpcs3/util/sysinfo.cpp:686"
note: "Read at startup, displayed as 'Firmware version: X.XX'. Missing = 'Missing Firmware'"
files:
- name: PS3UPDAT.PUP
required: true
storage: large_file
source_ref: "rpcs3/Loader/PUP.cpp:23-77"
note: "PUP firmware package. Validated via SCEUF magic + HMAC-SHA1 per entry. Extracted to dev_flash/ at install time."
# dev_flash filesystem layout extracted from PUP
dev_flash:

View File

@@ -3,7 +3,7 @@
"platform": "batocera",
"display_name": "Batocera",
"version": "1.0",
"generated": "2026-03-30T07:37:55Z",
"generated": "2026-03-30T09:46:19Z",
"base_destination": "bios",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "bizhawk",
"display_name": "BizHawk",
"version": "1.0",
"generated": "2026-03-30T07:37:59Z",
"generated": "2026-03-30T09:46:23Z",
"base_destination": "Firmware",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "emudeck",
"display_name": "EmuDeck",
"version": "1.0",
"generated": "2026-03-30T07:38:01Z",
"generated": "2026-03-30T09:46:25Z",
"base_destination": "bios",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "lakka",
"display_name": "Lakka",
"version": "1.0",
"generated": "2026-03-30T07:38:59Z",
"generated": "2026-03-30T09:47:34Z",
"base_destination": "system",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "recalbox",
"display_name": "Recalbox",
"version": "1.0",
"generated": "2026-03-30T07:39:27Z",
"generated": "2026-03-30T09:48:01Z",
"base_destination": "bios",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "retroarch",
"display_name": "RetroArch",
"version": "1.0",
"generated": "2026-03-30T07:38:59Z",
"generated": "2026-03-30T09:47:34Z",
"base_destination": "system",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "retrobat",
"display_name": "RetroBat",
"version": "1.0",
"generated": "2026-03-30T07:39:32Z",
"generated": "2026-03-30T09:48:07Z",
"base_destination": "bios",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "retrodeck",
"display_name": "RetroDECK",
"version": "1.0",
"generated": "2026-03-30T07:40:43Z",
"generated": "2026-03-30T09:49:19Z",
"base_destination": "",
"detect": [
{

View File

@@ -3,7 +3,7 @@
"platform": "romm",
"display_name": "RomM",
"version": "1.0",
"generated": "2026-03-30T07:40:45Z",
"generated": "2026-03-30T09:49:20Z",
"base_destination": "bios",
"detect": [
{

View File

@@ -132,7 +132,7 @@ nav:
- ZC: systems/zc.md
- Emulators:
- Overview: emulators/index.md
- Official ports (61):
- Official ports (62):
- amiarcadia: emulators/amiarcadia.md
- Amiberry: emulators/amiberry.md
- Ardens: emulators/ardens.md
@@ -176,6 +176,7 @@ nav:
- mGBA: emulators/mgba.md
- Mr.Boom: emulators/mrboom.md
- Panda3DS: emulators/panda3ds.md
- PCSX2: emulators/pcsx2.md
- PicoDrive: emulators/picodrive.md
- play: emulators/play.md
- PPSSPP: emulators/ppsspp.md
@@ -428,7 +429,7 @@ nav:
- PCSX-ReARMed: emulators/pcsx_rearmed.md
- Launchers (1):
- Dolphin Launcher: emulators/dolphin_launcher.md
- Other (25):
- Other (24):
- ares: emulators/ares.md
- Beetle GBA (Mednafen): emulators/beetle_gba.md
- BigPEmu: emulators/bigpemu.md
@@ -440,7 +441,6 @@ nav:
- 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

View File

@@ -7,6 +7,9 @@ platforms:
source_url: https://raw.githubusercontent.com/libretro/libretro-database/master/dat/System.dat
source_format: clrmamepro_dat
hash_type: sha1
verification_mode: existence
base_destination: system
case_insensitive_fs: true
schedule: weekly
cores: all_libretro
target_scraper: retroarch_targets

View File

@@ -183,6 +183,7 @@ systems:
required: true
md5: 0bac0c6a50104045d902df4503a4c30b
native_id: atari800
name: Atari 800
atari-5200:
files:
- name: 5200.rom
@@ -190,6 +191,7 @@ systems:
required: true
md5: 281f20ea4320404ec820fb7ec0693b38
native_id: atari5200
name: Atari 5200
atari-st:
files:
- name: tos.img
@@ -337,6 +339,7 @@ systems:
required: true
md5: e5ea0f216fb446f1c4a4f476bc5f03d4
native_id: atarist
name: Atari ST
atari-lynx:
files:
- name: lynxboot.img
@@ -344,6 +347,7 @@ systems:
required: true
md5: fcd403db69f54290b51035d82f835e7b
native_id: lynx
name: Lynx
magnavox-odyssey2:
files:
- name: o2rom.bin
@@ -355,6 +359,7 @@ systems:
required: true
md5: f1071cdb0b6b10dde94d3bc8a6146387
native_id: odyssey2
name: Odyssey 2
videopacplus:
files:
- name: g7400.bin
@@ -366,6 +371,7 @@ systems:
required: true
md5: 279008e4a0db2dc5f1c048853b033828
native_id: videopacplus
name: Videopac+ G7400
mattel-intellivision:
files:
- name: exec.bin
@@ -377,6 +383,7 @@ systems:
required: true
md5: 0cd5946c6473e42e8e4c2137785e427f
native_id: intellivision
name: Mattel Intellivision
nec-pc-engine:
files:
- name: syscard3.pce
@@ -388,6 +395,7 @@ systems:
required: true
md5: 38179df8f4ac870017db21ebcbf53114
native_id: pcengine
name: Supergrafx
nec-pc-fx:
files:
- name: pcfx.rom
@@ -395,6 +403,7 @@ systems:
required: true
md5: 08e36edbea28a017f79f8d4f7ff9b6d7
native_id: pcfx
name: PC-FX
snk-neogeo:
files:
- name: neogeo.zip
@@ -402,6 +411,7 @@ systems:
required: true
md5: dffb72f116d36d025068b23970a4f6df
native_id: neogeo
name: NeoGeo
snk-neogeo-cd:
files:
- name: neocd_f.rom
@@ -445,6 +455,7 @@ systems:
required: true
md5: 08ca8b2dba6662e8024f9e789711c6fc
native_id: neogeocd
name: NeoGeo CD
sharp-x68000:
files:
- name: iplrom.dat
@@ -456,6 +467,7 @@ systems:
required: true
md5: cb0a5cfcf7247a7eab74bb2716260269
native_id: x68000
name: Sharp x68000
3do:
files:
- name: panafz1.bin
@@ -471,6 +483,7 @@ systems:
required: true
md5: 8639fd5e549bd6238cfee79e3e749114
native_id: 3do
name: 3DO
sega-dreamcast:
files:
- name: dc_boot.bin
@@ -478,6 +491,7 @@ systems:
required: true
md5: e10c53c2f8b90bab96ead2d368858623
native_id: dreamcast
name: Dreamcast
sega-dreamcast-arcade:
files:
- name: naomi.zip
@@ -597,6 +611,7 @@ systems:
md5: 960ece0dc22a7c5ff81c812a2993e7cc
zipped_file: x76f100_eeprom.bin
native_id: naomi
name: Naomi 2
sega-mega-cd:
files:
- name: bios_CD_E.bin
@@ -612,6 +627,7 @@ systems:
required: true
md5: 278a9397d192149e84e820ac621a8edd
native_id: segacd
name: Sega CD
megadrive-msu:
files:
- name: bios_CD_E.bin
@@ -627,6 +643,7 @@ systems:
required: true
md5: 278a9397d192149e84e820ac621a8edd
native_id: megadrive-msu
name: MSU-MD
sega-saturn:
files:
- name: sega_101.bin
@@ -650,6 +667,7 @@ systems:
required: true
md5: af5828fdff51384f99b3c4926be27762
native_id: saturn
name: Sega Saturn
sony-playstation:
files:
- name: psxonpsp660.bin
@@ -681,6 +699,7 @@ systems:
required: true
md5: 1e68c231d0896b7eadcad1d7d8e76129
native_id: psx
name: PSX
sony-playstation-2:
files:
- name: ps2-0230a-20080220.bin
@@ -688,6 +707,7 @@ systems:
required: true
md5: 21038400dc633070a78ad53090c53017
native_id: ps2
name: PS2
sony-playstation-3:
files:
- name: PS3UPDAT.PUP
@@ -695,6 +715,7 @@ systems:
required: true
md5: 05fe32f5dc8c78acbcd84d36ee7fdc5b
native_id: ps3
name: PS3
nintendo-fds:
files:
- name: disksys.rom
@@ -702,6 +723,7 @@ systems:
required: true
md5: ca30b50f880eb660a320674ed365ef7a
native_id: fds
name: Nintendo Family Computer Disk System
nintendo-ds:
files:
- name: firmware.bin
@@ -730,6 +752,7 @@ systems:
destination: dsi_nand.bin
required: true
native_id: nds
name: Nintendo DS
nintendo-gba:
files:
- name: gba_bios.bin
@@ -749,6 +772,7 @@ systems:
required: true
md5: d574d4f9c12f305074798f54c091a8b4
native_id: gba
name: Nintendo Gameboy Advance
nintendo-satellaview:
files:
- name: BS-X.bin
@@ -756,6 +780,7 @@ systems:
required: true
md5: 96cf17bf589fcbfa6f8de2dc84f19fa2
native_id: satellaview
name: Satellaview
sufami:
files:
- name: STBIOS.bin
@@ -763,6 +788,7 @@ systems:
required: true
md5: d3a44ba7d42a74d3ac58cb9c14c6a5ca
native_id: sufami
name: Sufami
nintendo-sgb:
files:
- name: sgb_boot.bin
@@ -782,6 +808,7 @@ systems:
required: true
md5: 8ecd73eb4edf7ed7e81aef1be80031d5
native_id: sgb
name: Super Game Boy
microsoft-msx:
files:
- name: MSX.ROM
@@ -861,6 +888,7 @@ systems:
required: true
md5: 42af93619160ef2116416f74a6cb12f2
native_id: msx
name: MSX-Turbo
xbox:
files:
- name: mcpx_1.0.bin
@@ -872,6 +900,7 @@ systems:
required: true
md5: 39cee882148a87f93cb440b99dde3ceb
native_id: xbox
name: Xbox
amiga500:
files:
- name: kick33180.A500
@@ -915,6 +944,7 @@ systems:
required: true
md5: e40a5dfb3d017ba8779faba30cbd1c8e
native_id: amiga500
name: Amiga 500
commodore-amiga:
files:
- name: kick34005.A500
@@ -982,6 +1012,7 @@ systems:
required: true
md5: bb72565701b1b6faece07d68ea5da639
native_id: amigacdtv
name: Amiga CD32
pc60:
files:
- name: pc6001.zip
@@ -1050,6 +1081,7 @@ systems:
md5: 9d61f3cbd47c4a281c5241c4f1d80919
zipped_file: cgrom68.64
native_id: pc60
name: NEC PC-6000
nec-pc-88:
files:
- name: N88.ROM
@@ -1085,6 +1117,7 @@ systems:
required: true
md5: fc4b76a402ba501e6ba6de4b3e8b4273
native_id: pc88
name: NEC PC-8800
nec-pc-98:
files:
- name: BIOS.ROM
@@ -1108,6 +1141,7 @@ systems:
required: true
md5: 7da1e5b7c482d4108d22a5b09631d967
native_id: pc98
name: NEC PC-9800
loopy:
files:
- name: casloopy.zip
@@ -1125,6 +1159,7 @@ systems:
md5: c0f1c899c9ca098663d046d60779711d
zipped_file: hn62434fa.lsi352
native_id: loopy
name: Casio Loopy
fairchild-channel-f:
files:
- name: sl31253.bin
@@ -1140,6 +1175,7 @@ systems:
required: true
md5: 95d339631d867c8f1d15a5f2ec26069d
native_id: channelf
name: Fairchild ChannelF
sharp-x1:
files:
- name: IPLROM.X1
@@ -1151,6 +1187,7 @@ systems:
required: true
md5: 56c28adcf1f3a2f87cf3d57c378013f5
native_id: x1
name: Sharp X1
fmtowns:
files:
- name: FMT_DIC.ROM
@@ -1238,6 +1275,7 @@ systems:
md5: 1a15f6c1b58ec7e5f850118610a787a7
zipped_file: mytownsux.rom
native_id: fmtowns
name: Fujistu FM-Towns
gp32:
files:
- name: gp32.zip
@@ -1274,6 +1312,7 @@ systems:
md5: d4af2bc352bdaf4972ea40902feda114
zipped_file: gp32mfv2.bin
native_id: gp32
name: GamePark GP32
laser310:
files:
- name: laser310.zip
@@ -1290,6 +1329,7 @@ systems:
md5: f7e5d9a3eb2b57bf5f4e2a4565318a8f
zipped_file: vtechv21.u12
native_id: laser310
name: VTech Laser 310
scv:
files:
- name: upd7801g.s01
@@ -1297,6 +1337,7 @@ systems:
required: true
md5: 635a978fd40db9a18ee44eff449fc126
native_id: scv
name: Super Cassette Vision
apple-iigs:
files:
- name: ROM1
@@ -1332,6 +1373,7 @@ systems:
md5: 68ff96a624237d233e8d4c701f660dd1
zipped_file: apple2gs.chr
native_id: gsplus
name: Apple IIgs
zc210:
files:
- name: zcdata.dat
@@ -1387,6 +1429,7 @@ systems:
required: true
md5: e0ba7a8634b12cfee4b6760a6f89051a
native_id: zc210
name: Zelda Classic
apple-macintosh-ii:
files:
- name: MacII.ROM
@@ -1594,6 +1637,7 @@ systems:
destination: mac755.chd
required: true
native_id: macintosh
name: Apple Macintosh
sc3000:
files:
- name: sc3000.zip
@@ -1605,18 +1649,21 @@ systems:
md5: a6a47eae38600e41cc67e887e36e70b7
zipped_file: sc3000.rom
native_id: sc3000
name: Sega SC-3000
segaai:
files:
- name: segaai.zip
destination: segaai.zip
required: true
native_id: segaai
name: Sega AI Computer
beena:
files:
- name: beena.zip
destination: beena.zip
required: true
native_id: beena
name: Advanced Pico Beena
coco:
files:
- name: coco.zip
@@ -1678,12 +1725,14 @@ systems:
md5: 8cab28f4b7311b8df63c07bb3b59bfd5
zipped_file: disk11.rom
native_id: coco
name: Tandy Color Computer
cgenie:
files:
- name: cgenie.zip
destination: cgenie.zip
required: true
native_id: cgenie
name: Colour Genie
dragon64:
files:
- name: dragon64.zip
@@ -1711,6 +1760,7 @@ systems:
required: true
zipped_file: d32.rom
native_id: dragon64
name: Dragon 64
mc10:
files:
- name: mc10.zip
@@ -1730,6 +1780,7 @@ systems:
md5: 78af465c2f31cf4e05dec1efda77da01
zipped_file: alice.rom
native_id: mc10
name: Tandy MC-10
trs80:
files:
- name: trs80.zip
@@ -1761,6 +1812,7 @@ systems:
destination: trs80m4p.zip
required: true
native_id: trs80
name: TRS-80
tutor:
files:
- name: tutor.zip
@@ -1777,6 +1829,7 @@ systems:
md5: 5770834c10946ac2c3617504ba530884
zipped_file: tutor2.bin
native_id: tutor
name: Tomy Tutor
ti99:
files:
- name: ti99_4a.zip
@@ -1821,6 +1874,7 @@ systems:
md5: 206daf498ac5d0141de1d47d38afd899
zipped_file: cd2326a.u2b
native_id: ti99
name: Texas Instruments TI-99
astrocade:
files:
- name: astrocde.zip
@@ -1832,6 +1886,7 @@ systems:
md5: 7d25a26e5c4841b364cfe6b1735eaf03
zipped_file: astro.bin
native_id: astrocade
name: Bally Astrocade
gmaster:
files:
- name: gmaster.zip
@@ -1843,6 +1898,7 @@ systems:
md5: 6bff08b5e5f96de405cd56d5f04a08f8
zipped_file: d78c11agf_e19.u1
native_id: gmaster
name: Hartung Game Master
adam:
files:
- name: adam.zip
@@ -1951,6 +2007,7 @@ systems:
md5: 3cdf2fe48ac4224b56f26c03f6c68982
zipped_file: printer.u2
native_id: adam
name: Coleco Adam
coleco-colecovision:
files:
- name: colecovision.rom
@@ -1958,6 +2015,7 @@ systems:
required: true
md5: 2c66f5911e5b42b8ebe113403548eee7
native_id: colecovision
name: ColecoVision
bbc:
files:
- name: bbcb.zip
@@ -2150,6 +2208,7 @@ systems:
required: true
md5: f083f49d6fe66344c650d7e74249cb96
native_id: bbc
name: BBC Micro
pcw:
files:
- name: pcw8256.zip
@@ -2174,6 +2233,7 @@ systems:
md5: b664af93987d575b0248832832c61505
zipped_file: 40103.ic109
native_id: pcw
name: Amstrad PCW
apfm1000:
files:
- name: apfm1000.zip
@@ -2195,6 +2255,7 @@ systems:
md5: 89a7cfa5469ce24773721d65b28f8544
zipped_file: trash-ii.bin
native_id: apfm1000
name: APF M-1000
fm7:
files:
- name: fm7.zip
@@ -2254,6 +2315,7 @@ systems:
md5: 7db27dede3e358017d518101850bccfa
zipped_file: subsyscg.rom
native_id: fm7
name: Fujitsu FM-7
archimedes:
files:
- name: aa310.zip
@@ -2463,6 +2525,7 @@ systems:
md5: 1a8617c1abe3e0729d20ce844e1e12a8
zipped_file: acorn_0280,022-01_philips_8051ah-2.bin
native_id: archimedes
name: Acorn Archimedes
atom:
files:
- name: atom.zip
@@ -2484,6 +2547,7 @@ systems:
md5: 9627dfb5f8302db8dd5702dbf7c09f72
zipped_file: dosrom.u15
native_id: atom
name: Acorn Atom
electron:
files:
- name: electron.zip
@@ -2544,6 +2608,7 @@ systems:
md5: 5c39baa89fe8a40a5167a53cc5ae7791
zipped_file: pres_adfs_115.rom
native_id: electron
name: Acorn Electron
apple-ii:
files:
- name: apple2e.zip
@@ -2671,6 +2736,7 @@ systems:
md5: 5f1be0c1cdff26f5956eef9643911886
zipped_file: 341-0028-a.rom
native_id: apple2
name: Apple II
camplynx:
files:
- name: lynx48k.zip
@@ -2753,6 +2819,7 @@ systems:
md5: f9f54913cdedb22bb8f0c549ad121379
zipped_file: lynx128-3.ic3
native_id: camplynx
name: Camputers Lynx
mz80k:
files:
- name: mz80k.zip
@@ -2782,6 +2849,7 @@ systems:
md5: 4138784bdd2e2cbacd6c55eff195b2ac
zipped_file: mz80kfdif.rom
native_id: mz80k
name: Sharp MZ-80K
mz700:
files:
- name: mz700.zip
@@ -2798,6 +2866,7 @@ systems:
md5: e9045d57e8574f3eb3f775c02369fbfe
zipped_file: mz700fon.int
native_id: mz700
name: Sharp MZ-700
mz800:
files:
- name: mz800.zip
@@ -2809,6 +2878,7 @@ systems:
md5: 7d3909267b3f3e6ce7aa999de0ded226
zipped_file: mz800.rom
native_id: mz800
name: Sharp MZ-800
mz2000:
files:
- name: mz2000.zip
@@ -2825,6 +2895,7 @@ systems:
md5: dea49b39998885631db16de1176e71b9
zipped_file: font.bin
native_id: mz2000
name: Sharp MZ-2000
mz2500:
files:
- name: mz2500.zip
@@ -2861,6 +2932,7 @@ systems:
md5: 04843d14a8e2f69a7fcaff394a8cc012
zipped_file: phone.rom
native_id: mz2500
name: Sharp MZ-2500
vgmplay:
files:
- name: qsound.zip
@@ -2888,6 +2960,7 @@ systems:
md5: 8740932cda05e518a9955f1d08d6786f
zipped_file: ym2608_adpcm_rom.bin
native_id: vgmplay
name: Video Game Music Player
gamepock:
files:
- name: gamepock.zip
@@ -2899,6 +2972,7 @@ systems:
md5: a0dd595eafb407a6a4b4ed800005a394
zipped_file: egpcboot.bin
native_id: gamepock
name: Epoch Game Pocket Computer
gamecom:
files:
- name: gamecom.zip
@@ -2915,6 +2989,7 @@ systems:
md5: f7bcefb6daf923c8e5ea2eb69f619efe
zipped_file: internal.bin
native_id: gamecom
name: Tiger Game.com
xegs:
files:
- name: xegs.zip
@@ -2926,6 +3001,7 @@ systems:
md5: 42cbd989802c17d0ac3731d33270d835
zipped_file: c101687.rom
native_id: xegs
name: Atari XE Game System
crvision:
files:
- name: crvision.zip
@@ -2937,6 +3013,7 @@ systems:
md5: 3b1ef759d8e3fb4071582efd33dd05f9
zipped_file: crvision.u20
native_id: crvision
name: VTech CreatiVision
vsmile:
files:
- name: vsmile.zip
@@ -2958,6 +3035,7 @@ systems:
md5: 11e59253c578c8f16ea2375ec398e4e9
zipped_file: vsmile_v103.bin
native_id: vsmile
name: VTech V.Smile
socrates:
files:
- name: socrates.zip
@@ -2989,6 +3067,7 @@ systems:
md5: 31c29c57e3d3e6788ba5817eaaa8b17a
zipped_file: speech_eng_vsm3.bin
native_id: socrates
name: VTech Socrates
rx78:
files:
- name: rx78.zip
@@ -3000,6 +3079,7 @@ systems:
md5: f613b15c4b965013e4827d2ad79c7080
zipped_file: ipl.rom
native_id: rx78
name: Bandai RX-78
advision:
files:
- name: advision.zip
@@ -3016,6 +3096,7 @@ systems:
md5: fc5e71445e4947a9d00eedbc66b13a8f
zipped_file: b8223__cop411l-kcn_n.u8
native_id: advision
name: Entex Adventure Vision
gamate:
files:
- name: gamate.zip
@@ -3032,12 +3113,14 @@ systems:
md5: ef67993a94503c4b7798b5901c7dda52
zipped_file: gamate_bios_umc.bin
native_id: gamate
name: Bitcorp Gamate
pv2000:
files:
- name: pv2000.zip
destination: pv2000.zip
required: true
native_id: pv2000
name: Casio PV-2000
pc80:
files:
- name: pc8001.zip
@@ -3092,6 +3175,7 @@ systems:
md5: d81c6d5d7ad1a4bbbd6ae22a01257603
zipped_file: kanji1.rom
native_id: pc80
name: NEC PC-8001
cdi:
files:
- name: cdimono1.zip
@@ -3123,6 +3207,7 @@ systems:
md5: 3d20cf7550f1b723158b42a1fd5bac62
zipped_file: zx405042p__cdi_slave_2.0__b43t__zzmk9213.mc68hc705c8a_withtestrom.7206
native_id: cdi
name: Phillips CD-i
hikaru:
files:
- name: hikaru.zip
@@ -3138,6 +3223,7 @@ systems:
required: true
md5: ee643e8c7369fe7feca610d4daa4a57c
native_id: hikaru
name: Hikaru
sony-playstation-vita:
files:
- name: PSP2UPDAT.PUP
@@ -3149,6 +3235,7 @@ systems:
required: true
md5: f2c7b12fe85496ec88a0391b514d6e3b
native_id: psvita
name: PS Vita
vectrex:
files:
- name: vectrex.zip
@@ -3165,6 +3252,7 @@ systems:
md5: a9c238473229912eb757ff3dfe6f4631
zipped_file: exec_rom_intl_284001-1.bin
native_id: vectrex
name: GCE Vectrex
scummvm:
files:
- name: MT32_PCM.ROM
@@ -3176,6 +3264,7 @@ systems:
required: true
md5: 5626206284b22c2734f3e9efefcd2675
native_id: scummvm
name: ScummVM
supracan:
files:
- name: supracan.zip
@@ -3187,6 +3276,7 @@ systems:
md5: 53c4343e062bb3b337370bbb58e64d16
zipped_file: internal_68k.bin
native_id: supracan
name: Supr'A'can
tandy-vis:
files:
- name: vis.zip
@@ -3203,6 +3293,7 @@ systems:
md5: 758f8fec271fbf526bb22b36e88f154b
zipped_file: p513bk1b.bin
native_id: vis
name: Tandy Video Information System
nintendo-64dd:
files:
- name: 64DD_IPL.bin
@@ -3210,6 +3301,7 @@ systems:
required: true
md5: 8d3d9f294b6e174bc7b1d2fd1c727530
native_id: n64dd
name: Nintendo 64DD
nintendo-gamecube:
files:
- name: IPL.bin
@@ -3225,6 +3317,7 @@ systems:
required: true
md5: b17148254a5799684c7d783206504926
native_id: gamecube
name: Nintendo GameCube
chihiro:
files:
- name: mcpx_1.0.bin
@@ -3236,6 +3329,7 @@ systems:
required: true
md5: f23d7e00ae8fbf88908ed1f9165f35eb
native_id: chihiro
name: Sega Chihiro
oricatmos:
files:
- name: basic11.rom
@@ -3247,6 +3341,7 @@ systems:
required: true
md5: c888b36bf0fc222c3585c3fabe556d21
native_id: oricatmos
name: Oric Atmos
enterprise-64-128:
files:
- name: basic21.bin
@@ -3361,6 +3456,7 @@ systems:
md5: 47258d03aec37bc24df5dbc5d50fc9f8
zipped_file: exdos13.rom
native_id: enterprise
name: Enterprise
videoton-tvc:
files:
- name: tvc22_sys.rom
@@ -3413,6 +3509,7 @@ systems:
md5: 5ce95a26ceed5bec73995d83568da9cf
zipped_file: tvc22_d7.64k
native_id: tvc
name: Videoton TVC
triforce:
files:
- name: segaboot.gcm
@@ -3420,6 +3517,7 @@ systems:
required: true
md5: 2ef01c0e93f7ee3a0a4b139aa14728b9
native_id: triforce
name: Triforce
bk:
files:
- name: B11M_BOS.ROM
@@ -3513,6 +3611,7 @@ systems:
md5: fe4627d1e3a1535874085050733263e7
zipped_file: bk11m_324_bos.rom
native_id: bk
name: Elektronika BK
standalone_cores:
- abuse
- azahar

View File

@@ -4,10 +4,8 @@ dat_version: v1.19.0
homepage: https://www.retroarch.com
source: https://github.com/libretro/libretro-database/blob/master/dat/System.dat
base_destination: system
cores: all_libretro
hash_type: sha1
verification_mode: existence
case_insensitive_fs: true
systems:
3do:
files:
@@ -102,6 +100,7 @@ systems:
md5: 35fa1a1ebaaeea286dc5cd15487c13ea
crc32: d5cbc509
size: 1048576
native_id: 3DO Company, The - 3DO
core: opera
manufacturer: Panasonic|GoldStar|Sanyo
docs: https://docs.libretro.com/library/opera/
@@ -135,6 +134,7 @@ systems:
md5: 25629dfe870d097469c217b95fdc1c95
crc32: 1fe22ecd
size: 16384
native_id: Amstrad - CPC
arcade:
files:
- name: bubsys.zip
@@ -249,6 +249,10 @@ systems:
- name: aes.zip
destination: aes.zip
required: true
native_id: Arcade
core: fbneo
manufacturer: Various
docs: https://docs.libretro.com/library/fbneo/
data_directories:
- ref: fbneo-hiscore
destination: ''
@@ -256,9 +260,6 @@ systems:
destination: fbneo
- ref: fbneo-samples
destination: fbneo
core: fbneo
manufacturer: Various
docs: https://docs.libretro.com/library/fbneo/
atari-400-800:
files:
- name: ATARIBAS.ROM
@@ -303,6 +304,7 @@ systems:
md5: d7eb37aec6960cba36bc500e0e5d00bc
crc32: bdca01fb
size: 8192
native_id: Atari - 400-800
atari-5200:
files:
- name: 5200.rom
@@ -312,6 +314,7 @@ systems:
md5: 281f20ea4320404ec820fb7ec0693b38
crc32: 4248d3e3
size: 2048
native_id: Atari - 5200
core: a5200
manufacturer: Atari
docs: https://docs.libretro.com/library/a5200/
@@ -331,6 +334,7 @@ systems:
md5: 0763f1ffb006ddbe32e52d497ee848ae
crc32: 5d13730c
size: 4096
native_id: Atari - 7800
core: prosystem
manufacturer: Atari
docs: https://docs.libretro.com/library/prosystem/
@@ -343,6 +347,7 @@ systems:
md5: fcd403db69f54290b51035d82f835e7b
crc32: 0d973c9d
size: 512
native_id: Atari - Lynx
core: handy
manufacturer: Atari
docs: https://docs.libretro.com/library/handy/
@@ -355,6 +360,7 @@ systems:
md5: c1c57ce48e8ee4135885cee9e63a68a2
crc32: d3c32283
size: 196608
native_id: Atari - ST
core: hatari
manufacturer: Atari
docs: https://docs.libretro.com/library/hatari/
@@ -376,6 +382,7 @@ systems:
- name: bioscv.rom
destination: bioscv.rom
required: true
native_id: Coleco - ColecoVision
commodore-amiga:
files:
- name: kick33180.A500
@@ -462,6 +469,7 @@ systems:
md5: bb72565701b1b6faece07d68ea5da639
crc32: 87746be2
size: 524288
native_id: Commodore - Amiga
core: puae
manufacturer: Commodore
docs: https://docs.libretro.com/library/puae/
@@ -510,6 +518,7 @@ systems:
destination: scpu-dos-2.04.bin
required: true
md5: b2869f8678b8b274227f35aad26ba509
native_id: Commodore - C128
core: vice_x128
manufacturer: Commodore
docs: https://docs.libretro.com/library/vice_x128/
@@ -522,6 +531,7 @@ systems:
md5: a2e891e330d146c4046c2b622fc31462
crc32: 683ed4ad
size: 5763199
native_id: Dinothawr
dos:
files:
- name: MT32_CONTROL.ROM
@@ -552,6 +562,7 @@ systems:
md5: 08cdcfa0ed93e9cb16afa76e6ac5f0a4
crc32: 4b961eba
size: 1048576
native_id: DOS
elektronika-bk:
files:
- name: B11M_BOS.ROM
@@ -610,6 +621,7 @@ systems:
md5: 95f8c41c6abf7640e35a6a03cecebd01
crc32: 26c6e8a0
size: 8192
native_id: Elektronika - BK-0010/BK-0011(M)
enterprise-64-128:
files:
- name: hun.rom
@@ -696,6 +708,7 @@ systems:
md5: 55af78f877a21ca45eb2df68a74fcc60
crc32: c099a5e3
size: 65536
native_id: Enterprise - 64/128
includes:
- ep128emu
epoch-scv:
@@ -707,6 +720,7 @@ systems:
md5: 635a978fd40db9a18ee44eff449fc126
crc32: 7ac06182
size: 4096
native_id: EPOCH/YENO Super Cassette Vision
fairchild-channel-f:
files:
- name: sl31253.bin
@@ -730,6 +744,7 @@ systems:
md5: 95d339631d867c8f1d15a5f2ec26069d
crc32: 015c1e38
size: 1024
native_id: Fairchild Channel F
doom:
files:
- name: prboom.wad
@@ -739,6 +754,7 @@ systems:
md5: 72ae1b47820fcc93cc0df9c428d0face
crc32: a5751b99
size: 143312
native_id: Id Software - Doom
j2me:
files:
- name: freej2me-lr.jar
@@ -762,6 +778,7 @@ systems:
md5: 29a92d0867da2917275b7c6c805d256f
crc32: ffb98ffa
size: 552039
native_id: J2ME
core: freej2me
manufacturer: Java
docs: https://docs.libretro.com/library/freej2me/
@@ -774,6 +791,7 @@ systems:
md5: 66223be1497460f1e60885eeb35e03cc
crc32: 4df6d054
size: 262144
native_id: MacII
magnavox-odyssey2:
files:
- name: o2rom.bin
@@ -804,6 +822,7 @@ systems:
md5: 279008e4a0db2dc5f1c048853b033828
crc32: 11647ca5
size: 1024
native_id: Magnavox - Odyssey2
core: o2em
manufacturer: Magnavox|Philips
docs: https://docs.libretro.com/library/o2em/
@@ -823,6 +842,7 @@ systems:
md5: 0cd5946c6473e42e8e4c2137785e427f
crc32: 683a4158
size: 2048
native_id: Mattel - Intellivision
data_directories:
- ref: freeintv-overlays
destination: freeintv_overlays
@@ -933,6 +953,7 @@ systems:
md5: 279efd1eae0d358eecd4edc7d9adedf3
crc32: ab6874f8
size: 16640
native_id: Microsoft - MSX
core: bluemsx
manufacturer: Spectravideo|Philips|Al Alamiah|Sony|Sanyo|Mitsubishi|Toshiba|Hitachi|Panasonic|Canon|Casio|Pioneer|Fujitsu|Yamaha|JVC|Kyocera|GoldStar|Samsung|Daewoo|Gradiente|Sharp|Talent|NTT|ACVS/CIEL|DDX|AGE
Labs
@@ -991,6 +1012,7 @@ systems:
md5: 0754f903b52e3b3342202bdafb13efa5
crc32: 2b5b75fe
size: 262144
native_id: NEC - PC Engine - TurboGrafx 16 - SuperGrafx
core: mednafen_pce_fast
manufacturer: NEC
docs: https://docs.libretro.com/library/mednafen_pce_fast/
@@ -1073,6 +1095,7 @@ systems:
md5: 524473c1a5a03b17e21d86a0408ff827
crc32: fe9f57f2
size: 16384
native_id: NEC - PC-98
core: np2kai
manufacturer: NEC
docs: https://docs.libretro.com/library/np2kai/
@@ -1115,6 +1138,7 @@ systems:
md5: e2fb7c7220e3a7838c2dd7e401a7f3d8
crc32: 236102c9
size: 1048576
native_id: NEC - PC-FX
core: mednafen_pcfx
manufacturer: NEC
docs: https://docs.libretro.com/library/mednafen_pcfx/
@@ -1134,6 +1158,7 @@ systems:
md5: 7f98d77d7a094ad7d069b74bd553ec98
crc32: 4c514089
size: 24592
native_id: Nintendo - Famicom Disk System
nintendo-gb:
files:
- name: dmg_boot.bin
@@ -1150,6 +1175,7 @@ systems:
md5: 32fbbd84168d3482956eb3c5051637f5
crc32: 59c8598e
size: 256
native_id: Nintendo - Gameboy
core: gambatte
manufacturer: Nintendo
docs: https://docs.libretro.com/library/gambatte/
@@ -1162,6 +1188,7 @@ systems:
md5: a860e8c0b6d573d191e4ec7db1b1e4f6
crc32: '81977335'
size: 16384
native_id: Nintendo - Game Boy Advance
core: gpsp
manufacturer: Nintendo
docs: https://docs.libretro.com/library/gpsp/
@@ -1181,6 +1208,7 @@ systems:
md5: dbfce9db9deaa2567f6a84fde55f9680
crc32: 41884e46
size: 2304
native_id: Nintendo - Gameboy Color
nintendo-gamecube:
files:
- name: gc-dvd-20010608.bin
@@ -1274,6 +1302,7 @@ systems:
- name: font_japanese.bin
destination: dolphin-emu/Sys/GC/font_japanese.bin
required: false
native_id: Nintendo - GameCube
core: dolphin
manufacturer: Nintendo
docs: https://docs.libretro.com/library/dolphin/
@@ -1289,6 +1318,7 @@ systems:
md5: 8d3d9f294b6e174bc7b1d2fd1c727530
crc32: 7f933ce2
size: 4194304
native_id: Nintendo - Nintendo 64DD
nintendo-ds:
files:
- name: bios7.bin
@@ -1324,6 +1354,7 @@ systems:
- name: dsi_nand.bin
destination: dsi_nand.bin
required: true
native_id: Nintendo - Nintendo DS
core: desmume
manufacturer: Nintendo
docs: https://docs.libretro.com/library/desmume/
@@ -1332,10 +1363,11 @@ systems:
- name: NstDatabase.xml
destination: NstDatabase.xml
required: true
sha1: f92312bae56e29c5bf00a5103105fce78472bf5c
md5: 0ee6cbdc6f5c96ce9c8aa5edb59066f4
crc32: 0e4d552b
sha1: 26322f182540211e9b5e3647675b7c593706ae2b
md5: 7bfe8c0540ed4bd6a0f1e2a0f0118ced
crc32: ebb2196c
size: 1009534
native_id: Nintendo - Nintendo Entertainment System
core: fceumm
manufacturer: Nintendo
docs: https://docs.libretro.com/library/fceumm/
@@ -1348,6 +1380,7 @@ systems:
md5: 1e4fb124a3a886865acb574f388c803d
crc32: aed3c14d
size: 4096
native_id: Nintendo - Pokemon Mini
nintendo-satellaview:
files:
- name: BS-X.bin
@@ -1371,6 +1404,7 @@ systems:
md5: 4ed9648505ab33a4daec93707b16caba
crc32: 8c573c7e
size: 1048576
native_id: Nintendo - Satellaview
nintendo-sufami-turbo:
files:
- name: STBIOS.bin
@@ -1380,6 +1414,7 @@ systems:
md5: d3a44ba7d42a74d3ac58cb9c14c6a5ca
crc32: 9b4ca911
size: 262144
native_id: Nintendo - SuFami Turbo
nintendo-sgb:
files:
- name: SGB1.sfc
@@ -1441,6 +1476,7 @@ systems:
- name: sgb.boot.rom
destination: sgb.boot.rom
required: false
native_id: Nintendo - Super Game Boy
nintendo-snes:
files:
- name: cx4.data.rom
@@ -1562,6 +1598,7 @@ systems:
md5: dda40ccd57390c96e49d30a041f9a9e7
crc32: f73d5e10
size: 131072
native_id: Nintendo - Super Nintendo Entertainment System
core: bsnes
manufacturer: Nintendo
docs: https://docs.libretro.com/library/bsnes/
@@ -1588,6 +1625,7 @@ systems:
md5: 279008e4a0db2dc5f1c048853b033828
crc32: 11647ca5
size: 1024
native_id: Phillips - Videopac+
sega-dreamcast:
files:
- name: dc_boot.bin
@@ -1611,6 +1649,7 @@ systems:
md5: 0a93f7940c455905bea6e392dfde92a4
crc32: c611b498
size: 131072
native_id: Sega - Dreamcast
core: flycast
manufacturer: Sega
docs: https://docs.libretro.com/library/flycast/
@@ -1668,6 +1707,7 @@ systems:
- name: segasp.zip
destination: dc/segasp.zip
required: true
native_id: Sega - Dreamcast-based Arcade
sega-game-gear:
files:
- name: bios.gg
@@ -1677,6 +1717,7 @@ systems:
md5: 672e104c3be3a238301aceffc3b23fd6
crc32: 0ebea9d4
size: 1024
native_id: Sega - Game Gear
sega-master-system:
files:
- name: bios.sms
@@ -1707,6 +1748,7 @@ systems:
md5: 840481177270d5642a14ca71ee72844c
crc32: 0072ed54
size: 8192
native_id: Sega - Master System - Mark III
sega-mega-cd:
files:
- name: bios_CD_E.bin
@@ -1730,6 +1772,7 @@ systems:
md5: 2efd74e3232ff260e371b99f84024f7f
crc32: c6d10268
size: 131072
native_id: Sega - Mega CD - Sega CD
sega-mega-drive:
files:
- name: areplay.bin
@@ -1774,6 +1817,7 @@ systems:
md5: b4e76e416b887f4e7413ba76fa735f16
crc32: 4dcfd55c
size: 262144
native_id: Sega - Mega Drive - Genesis
core: genesis_plus_gx
manufacturer: Sega
docs: https://docs.libretro.com/library/genesis_plus_gx/
@@ -1859,6 +1903,7 @@ systems:
- name: stvbios.zip
destination: kronos/stvbios.zip
required: true
native_id: Sega - Saturn
core: kronos
manufacturer: Sega
docs: https://docs.libretro.com/library/kronos/
@@ -1880,6 +1925,7 @@ systems:
md5: 851e4a5936f17d13f8c39a980cf00d77
crc32: e3995a57
size: 2048
native_id: Sharp - X1
core: x1
manufacturer: Sharp
docs: https://docs.libretro.com/library/x1/
@@ -1920,6 +1966,7 @@ systems:
md5: 0617321daa182c3f3d6f41fd02fb3275
crc32: 00eeb408
size: 131072
native_id: Sharp - X68000
core: px68k
manufacturer: Sharp
docs: https://docs.libretro.com/library/px68k/
@@ -2270,6 +2317,7 @@ systems:
md5: 85fede415f4294cc777517d7eada482e
crc32: 2cbe8995
size: 32768
native_id: Sinclair - ZX Spectrum
core: fuse
manufacturer: Sinclair|Amstrad
docs: https://docs.libretro.com/library/fuse/
@@ -2352,6 +2400,7 @@ systems:
md5: 08ca8b2dba6662e8024f9e789711c6fc
crc32: ff3abc59
size: 524288
native_id: SNK - NeoGeo CD
core: neocd
manufacturer: SNK
docs: https://docs.libretro.com/library/neocd/
@@ -2511,6 +2560,7 @@ systems:
md5: 81bbe60ba7a3d1cea1d48c14cbcc647b
crc32: 2f53b852
size: 524288
native_id: Sony - PlayStation
core: duckstation
manufacturer: Sony
docs: https://docs.libretro.com/library/duckstation/
@@ -3027,6 +3077,7 @@ systems:
md5: d3e81e95db25f5a86a7b7474550a2155
crc32: 4e8c160c
size: 4194304
native_id: Sony - PlayStation 2
sony-psp:
files:
- name: ppge_atlas.zim
@@ -3036,6 +3087,7 @@ systems:
md5: 866855cc330b9b95cc69135fb7b41d38
crc32: 7b57fa78
size: 666530
native_id: Sony - PlayStation Portable
core: ppsspp
manufacturer: Sony
docs: https://docs.libretro.com/library/ppsspp/
@@ -3065,6 +3117,7 @@ systems:
md5: d4448d09bbfde687c04f9e3310e023ab
crc32: 4bf05697
size: 262144
native_id: Texas Instruments TI-83
core: numero
manufacturer: Texas Instruments
docs: https://docs.libretro.com/library/numero/
@@ -3098,6 +3151,7 @@ systems:
md5: 88dc7876d584f90e4106f91444ab23b7
crc32: 1466aed4
size: 16384
native_id: Videoton - TV Computer
wolfenstein-3d:
files:
- name: ecwolf.pk3
@@ -3107,6 +3161,7 @@ systems:
md5: c011b428819eea4a80b455c245a5a04d
crc32: 26dc3fba
size: 178755
native_id: Wolfenstein 3D
scummvm:
files:
- name: scummvm.zip
@@ -3116,6 +3171,7 @@ systems:
md5: a17e0e0150155400d8cced329563d9c8
crc32: a93f1c4b
size: 9523360
native_id: ScummVM
core: scummvm
manufacturer: Various
docs: https://docs.libretro.com/library/scummvm/
@@ -3141,3 +3197,5 @@ systems:
core: xrick
manufacturer: Other
docs: https://docs.libretro.com/library/xrick/
case_insensitive_fs: true
cores: all_libretro

View File

@@ -191,10 +191,9 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
system.setdefault("files", []).append(gf)
existing.add(key)
# Merge cores from _registry.yml if the registry has a broader list.
# The scraped YAML may have an incomplete cores list; the registry is
# our curated source and supplements it. This affects all consumers:
# generate_pack, verify, generate_truth, diff_truth.
# Merge metadata from _registry.yml. The registry is our curated source;
# the scraped YAML may be incomplete (missing cores, metadata fields).
# Registry fields supplement (not replace) the scraped config.
registry_path = os.path.join(platforms_dir, "_registry.yml")
if os.path.exists(registry_path):
reg_real = os.path.realpath(registry_path)
@@ -203,18 +202,26 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
_shared_yml_cache[reg_real] = yaml.safe_load(f) or {}
reg = _shared_yml_cache[reg_real]
reg_entry = reg.get("platforms", {}).get(platform_name, {})
# Merge cores (union for lists, override for all_libretro)
reg_cores = reg_entry.get("cores")
if reg_cores is not None:
cfg_cores = config.get("cores")
if reg_cores == "all_libretro":
config["cores"] = "all_libretro"
elif isinstance(reg_cores, list) and isinstance(cfg_cores, list):
# Union: registry supplements scraped cores
merged_set = {str(c) for c in cfg_cores} | {str(c) for c in reg_cores}
config["cores"] = sorted(merged_set)
elif isinstance(reg_cores, list) and cfg_cores is None:
config["cores"] = reg_cores
# Merge all registry fields absent from config (except cores,
# handled above with union logic). No hardcoded list — any field
# added to the registry is automatically available in the config.
for key, val in reg_entry.items():
if key != "cores" and key not in config:
config[key] = val
_platform_config_cache[cache_key] = config
return config
@@ -546,6 +553,25 @@ def resolve_local_file(
if fn.casefold() in basename_targets:
return os.path.join(root, fn), "data_dir"
# Agnostic fallback: for filename-agnostic files, find any DB file
# matching the system path prefix and size criteria
if file_entry.get("agnostic"):
agnostic_prefix = file_entry.get("agnostic_path_prefix", "")
min_size = file_entry.get("min_size", 0)
max_size = file_entry.get("max_size", float("inf"))
exact_size = file_entry.get("size")
if exact_size and not min_size:
min_size = exact_size
max_size = exact_size
if agnostic_prefix:
for _sha1, entry in files_db.items():
path = entry.get("path", "")
if not path.startswith(agnostic_prefix):
continue
size = entry.get("size", 0)
if min_size <= size <= max_size and os.path.exists(path):
return path, "agnostic_fallback"
return None, "not_found"
@@ -949,6 +975,39 @@ def expand_platform_declared_names(config: dict, db: dict) -> set[str]:
return declared
import re
_TIMESTAMP_PATTERNS = [
re.compile(r'"generated_at":\s*"[^"]*"'), # database.json
re.compile(r'\*Auto-generated on [^*]*\*'), # README.md
re.compile(r'\*Generated on [^*]*\*'), # docs site pages
]
def write_if_changed(path: str, content: str) -> bool:
"""Write content to path only if the non-timestamp content differs.
Compares new and existing content after stripping timestamp lines.
Returns True if the file was written, False if skipped (unchanged).
"""
if os.path.exists(path):
with open(path) as f:
existing = f.read()
if _strip_timestamps(existing) == _strip_timestamps(content):
return False
with open(path, "w") as f:
f.write(content)
return True
def _strip_timestamps(text: str) -> str:
"""Remove known timestamp patterns for content comparison."""
result = text
for pattern in _TIMESTAMP_PATTERNS:
result = pattern.sub("", result)
return result
# Validation and mode filtering -extracted to validation.py for SoC.
# Re-exported below for backward compatibility.

View File

@@ -16,15 +16,27 @@ from exporter import discover_exporters
OUTPUT_FILENAMES: dict[str, str] = {
"retroarch": "System.dat",
"lakka": "System.dat",
"retropie": "System.dat",
"batocera": "batocera-systems",
"recalbox": "es_bios.xml",
"retrobat": "batocera-systems.json",
"emudeck": "checkBIOS.sh",
"retrodeck": "component_manifest.json",
"romm": "known_bios_files.json",
}
def output_filename(platform: str) -> str:
"""Return the native output filename for a platform."""
return OUTPUT_FILENAMES.get(platform, f"{platform}_bios.dat")
def output_path(platform: str, output_dir: str) -> str:
"""Return the full output path for a platform's native export.
Each platform gets its own subdirectory to avoid filename collisions
(e.g. retroarch, lakka, retropie all produce System.dat).
"""
filename = OUTPUT_FILENAMES.get(platform, f"{platform}_bios.dat")
plat_dir = Path(output_dir) / platform
plat_dir.mkdir(parents=True, exist_ok=True)
return str(plat_dir / filename)
def run(
@@ -35,8 +47,6 @@ def run(
) -> int:
"""Export truth to native formats, return exit code."""
exporters = discover_exporters()
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
errors = 0
@@ -60,7 +70,7 @@ def run(
except (FileNotFoundError, OSError):
pass
dest = str(output_path / output_filename(platform))
dest = output_path(platform, output_dir)
exporter = exporter_cls()
exporter.export(truth_data, dest, scraped_data=scraped)

View File

@@ -25,3 +25,37 @@ class BaseExporter(ABC):
@abstractmethod
def validate(self, truth_data: dict, output_path: str) -> list[str]:
"""Validate exported file against truth data, return list of issues."""
@staticmethod
def _is_pattern(name: str) -> bool:
"""Check if a filename is a placeholder pattern (not a real file)."""
return "<" in name or ">" in name or "*" in name
@staticmethod
def _dest(fe: dict) -> str:
"""Get destination path for a file entry, falling back to name."""
return fe.get("path") or fe.get("destination") or fe.get("name", "")
@staticmethod
def _display_name(
sys_id: str, scraped_sys: dict | None = None,
) -> str:
"""Get display name for a system from scraped data or slug."""
if scraped_sys:
name = scraped_sys.get("name")
if name:
return name
# Fallback: convert slug to display name with acronym handling
_UPPER = {
"3do", "cdi", "cpc", "cps1", "cps2", "cps3", "dos", "gba",
"gbc", "hle", "msx", "nes", "nds", "ngp", "psp", "psx",
"sms", "snes", "stv", "tvc", "vb", "zx",
}
parts = sys_id.replace("-", " ").split()
result = []
for p in parts:
if p.lower() in _UPPER:
result.append(p.upper())
else:
result.append(p.capitalize())
return " ".join(result)

View File

@@ -0,0 +1,111 @@
"""Exporter for Batocera batocera-systems format.
Produces a Python dict matching the exact format of
batocera-linux/batocera-scripts/scripts/batocera-systems.
"""
from __future__ import annotations
from pathlib import Path
from .base_exporter import BaseExporter
class Exporter(BaseExporter):
"""Export truth data to Batocera batocera-systems format."""
@staticmethod
def platform_name() -> str:
return "batocera"
def export(
self,
truth_data: dict,
output_path: str,
scraped_data: dict | None = None,
) -> None:
# Build native_id and display name maps from scraped data
native_map: dict[str, str] = {}
if scraped_data:
for sys_id, sys_data in scraped_data.get("systems", {}).items():
nid = sys_data.get("native_id")
if nid:
native_map[sys_id] = nid
lines: list[str] = ["systems = {", ""]
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
sys_data = systems[sys_id]
files = sys_data.get("files", [])
if not files:
continue
native_id = native_map.get(sys_id, sys_id)
scraped_sys = scraped_data.get("systems", {}).get(sys_id) if scraped_data else None
display_name = self._display_name(sys_id, scraped_sys)
# Build md5 lookup from scraped data for this system
scraped_md5: dict[str, str] = {}
if scraped_data:
s_sys = scraped_data.get("systems", {}).get(sys_id, {})
for sf in s_sys.get("files", []):
sname = sf.get("name", "").lower()
smd5 = sf.get("md5", "")
if sname and smd5:
scraped_md5[sname] = smd5
# Build biosFiles entries as compact single-line dicts
# Original format ALWAYS has md5 — use scraped md5 as fallback
bios_parts: list[str] = []
for fe in files:
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
dest = self._dest(fe)
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
if not md5:
md5 = scraped_md5.get(name.lower(), "")
# Original format requires md5 for every entry — skip without
if not md5:
continue
bios_parts.append(
f'{{ "md5": "{md5}", "file": "bios/{dest}" }}'
)
bios_str = ", ".join(bios_parts)
line = (
f' "{native_id}": '
f'{{ "name": "{display_name}", '
f'"biosFiles": [ {bios_str} ] }},'
)
lines.append(line)
lines.append("")
lines.append("}")
lines.append("")
Path(output_path).write_text("\n".join(lines), encoding="utf-8")
def validate(self, truth_data: dict, output_path: str) -> list[str]:
content = Path(output_path).read_text(encoding="utf-8")
issues: list[str] = []
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
# Skip entries without md5 (not exportable in this format)
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
if not md5:
continue
dest = self._dest(fe)
if dest not in content and name not in content:
issues.append(f"missing: {name}")
return issues

View File

@@ -0,0 +1,207 @@
"""Exporter for EmuDeck checkBIOS.sh format.
Produces a bash script matching the exact pattern of EmuDeck's
functions/checkBIOS.sh: per-system check functions with MD5 arrays
inside the function body, iterating over $biosPath/* files.
Two patterns:
- MD5 pattern: systems with known hashes, loop $biosPath/*, md5sum each, match
- File-exists pattern: systems with specific paths, check -f
"""
from __future__ import annotations
import re
from pathlib import Path
from .base_exporter import BaseExporter
# Map our system IDs to EmuDeck function naming conventions
_SYSTEM_CONFIG: dict[str, dict] = {
"sony-playstation": {
"func": "checkPS1BIOS",
"var": "PSXBIOS",
"array": "PSBios",
"pattern": "md5",
},
"sony-playstation-2": {
"func": "checkPS2BIOS",
"var": "PS2BIOS",
"array": "PS2Bios",
"pattern": "md5",
},
"sega-mega-cd": {
"func": "checkSegaCDBios",
"var": "SEGACDBIOS",
"array": "CDBios",
"pattern": "md5",
},
"sega-saturn": {
"func": "checkSaturnBios",
"var": "SATURNBIOS",
"array": "SaturnBios",
"pattern": "md5",
},
"sega-dreamcast": {
"func": "checkDreamcastBios",
"var": "BIOS",
"array": "hashes",
"pattern": "md5",
},
"nintendo-ds": {
"func": "checkDSBios",
"var": "BIOS",
"array": "hashes",
"pattern": "md5",
},
"nintendo-switch": {
"func": "checkCitronBios",
"pattern": "file-exists",
"firmware_path": "$biosPath/citron/firmware",
"keys_path": "$biosPath/citron/keys/prod.keys",
},
}
def _make_md5_function(cfg: dict, md5s: list[str]) -> list[str]:
"""Generate a MD5-checking function matching EmuDeck's exact pattern."""
func = cfg["func"]
var = cfg["var"]
array = cfg["array"]
md5_str = " ".join(md5s)
return [
f"{func}(){{",
"",
f'\t{var}="NULL"',
"",
'\tfor entry in "$biosPath/"*',
"\tdo",
'\t\tif [ -f "$entry" ]; then',
'\t\t\tmd5=($(md5sum "$entry"))',
f'\t\t\tif [[ "${var}" != true ]]; then',
f"\t\t\t\t{array}=({md5_str})",
f'\t\t\t\tfor i in "${{{array}[@]}}"',
"\t\t\t\tdo",
'\t\t\t\tif [[ "$md5" == *"${i}"* ]]; then',
f"\t\t\t\t\t{var}=true",
"\t\t\t\t\tbreak",
"\t\t\t\telse",
f"\t\t\t\t\t{var}=false",
"\t\t\t\tfi",
"\t\t\t\tdone",
"\t\t\tfi",
"\t\tfi",
"\tdone",
"",
"",
f"\tif [ ${var} == true ]; then",
'\t\techo "$entry true";',
"\telse",
'\t\techo "false";',
"\tfi",
"}",
]
def _make_file_exists_function(cfg: dict) -> list[str]:
"""Generate a file-exists function matching EmuDeck's pattern."""
func = cfg["func"]
firmware = cfg.get("firmware_path", "")
keys = cfg.get("keys_path", "")
return [
f"{func}(){{",
"",
f'\tlocal FIRMWARE="{firmware}"',
f'\tlocal KEYS="{keys}"',
'\tif [[ -f "$KEYS" ]] && [[ "$( ls -A "$FIRMWARE")" ]]; then',
'\t\t\techo "true";',
"\telse",
'\t\t\techo "false";',
"\tfi",
"}",
]
class Exporter(BaseExporter):
"""Export truth data to EmuDeck checkBIOS.sh format."""
@staticmethod
def platform_name() -> str:
return "emudeck"
def export(
self,
truth_data: dict,
output_path: str,
scraped_data: dict | None = None,
) -> None:
lines: list[str] = ["#!/bin/bash"]
systems = truth_data.get("systems", {})
for sys_id, cfg in sorted(_SYSTEM_CONFIG.items(), key=lambda x: x[1]["func"]):
sys_data = systems.get(sys_id)
if not sys_data:
continue
lines.append("")
if cfg["pattern"] == "md5":
md5s: list[str] = []
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if self._is_pattern(name) or name.startswith("_"):
continue
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5s.extend(m for m in md5 if m and re.fullmatch(r"[a-f0-9]{32}", m))
elif md5 and re.fullmatch(r"[a-f0-9]{32}", md5):
md5s.append(md5)
if md5s:
lines.extend(_make_md5_function(cfg, md5s))
elif cfg["pattern"] == "file-exists":
lines.extend(_make_file_exists_function(cfg))
lines.append("")
Path(output_path).write_text("\n".join(lines), encoding="utf-8")
def validate(self, truth_data: dict, output_path: str) -> list[str]:
content = Path(output_path).read_text(encoding="utf-8")
issues: list[str] = []
systems = truth_data.get("systems", {})
for sys_id, cfg in _SYSTEM_CONFIG.items():
if cfg["pattern"] != "md5":
continue
sys_data = systems.get(sys_id)
if not sys_data:
continue
for fe in sys_data.get("files", []):
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
if md5 and re.fullmatch(r"[a-f0-9]{32}", md5) and md5 not in content:
issues.append(f"missing md5: {md5} ({fe.get('name', '')})")
for sys_id, cfg in _SYSTEM_CONFIG.items():
func = cfg["func"]
if func in content:
continue
sys_data = systems.get(sys_id)
if not sys_data or not sys_data.get("files"):
continue
# Only flag if the system has usable data for the function type
if cfg["pattern"] == "md5":
has_md5 = any(
fe.get("md5") and isinstance(fe.get("md5"), str)
and re.fullmatch(r"[a-f0-9]{32}", fe["md5"])
for fe in sys_data["files"]
)
if has_md5:
issues.append(f"missing function: {func}")
elif cfg["pattern"] == "file-exists":
issues.append(f"missing function: {func}")
return issues

View File

@@ -0,0 +1,17 @@
"""Exporter for Lakka (System.dat format, same as RetroArch).
Lakka inherits RetroArch cores and uses the same System.dat format.
Delegates to systemdat_exporter for export and validation.
"""
from __future__ import annotations
from .systemdat_exporter import Exporter as SystemDatExporter
class Exporter(SystemDatExporter):
"""Export truth data to Lakka System.dat format."""
@staticmethod
def platform_name() -> str:
return "lakka"

View File

@@ -0,0 +1,130 @@
"""Exporter for Recalbox es_bios.xml format.
Produces XML matching the exact format of recalbox's es_bios.xml:
- XML namespace declaration
- <system fullname="..." platform="...">
- <bios path="system/file" md5="..." core="..." /> with optional mandatory, hashMatchMandatory, note
- mandatory absent = true (only explicit when false)
- 2-space indentation
"""
from __future__ import annotations
from pathlib import Path
from .base_exporter import BaseExporter
class Exporter(BaseExporter):
"""Export truth data to Recalbox es_bios.xml format."""
@staticmethod
def platform_name() -> str:
return "recalbox"
def export(
self,
truth_data: dict,
output_path: str,
scraped_data: dict | None = None,
) -> None:
native_map: dict[str, str] = {}
if scraped_data:
for sys_id, sys_data in scraped_data.get("systems", {}).items():
nid = sys_data.get("native_id")
if nid:
native_map[sys_id] = nid
lines: list[str] = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<biosList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
' xsi:noNamespaceSchemaLocation="es_bios.xsd">',
]
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
sys_data = systems[sys_id]
files = sys_data.get("files", [])
if not files:
continue
native_id = native_map.get(sys_id, sys_id)
scraped_sys = scraped_data.get("systems", {}).get(sys_id) if scraped_data else None
display_name = self._display_name(sys_id, scraped_sys)
lines.append(f' <system fullname="{display_name}" platform="{native_id}">')
# Build path lookup from scraped data for this system
scraped_paths: dict[str, str] = {}
if scraped_data:
s_sys = scraped_data.get("systems", {}).get(sys_id, {})
for sf in s_sys.get("files", []):
sname = sf.get("name", "").lower()
spath = sf.get("destination", sf.get("name", ""))
if sname and spath:
scraped_paths[sname] = spath
for fe in files:
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
# Use scraped path when available (preserves original format)
path = scraped_paths.get(name.lower())
if not path:
dest = self._dest(fe)
path = f"{native_id}/{dest}" if "/" not in dest else dest
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = ",".join(md5)
required = fe.get("required", True)
# Build cores string from _cores
cores_list = fe.get("_cores", [])
core_str = ",".join(f"libretro/{c}" for c in cores_list) if cores_list else ""
attrs = [f'path="{path}"']
if md5:
attrs.append(f'md5="{md5}"')
if not required:
attrs.append('mandatory="false"')
if not required:
attrs.append('hashMatchMandatory="true"')
if core_str:
attrs.append(f'core="{core_str}"')
lines.append(f' <bios {" ".join(attrs)} />')
lines.append(" </system>")
lines.append("</biosList>")
lines.append("")
Path(output_path).write_text("\n".join(lines), encoding="utf-8")
def validate(self, truth_data: dict, output_path: str) -> list[str]:
from xml.etree.ElementTree import parse as xml_parse
tree = xml_parse(output_path)
root = tree.getroot()
exported_paths: set[str] = set()
for bios_el in root.iter("bios"):
path = bios_el.get("path", "")
if path:
exported_paths.add(path.lower())
exported_paths.add(path.split("/")[-1].lower())
issues: list[str] = []
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
dest = self._dest(fe)
if name.lower() not in exported_paths and dest.lower() not in exported_paths:
issues.append(f"missing: {name}")
return issues

View File

@@ -0,0 +1,114 @@
"""Exporter for RetroBat batocera-systems.json format.
Produces JSON matching the exact format of
RetroBat-Official/emulatorlauncher/batocera-systems/Resources/batocera-systems.json:
- System keys with "name" and "biosFiles" fields
- Each biosFile has "md5" before "file" (matching original key order)
"""
from __future__ import annotations
import json
from collections import OrderedDict
from pathlib import Path
from .base_exporter import BaseExporter
class Exporter(BaseExporter):
"""Export truth data to RetroBat batocera-systems.json format."""
@staticmethod
def platform_name() -> str:
return "retrobat"
def export(
self,
truth_data: dict,
output_path: str,
scraped_data: dict | None = None,
) -> None:
native_map: dict[str, str] = {}
if scraped_data:
for sys_id, sys_data in scraped_data.get("systems", {}).items():
nid = sys_data.get("native_id")
if nid:
native_map[sys_id] = nid
output: OrderedDict[str, dict] = OrderedDict()
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
sys_data = systems[sys_id]
files = sys_data.get("files", [])
if not files:
continue
native_id = native_map.get(sys_id, sys_id)
scraped_sys = scraped_data.get("systems", {}).get(sys_id) if scraped_data else None
display_name = self._display_name(sys_id, scraped_sys)
bios_files: list[OrderedDict] = []
for fe in files:
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
dest = self._dest(fe)
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
# Original format requires md5 for every entry
if not md5:
continue
entry: OrderedDict[str, str] = OrderedDict()
entry["md5"] = md5
entry["file"] = f"bios/{dest}"
bios_files.append(entry)
if bios_files:
if native_id in output:
existing_files = {e.get("file") for e in output[native_id]["biosFiles"]}
for entry in bios_files:
if entry.get("file") not in existing_files:
output[native_id]["biosFiles"].append(entry)
else:
sys_entry: OrderedDict[str, object] = OrderedDict()
sys_entry["name"] = display_name
sys_entry["biosFiles"] = bios_files
output[native_id] = sys_entry
Path(output_path).write_text(
json.dumps(output, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
def validate(self, truth_data: dict, output_path: str) -> list[str]:
data = json.loads(Path(output_path).read_text(encoding="utf-8"))
exported_files: set[str] = set()
for sys_data in data.values():
for bf in sys_data.get("biosFiles", []):
path = bf.get("file", "")
stripped = path.removeprefix("bios/")
exported_files.add(stripped)
basename = path.split("/")[-1] if "/" in path else path
exported_files.add(basename)
issues: list[str] = []
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
if not md5:
continue
dest = self._dest(fe)
if name not in exported_files and dest not in exported_files:
issues.append(f"missing: {name}")
return issues

View File

@@ -0,0 +1,208 @@
"""Exporter for RetroDECK component_manifest.json format.
Produces a JSON file compatible with RetroDECK's component manifests.
Each system maps to a component with BIOS entries containing filename,
md5 (comma-separated if multiple), paths ($bios_path default), and
required status.
Path tokens: $bios_path for bios/, $roms_path for roms/.
Entries without an explicit path default to $bios_path.
"""
from __future__ import annotations
import json
import re
from collections import OrderedDict
from pathlib import Path
from .base_exporter import BaseExporter
# retrobios slug -> RetroDECK system ID (reverse of scraper SYSTEM_SLUG_MAP)
_REVERSE_SLUG: dict[str, str] = {
"nintendo-nes": "nes",
"nintendo-snes": "snes",
"nintendo-64": "n64",
"nintendo-64dd": "n64dd",
"nintendo-gamecube": "gc",
"nintendo-wii": "wii",
"nintendo-wii-u": "wiiu",
"nintendo-switch": "switch",
"nintendo-gb": "gb",
"nintendo-gbc": "gbc",
"nintendo-gba": "gba",
"nintendo-ds": "nds",
"nintendo-3ds": "3ds",
"nintendo-fds": "fds",
"nintendo-sgb": "sgb",
"nintendo-virtual-boy": "virtualboy",
"nintendo-pokemon-mini": "pokemini",
"sony-playstation": "psx",
"sony-playstation-2": "ps2",
"sony-playstation-3": "ps3",
"sony-psp": "psp",
"sony-psvita": "psvita",
"sega-mega-drive": "megadrive",
"sega-mega-cd": "megacd",
"sega-saturn": "saturn",
"sega-dreamcast": "dreamcast",
"sega-dreamcast-arcade": "naomi",
"sega-game-gear": "gamegear",
"sega-master-system": "mastersystem",
"nec-pc-engine": "pcengine",
"nec-pc-fx": "pcfx",
"nec-pc-98": "pc98",
"nec-pc-88": "pc88",
"3do": "3do",
"amstrad-cpc": "amstradcpc",
"arcade": "arcade",
"atari-400-800": "atari800",
"atari-5200": "atari5200",
"atari-7800": "atari7800",
"atari-jaguar": "atarijaguar",
"atari-lynx": "atarilynx",
"atari-st": "atarist",
"commodore-c64": "c64",
"commodore-amiga": "amiga",
"philips-cdi": "cdimono1",
"fairchild-channel-f": "channelf",
"coleco-colecovision": "colecovision",
"mattel-intellivision": "intellivision",
"microsoft-msx": "msx",
"microsoft-xbox": "xbox",
"doom": "doom",
"j2me": "j2me",
"apple-macintosh-ii": "macintosh",
"apple-ii": "apple2",
"apple-iigs": "apple2gs",
"enterprise-64-128": "enterprise",
"tiger-game-com": "gamecom",
"hartung-game-master": "gmaster",
"epoch-scv": "scv",
"watara-supervision": "supervision",
"bandai-wonderswan": "wonderswan",
"snk-neogeo-cd": "neogeocd",
"tandy-coco": "coco",
"tandy-trs-80": "trs80",
"dragon-32-64": "dragon",
"pico8": "pico8",
"wolfenstein-3d": "wolfenstein",
"sinclair-zx-spectrum": "zxspectrum",
}
def _dest_to_path_token(destination: str) -> str:
"""Convert a truth destination path to a RetroDECK path token."""
if destination.startswith("roms/"):
return "$roms_path/" + destination.removeprefix("roms/")
if destination.startswith("bios/"):
return "$bios_path/" + destination.removeprefix("bios/")
# Default: bios path
return "$bios_path/" + destination
class Exporter(BaseExporter):
"""Export truth data to RetroDECK component_manifest.json format."""
@staticmethod
def platform_name() -> str:
return "retrodeck"
def export(
self,
truth_data: dict,
output_path: str,
scraped_data: dict | None = None,
) -> None:
native_map: dict[str, str] = {}
if scraped_data:
for sys_id, sys_data in scraped_data.get("systems", {}).items():
nid = sys_data.get("native_id")
if nid:
native_map[sys_id] = nid
manifest: OrderedDict[str, dict] = OrderedDict()
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
sys_data = systems[sys_id]
files = sys_data.get("files", [])
if not files:
continue
native_id = native_map.get(sys_id, _REVERSE_SLUG.get(sys_id, sys_id))
bios_entries: list[OrderedDict] = []
for fe in files:
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
dest = self._dest(fe)
path_token = _dest_to_path_token(dest)
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = ",".join(m for m in md5 if m)
required = fe.get("required", True)
entry: OrderedDict[str, object] = OrderedDict()
entry["filename"] = name
if md5:
# Validate MD5 entries
parts = [
m.strip().lower()
for m in str(md5).split(",")
if re.fullmatch(r"[0-9a-f]{32}", m.strip())
]
if parts:
entry["md5"] = ",".join(parts) if len(parts) > 1 else parts[0]
entry["paths"] = path_token
entry["required"] = required
system_val = native_id
entry["system"] = system_val
bios_entries.append(entry)
if bios_entries:
if native_id in manifest:
# Merge into existing component (multiple truth systems
# may map to the same native ID)
existing_names = {e["filename"] for e in manifest[native_id]["bios"]}
for entry in bios_entries:
if entry["filename"] not in existing_names:
manifest[native_id]["bios"].append(entry)
else:
component = OrderedDict()
component["system"] = native_id
component["bios"] = bios_entries
manifest[native_id] = component
Path(output_path).write_text(
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
def validate(self, truth_data: dict, output_path: str) -> list[str]:
data = json.loads(Path(output_path).read_text(encoding="utf-8"))
exported_names: set[str] = set()
for comp_data in data.values():
bios = comp_data.get("bios", [])
if isinstance(bios, list):
for entry in bios:
fn = entry.get("filename", "")
if fn:
exported_names.add(fn)
issues: list[str] = []
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
if name not in exported_names:
issues.append(f"missing: {name}")
return issues

View File

@@ -0,0 +1,17 @@
"""Exporter for RetroPie (System.dat format, same as RetroArch).
RetroPie inherits RetroArch cores and uses the same System.dat format.
Delegates to systemdat_exporter for export and validation.
"""
from __future__ import annotations
from .systemdat_exporter import Exporter as SystemDatExporter
class Exporter(SystemDatExporter):
"""Export truth data to RetroPie System.dat format."""
@staticmethod
def platform_name() -> str:
return "retropie"

View File

@@ -0,0 +1,160 @@
"""Exporter for RomM known_bios_files.json format.
Produces JSON matching the exact format of
rommapp/romm/backend/models/fixtures/known_bios_files.json:
- Keys are "igdb_slug:filename"
- Values contain size, crc, md5, sha1 (all optional but at least one hash)
- Hashes are lowercase hex strings
- Size is an integer
"""
from __future__ import annotations
import json
from collections import OrderedDict
from pathlib import Path
from .base_exporter import BaseExporter
# retrobios slug -> IGDB slug (reverse of scraper SLUG_MAP)
_REVERSE_SLUG: dict[str, str] = {
"3do": "3do",
"nintendo-64dd": "64dd",
"amstrad-cpc": "acpc",
"commodore-amiga": "amiga",
"arcade": "arcade",
"atari-st": "atari-st",
"atari-5200": "atari5200",
"atari-7800": "atari7800",
"atari-400-800": "atari8bit",
"coleco-colecovision": "colecovision",
"sega-dreamcast": "dc",
"doom": "doom",
"enterprise-64-128": "enterprise",
"fairchild-channel-f": "fairchild-channel-f",
"nintendo-fds": "fds",
"sega-game-gear": "gamegear",
"nintendo-gb": "gb",
"nintendo-gba": "gba",
"nintendo-gbc": "gbc",
"sega-mega-drive": "genesis",
"mattel-intellivision": "intellivision",
"j2me": "j2me",
"atari-lynx": "lynx",
"apple-macintosh-ii": "mac",
"microsoft-msx": "msx",
"nintendo-ds": "nds",
"snk-neogeo-cd": "neo-geo-cd",
"nintendo-nes": "nes",
"nintendo-gamecube": "ngc",
"magnavox-odyssey2": "odyssey-2-slash-videopac-g7000",
"nec-pc-98": "pc-9800-series",
"nec-pc-fx": "pc-fx",
"nintendo-pokemon-mini": "pokemon-mini",
"sony-playstation-2": "ps2",
"sony-psp": "psp",
"sony-playstation": "psx",
"nintendo-satellaview": "satellaview",
"sega-saturn": "saturn",
"scummvm": "scummvm",
"sega-mega-cd": "segacd",
"sharp-x68000": "sharp-x68000",
"sega-master-system": "sms",
"nintendo-snes": "snes",
"nintendo-sufami-turbo": "sufami-turbo",
"nintendo-sgb": "super-gb",
"nec-pc-engine": "tg16",
"videoton-tvc": "tvc",
"philips-videopac": "videopac-g7400",
"wolfenstein-3d": "wolfenstein",
"sharp-x1": "x1",
"microsoft-xbox": "xbox",
"sinclair-zx-spectrum": "zxs",
}
class Exporter(BaseExporter):
"""Export truth data to RomM known_bios_files.json format."""
@staticmethod
def platform_name() -> str:
return "romm"
def export(
self,
truth_data: dict,
output_path: str,
scraped_data: dict | None = None,
) -> None:
native_map: dict[str, str] = {}
if scraped_data:
for sys_id, sys_data in scraped_data.get("systems", {}).items():
nid = sys_data.get("native_id")
if nid:
native_map[sys_id] = nid
output: OrderedDict[str, dict] = OrderedDict()
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
sys_data = systems[sys_id]
files = sys_data.get("files", [])
if not files:
continue
igdb_slug = native_map.get(sys_id, _REVERSE_SLUG.get(sys_id, sys_id))
for fe in files:
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
key = f"{igdb_slug}:{name}"
entry: OrderedDict[str, object] = OrderedDict()
size = fe.get("size")
if size is not None:
entry["size"] = int(size)
crc = fe.get("crc32", "")
if crc:
entry["crc"] = str(crc).strip().lower()
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
if md5:
entry["md5"] = str(md5).strip().lower()
sha1 = fe.get("sha1", "")
if isinstance(sha1, list):
sha1 = sha1[0] if sha1 else ""
if sha1:
entry["sha1"] = str(sha1).strip().lower()
output[key] = entry
Path(output_path).write_text(
json.dumps(output, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
def validate(self, truth_data: dict, output_path: str) -> list[str]:
data = json.loads(Path(output_path).read_text(encoding="utf-8"))
exported_names: set[str] = set()
for key in data:
if ":" in key:
_, filename = key.split(":", 1)
exported_names.add(filename)
issues: list[str] = []
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_") or self._is_pattern(name):
continue
if name not in exported_names:
issues.append(f"missing: {name}")
return issues

View File

@@ -1,4 +1,8 @@
"""Exporter for libretro System.dat (clrmamepro DAT format)."""
"""Exporter for libretro System.dat (clrmamepro DAT format).
Produces a single 'game' block with all ROMs grouped by system,
matching the exact format of libretro-database/dat/System.dat.
"""
from __future__ import annotations
@@ -13,7 +17,7 @@ from .base_exporter import BaseExporter
def _slug_to_native(slug: str) -> str:
"""Convert a system slug to a native 'Manufacturer - Console' name."""
"""Convert a system slug to 'Manufacturer - Console' format."""
parts = slug.split("-", 1)
if len(parts) == 1:
return parts[0].title()
@@ -42,45 +46,69 @@ class Exporter(BaseExporter):
if nid:
native_map[sys_id] = nid
lines: list[str] = []
lines.append('clrmamepro (')
lines.append('\tname "System.dat"')
lines.append(')')
# Match exact header format of libretro-database/dat/System.dat
version = ""
if scraped_data:
version = scraped_data.get("dat_version", scraped_data.get("version", ""))
lines: list[str] = [
"clrmamepro (",
'\tname "System"',
'\tdescription "System"',
'\tcomment "System, firmware, and BIOS files used by libretro cores."',
]
if version:
lines.append(f"\tversion {version}")
lines.extend([
'\tauthor "libretro"',
'\thomepage "https://github.com/libretro/libretro-database/blob/master/dat/System.dat"',
'\turl "https://raw.githubusercontent.com/libretro/libretro-database/master/dat/System.dat"',
")",
"",
"game (",
'\tname "System"',
'\tcomment "System"',
])
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
sys_data = systems[sys_id]
native_name = native_map.get(sys_id, _slug_to_native(sys_id))
files = sys_data.get("files", [])
if not files:
continue
for fe in sys_data.get("files", []):
native_name = native_map.get(sys_id, _slug_to_native(sys_id))
lines.append("")
lines.append(f'\tcomment "{native_name}"')
for fe in files:
name = fe.get("name", "")
if name.startswith("_"):
if name.startswith("_") or self._is_pattern(name):
continue
dest = fe.get("path", name)
size = fe.get("size", 0)
# Quote names with spaces or special chars (matching original format)
needs_quote = " " in name or "(" in name or ")" in name
name_str = f'"{name}"' if needs_quote else name
rom_parts = [f"name {name_str}"]
size = fe.get("size")
if size:
rom_parts.append(f"size {size}")
crc = fe.get("crc32", "")
md5 = fe.get("md5", "")
sha1 = fe.get("sha1", "")
rom_parts = [f'name "{name}"']
rom_parts.append(f"size {size}")
if crc:
rom_parts.append(f"crc {crc}")
rom_parts.append(f"crc {crc.upper()}")
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = md5[0] if md5 else ""
if md5:
rom_parts.append(f"md5 {md5}")
sha1 = fe.get("sha1", "")
if isinstance(sha1, list):
sha1 = sha1[0] if sha1 else ""
if sha1:
rom_parts.append(f"sha1 {sha1}")
rom_str = " ".join(rom_parts)
game_name = f"{native_name}/{dest}"
lines.append("")
lines.append("game (")
lines.append(f'\tname "{game_name}"')
lines.append(f'\tdescription "{name}"')
lines.append(f"\trom ( {rom_str} )")
lines.append(")")
lines.append(f"\trom ( {' '.join(rom_parts)} )")
lines.append(")")
lines.append("")
Path(output_path).write_text("\n".join(lines), encoding="utf-8")
@@ -93,12 +121,11 @@ class Exporter(BaseExporter):
exported_names.add(rom.name)
issues: list[str] = []
for sys_id, sys_data in truth_data.get("systems", {}).items():
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_"):
if name.startswith("_") or self._is_pattern(name):
continue
if name not in exported_names:
issues.append(f"missing: {name} (system {sys_id})")
issues.append(f"missing: {name}")
return issues

View File

@@ -18,7 +18,7 @@ from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import compute_hashes, list_registered_platforms
from common import compute_hashes, list_registered_platforms, write_if_changed
CACHE_DIR = ".cache"
CACHE_FILE = os.path.join(CACHE_DIR, "db_cache.json")
@@ -315,14 +315,15 @@ def main():
"indexes": indexes,
}
with open(args.output, "w") as f:
json.dump(database, f, indent=2)
new_content = json.dumps(database, indent=2)
written = write_if_changed(args.output, new_content)
save_cache(CACHE_FILE, new_cache)
alias_count = sum(len(v) for v in aliases.values())
name_count = len(indexes["by_name"])
print(f"Generated {args.output}: {len(files)} files, {total_size:,} bytes total")
status = "Generated" if written else "Unchanged"
print(f"{status} {args.output}: {len(files)} files, {total_size:,} bytes total")
print(f" Name index: {name_count} names ({alias_count} aliases)")
return 0

View File

@@ -422,6 +422,91 @@ def _collect_emulator_extras(
"source_emulator": profile.get("emulator", emu_name),
})
# Third pass: agnostic scan — for filename-agnostic cores, include all
# DB files matching the system path prefix and size criteria.
files_db = db.get("files", {})
for emu_name, profile in sorted(profiles.items()):
if profile.get("type") in ("launcher", "alias"):
continue
if emu_name not in relevant:
continue
is_profile_agnostic = profile.get("bios_mode") == "agnostic"
if not is_profile_agnostic:
if not any(f.get("agnostic") for f in profile.get("files", [])):
continue
for f in profile.get("files", []):
if not is_profile_agnostic and not f.get("agnostic"):
continue
fname = f.get("name", "")
if not fname:
continue
# Derive path prefix from the representative file in the DB
path_prefix = None
sha1_list = by_name.get(fname, [])
for sha1 in sha1_list:
entry = files_db.get(sha1, {})
path = entry.get("path", "")
if path:
parts = path.rsplit("/", 1)
if len(parts) == 2:
path_prefix = parts[0] + "/"
break
if not path_prefix:
# Fallback: try other files in the profile for the same system
for other_f in profile.get("files", []):
if other_f is f:
continue
other_name = other_f.get("name", "")
for sha1 in by_name.get(other_name, []):
entry = files_db.get(sha1, {})
path = entry.get("path", "")
if path:
parts = path.rsplit("/", 1)
if len(parts) == 2:
path_prefix = parts[0] + "/"
break
if path_prefix:
break
if not path_prefix:
continue
# Size criteria from the file entry
min_size = f.get("min_size", 0)
max_size = f.get("max_size", float("inf"))
exact_size = f.get("size")
if exact_size and not min_size:
min_size = exact_size
max_size = exact_size
# Scan DB for all files under this prefix matching size
for sha1, entry in files_db.items():
path = entry.get("path", "")
if not path.startswith(path_prefix):
continue
size = entry.get("size", 0)
if not (min_size <= size <= max_size):
continue
scan_name = entry.get("name", "")
if not scan_name:
continue
dest = scan_name
full_dest = f"{base_dest}/{dest}" if base_dest else dest
if full_dest in seen_dests:
continue
seen_dests.add(full_dest)
extras.append({
"name": scan_name,
"destination": dest,
"required": False,
"hle_fallback": False,
"source_emulator": profile.get("emulator", emu_name),
"agnostic_scan": True,
})
return extras
@@ -621,6 +706,24 @@ def _build_readme(platform_name: str, platform_display: str,
return header + guide + footer
def _build_agnostic_rename_readme(
destination: str, original: str, alternatives: list[str],
) -> str:
"""Build a README explaining an agnostic file rename."""
lines = [
"This file was renamed for compatibility:",
f" {destination} <- {original}",
"",
]
if alternatives:
lines.append("All variants included in this pack:")
for alt in sorted(alternatives):
lines.append(f" {alt}")
lines.append("")
lines.append(f"To use a different variant, rename it to: {destination}")
return "\n".join(lines) + "\n"
def generate_pack(
platform_name: str,
platforms_dir: str,
@@ -788,10 +891,71 @@ def generate_pack(
continue
if status == "not_found":
if not already_packed:
missing_files.append(file_entry["name"])
file_status[dedup_key] = "missing"
continue
# Agnostic fallback: if an agnostic core covers this system,
# find any matching file in the DB
by_name = db.get("indexes", {}).get("by_name", {})
files_db = db.get("files", {})
agnostic_path = None
agnostic_resolved = False
if emu_profiles:
for _emu_key, _emu_prof in emu_profiles.items():
if _emu_prof.get("bios_mode") != "agnostic":
continue
if sys_id not in set(_emu_prof.get("systems", [])):
continue
for _ef in _emu_prof.get("files", []):
ef_name = _ef.get("name", "")
for _sha1 in by_name.get(ef_name, []):
_entry = files_db.get(_sha1, {})
_path = _entry.get("path", "")
if _path:
_prefix = _path.rsplit("/", 1)[0] + "/"
_min = _ef.get("min_size", 0)
_max = _ef.get("max_size", float("inf"))
if _ef.get("size") and not _min:
_min = _ef["size"]
_max = _ef["size"]
for _s, _e in files_db.items():
if _e.get("path", "").startswith(_prefix):
if _min <= _e.get("size", 0) <= _max:
if os.path.exists(_e["path"]):
local_path = _e["path"]
agnostic_path = _prefix
agnostic_resolved = True
break
break
if agnostic_resolved:
break
if agnostic_resolved:
break
if agnostic_resolved and local_path:
# Write rename README
original_name = os.path.basename(local_path)
dest_name = file_entry.get("name", "")
if original_name != dest_name and agnostic_path:
alt_names = []
for _s, _e in files_db.items():
_p = _e.get("path", "")
if _p.startswith(agnostic_path):
_n = _e.get("name", "")
if _n and _n != original_name:
alt_names.append(_n)
readme_text = _build_agnostic_rename_readme(
dest_name, original_name, alt_names,
)
readme_name = f"RENAMED_{dest_name}.txt"
readme_full = f"{base_dest}/{readme_name}" if base_dest else readme_name
if readme_full not in seen_destinations:
zf.writestr(readme_full, readme_text)
seen_destinations.add(readme_full)
status = "agnostic_fallback"
# Fall through to normal packing below
else:
if not already_packed:
missing_files.append(file_entry["name"])
file_status[dedup_key] = "missing"
continue
if status == "hash_mismatch" and verification_mode != "existence":
zf_name = file_entry.get("zipped_file")

View File

@@ -18,7 +18,7 @@ from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import list_registered_platforms, load_database, load_platform_config
from common import list_registered_platforms, load_database, load_platform_config, write_if_changed
from verify import verify_platform
def compute_coverage(platform_name: str, platforms_dir: str, db: dict) -> dict:
@@ -316,14 +316,12 @@ def main():
db = load_database(args.db)
readme = generate_readme(db, args.platforms_dir)
with open("README.md", "w") as f:
f.write(readme)
print(f"Generated ./README.md")
status = "Generated" if write_if_changed("README.md", readme) else "Unchanged"
print(f"{status} ./README.md")
contributing = generate_contributing()
with open("CONTRIBUTING.md", "w") as f:
f.write(contributing)
print(f"Generated ./CONTRIBUTING.md")
status = "Generated" if write_if_changed("CONTRIBUTING.md", contributing) else "Unchanged"
print(f"{status} ./CONTRIBUTING.md")
if __name__ == "__main__":

View File

@@ -20,7 +20,7 @@ from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import list_registered_platforms, load_database, load_emulator_profiles, load_platform_config, require_yaml
from common import list_registered_platforms, load_database, load_emulator_profiles, load_platform_config, require_yaml, write_if_changed
yaml = require_yaml()
from generate_readme import compute_coverage
@@ -2064,7 +2064,7 @@ def main():
# Generate home
print("Generating home page...")
(docs / "index.md").write_text(generate_home(db, coverages, profiles, registry))
write_if_changed(str(docs / "index.md"), generate_home(db, coverages, profiles, registry))
# Build system_id -> manufacturer page map (needed by all generators)
print("Building system cross-reference map...")
@@ -2074,37 +2074,35 @@ def main():
# Generate platform pages
print("Generating platform pages...")
(docs / "platforms" / "index.md").write_text(generate_platform_index(coverages))
write_if_changed(str(docs / "platforms" / "index.md"), generate_platform_index(coverages))
for name, cov in coverages.items():
(docs / "platforms" / f"{name}.md").write_text(generate_platform_page(name, cov, registry, emulator_files))
write_if_changed(str(docs / "platforms" / f"{name}.md"), generate_platform_page(name, cov, registry, emulator_files))
# Generate system pages
print("Generating system pages...")
(docs / "systems" / "index.md").write_text(generate_systems_index(manufacturers))
write_if_changed(str(docs / "systems" / "index.md"), generate_systems_index(manufacturers))
for mfr, consoles in manufacturers.items():
slug = mfr.lower().replace(" ", "-")
page = generate_system_page(mfr, consoles, platform_files, emulator_files)
(docs / "systems" / f"{slug}.md").write_text(page)
write_if_changed(str(docs / "systems" / f"{slug}.md"), page)
# Generate emulator pages
print("Generating emulator pages...")
(docs / "emulators" / "index.md").write_text(generate_emulators_index(profiles))
write_if_changed(str(docs / "emulators" / "index.md"), generate_emulators_index(profiles))
for name, profile in profiles.items():
page = generate_emulator_page(name, profile, db, platform_files)
(docs / "emulators" / f"{name}.md").write_text(page)
write_if_changed(str(docs / "emulators" / f"{name}.md"), page)
# Generate cross-reference page
print("Generating cross-reference page...")
(docs / "cross-reference.md").write_text(
generate_cross_reference(coverages, profiles)
)
write_if_changed(str(docs / "cross-reference.md"),
generate_cross_reference(coverages, profiles))
# Generate gap analysis page
print("Generating gap analysis page...")
(docs / "gaps.md").write_text(
generate_gap_analysis(profiles, coverages, db)
)
write_if_changed(str(docs / "gaps.md"),
generate_gap_analysis(profiles, coverages, db))
# Wiki pages: copy manually maintained sources + generate dynamic ones
print("Generating wiki pages...")
@@ -2115,11 +2113,11 @@ def main():
for src_file in wiki_src.glob("*.md"):
shutil.copy2(src_file, wiki_dest / src_file.name)
# data-model.md is generated (contains live DB stats)
(wiki_dest / "data-model.md").write_text(generate_wiki_data_model(db, profiles))
write_if_changed(str(wiki_dest / "data-model.md"), generate_wiki_data_model(db, profiles))
# Generate contributing
print("Generating contributing page...")
(docs / "contributing.md").write_text(generate_contributing())
write_if_changed(str(docs / "contributing.md"), generate_contributing())
# Update mkdocs.yml nav section only (avoid yaml.dump round-trip mangling quotes)
print("Updating mkdocs.yml nav...")
@@ -2173,9 +2171,7 @@ markdown_extensions:
plugins:
- search
"""
with open("mkdocs.yml", "w") as f:
f.write(mkdocs_static)
f.write(nav_yaml)
write_if_changed("mkdocs.yml", mkdocs_static + nav_yaml)
total_pages = (
1 # home

View File

@@ -8,6 +8,7 @@ import urllib.request
import urllib.error
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
@dataclass
@@ -231,7 +232,28 @@ def scraper_cli(scraper_class: type, description: str = "Scrape BIOS requirement
if req.zipped_file:
entry["zipped_file"] = req.zipped_file
config["systems"][sys_id]["files"].append(entry)
with open(args.output, "w") as f:
# Merge into existing YAML: preserve fields the scraper doesn't generate
# (data_directories, case_insensitive_fs, manually added metadata).
# The scraper replaces systems + files; everything else is preserved.
output_path = Path(args.output)
if output_path.exists():
with open(output_path) as f:
existing = yaml.safe_load(f) or {}
# Preserve existing keys not generated by the scraper.
# Only keys present in the NEW config are considered scraper-generated.
# Everything else in the existing file is preserved.
for key, val in existing.items():
if key not in config:
config[key] = val
# Preserve per-system fields not generated by the scraper
# (data_directories, native_id from manual additions, etc.)
existing_systems = existing.get("systems", {})
for sys_id, sys_data in config.get("systems", {}).items():
old_sys = existing_systems.get(sys_id, {})
for field in ("data_directories",):
if field in old_sys and field not in sys_data:
sys_data[field] = old_sys[field]
with open(output_path, "w") as f:
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
print(f"Written {len(reqs)} entries to {args.output}")
return

View File

@@ -303,12 +303,25 @@ class Scraper(BaseScraper):
"""Generate a platform YAML config dict from scraped data."""
requirements = self.fetch_requirements()
# Parse source to extract display names per system
raw = self._fetch_raw()
source_dict = self._extract_systems_dict(raw)
display_names: dict[str, str] = {}
for sys_key, sys_data in source_dict.items():
dname = sys_data.get("name", "")
if dname:
slug = SYSTEM_SLUG_MAP.get(sys_key, sys_key)
display_names[slug] = dname
systems = {}
for req in requirements:
if req.system not in systems:
sys_entry: dict = {"files": []}
if req.native_id:
sys_entry["native_id"] = req.native_id
dname = display_names.get(req.system)
if dname:
sys_entry["name"] = dname
systems[req.system] = sys_entry
entry = {

View File

@@ -121,10 +121,25 @@ class Scraper(BaseScraper):
"""Generate a platform YAML config dict from scraped data."""
requirements = self.fetch_requirements()
# Parse source to extract display names per system
raw = self._fetch_raw()
source_data = json.loads(raw)
display_names: dict[str, str] = {}
for sys_key, sys_data in source_data.items():
if isinstance(sys_data, dict):
dname = sys_data.get("name", "")
if dname:
slug = SYSTEM_SLUG_MAP.get(sys_key, sys_key)
display_names[slug] = dname
systems = {}
for req in requirements:
if req.system not in systems:
systems[req.system] = {"files": []}
sys_entry: dict = {"files": []}
dname = display_names.get(req.system)
if dname:
sys_entry["name"] = dname
systems[req.system] = sys_entry
entry = {
"name": req.name,

View File

@@ -291,6 +291,10 @@ def find_undeclared_files(
if emu_name not in relevant:
continue
# Skip agnostic profiles entirely (filename-agnostic BIOS detection)
if profile.get("bios_mode") == "agnostic":
continue
# Check if this profile is standalone: match profile name or any cores: alias
is_standalone = emu_name in standalone_set or bool(
standalone_set & {str(c) for c in profile.get("cores", [])}
@@ -317,6 +321,10 @@ def find_undeclared_files(
if load_from and load_from != "system_dir":
continue
# Skip agnostic files (filename-agnostic, handled by agnostic scan)
if f.get("agnostic"):
continue
archive = f.get("archive")
# Skip files declared by the platform (by name or archive)

View File

@@ -170,6 +170,7 @@ class TestE2E(unittest.TestCase):
"md5": info["md5"],
"name": name,
"crc32": info.get("crc32", ""),
"size": len(info["data"]),
}
by_md5[info["md5"]] = sha1
by_name.setdefault(name, []).append(sha1)
@@ -510,6 +511,33 @@ class TestE2E(unittest.TestCase):
with open(os.path.join(self.emulators_dir, "test_renamed.yml"), "w") as fh:
yaml.dump(emu_renamed, fh)
# Agnostic profile (bios_mode: agnostic) — skipped by find_undeclared_files
emu_agnostic = {
"emulator": "TestAgnostic",
"type": "standalone",
"bios_mode": "agnostic",
"systems": ["console-a"],
"files": [
{"name": "correct_hash.bin", "required": True,
"min_size": 1, "max_size": 999999},
],
}
with open(os.path.join(self.emulators_dir, "test_agnostic.yml"), "w") as fh:
yaml.dump(emu_agnostic, fh)
# Mixed profile with per-file agnostic
emu_mixed_agnostic = {
"emulator": "TestMixedAgnostic",
"type": "libretro",
"systems": ["console-a"],
"files": [
{"name": "undeclared_req.bin", "required": True},
{"name": "agnostic_file.bin", "required": True, "agnostic": True},
],
}
with open(os.path.join(self.emulators_dir, "test_mixed_agnostic.yml"), "w") as fh:
yaml.dump(emu_mixed_agnostic, fh)
# ---------------------------------------------------------------
# THE TEST -one method per feature area, all using same fixtures
# ---------------------------------------------------------------
@@ -3142,7 +3170,8 @@ class TestE2E(unittest.TestCase):
self.assertIn("Sony - PlayStation", content)
self.assertIn("scph5501.bin", content)
self.assertIn("b056ee5a4d65937e1a3a17e1e78f3258ea49c38e", content)
self.assertIn('name "System.dat"', content)
self.assertIn('name "System"', content)
self.assertIn("71AF80B4", content) # CRC uppercase
issues = exporter.validate(truth, out_path)
self.assertEqual(issues, [])
@@ -3411,6 +3440,173 @@ class TestE2E(unittest.TestCase):
self.assertEqual(result["summary"]["total_missing"], 0)
self.assertEqual(result["summary"]["systems_compared"], 1)
def test_179_agnostic_profile_skipped_in_undeclared(self):
"""bios_mode: agnostic profiles are skipped entirely by find_undeclared_files."""
config = load_platform_config("test_existence", self.platforms_dir)
profiles = load_emulator_profiles(self.emulators_dir)
undeclared = find_undeclared_files(config, self.emulators_dir, self.db, profiles)
emulators = {u["emulator"] for u in undeclared}
# TestAgnostic should NOT appear in undeclared (bios_mode: agnostic)
self.assertNotIn("TestAgnostic", emulators)
def test_180_agnostic_file_skipped_in_undeclared(self):
"""Files with agnostic: true are skipped, others in same profile are not."""
config = load_platform_config("test_existence", self.platforms_dir)
profiles = load_emulator_profiles(self.emulators_dir)
undeclared = find_undeclared_files(config, self.emulators_dir, self.db, profiles)
names = {u["name"] for u in undeclared}
# agnostic_file.bin should NOT be in undeclared (agnostic: true)
self.assertNotIn("agnostic_file.bin", names)
# undeclared_req.bin should still be in undeclared (not agnostic)
self.assertIn("undeclared_req.bin", names)
def test_181_agnostic_extras_scan(self):
"""Agnostic profiles add all matching DB files as extras."""
from generate_pack import _collect_emulator_extras
config = load_platform_config("test_existence", self.platforms_dir)
profiles = load_emulator_profiles(self.emulators_dir)
extras = _collect_emulator_extras(
config, self.emulators_dir, self.db, set(), "system", profiles,
)
agnostic_extras = [e for e in extras if e.get("source_emulator") == "TestAgnostic"]
# Agnostic scan should find files in the same directory as correct_hash.bin
self.assertTrue(len(agnostic_extras) > 0, "Agnostic scan should produce extras")
# All agnostic extras should have agnostic_scan flag
for e in agnostic_extras:
self.assertTrue(e.get("agnostic_scan", False))
def test_182_agnostic_rename_readme(self):
"""_build_agnostic_rename_readme generates correct text."""
from generate_pack import _build_agnostic_rename_readme
result = _build_agnostic_rename_readme(
"dsi_nand.bin", "DSi_Nand_AUS.bin",
["DSi_Nand_EUR.bin", "DSi_Nand_USA.bin"],
)
self.assertIn("dsi_nand.bin <- DSi_Nand_AUS.bin", result)
self.assertIn("DSi_Nand_EUR.bin", result)
self.assertIn("DSi_Nand_USA.bin", result)
self.assertIn("rename it to: dsi_nand.bin", result)
def test_183_agnostic_resolve_fallback(self):
"""resolve_local_file with agnostic fallback finds a system file."""
file_entry = {
"name": "nonexistent_agnostic.bin",
"agnostic": True,
"min_size": 1,
"max_size": 999999,
"agnostic_path_prefix": self.bios_dir + "/",
}
path, status = resolve_local_file(file_entry, self.db)
self.assertIsNotNone(path)
self.assertEqual(status, "agnostic_fallback")
def test_179_batocera_exporter_round_trip(self):
"""Batocera exporter produces valid Python dict format."""
from exporter.batocera_exporter import Exporter
truth = {
"systems": {
"sony-playstation": {
"_coverage": {"cores_profiled": ["c"]},
"files": [
{"name": "scph5501.bin", "destination": "scph5501.bin",
"required": True, "md5": "b" * 32,
"_cores": ["c"], "_source_refs": []},
],
}
}
}
scraped = {
"systems": {
"sony-playstation": {"native_id": "psx", "files": []},
}
}
out = os.path.join(self.root, "batocera-systems")
exp = Exporter()
exp.export(truth, out, scraped_data=scraped)
content = open(out).read()
self.assertIn('"psx"', content)
self.assertIn("scph5501.bin", content)
self.assertIn("b" * 32, content)
self.assertEqual(exp.validate(truth, out), [])
def test_180_recalbox_exporter_round_trip(self):
"""Recalbox exporter produces valid es_bios.xml."""
from exporter.recalbox_exporter import Exporter
truth = {
"systems": {
"sony-playstation": {
"_coverage": {"cores_profiled": ["c"]},
"files": [
{"name": "scph5501.bin", "destination": "scph5501.bin",
"required": True, "md5": "b" * 32,
"_cores": ["c"], "_source_refs": []},
],
}
}
}
scraped = {
"systems": {
"sony-playstation": {"native_id": "psx", "files": []},
}
}
out = os.path.join(self.root, "es_bios.xml")
exp = Exporter()
exp.export(truth, out, scraped_data=scraped)
content = open(out).read()
self.assertIn("<biosList", content)
self.assertIn('platform="psx"', content)
self.assertIn('fullname=', content)
self.assertIn("scph5501.bin", content)
# mandatory="true" is the default, not emitted (matching Recalbox format)
self.assertNotIn('mandatory="false"', content)
self.assertIn('core="libretro/c"', content)
self.assertEqual(exp.validate(truth, out), [])
def test_181_retrobat_exporter_round_trip(self):
"""RetroBat exporter produces valid JSON."""
import json as _json
from exporter.retrobat_exporter import Exporter
truth = {
"systems": {
"sony-playstation": {
"_coverage": {"cores_profiled": ["c"]},
"files": [
{"name": "scph5501.bin", "destination": "scph5501.bin",
"required": True, "md5": "b" * 32,
"_cores": ["c"], "_source_refs": []},
],
}
}
}
scraped = {
"systems": {
"sony-playstation": {"native_id": "psx", "files": []},
}
}
out = os.path.join(self.root, "batocera-systems.json")
exp = Exporter()
exp.export(truth, out, scraped_data=scraped)
data = _json.loads(open(out).read())
self.assertIn("psx", data)
self.assertTrue(any("scph5501" in bf["file"] for bf in data["psx"]["biosFiles"]))
self.assertEqual(exp.validate(truth, out), [])
def test_182_exporter_discovery(self):
"""All exporters are discovered by the plugin system."""
from exporter import discover_exporters
exporters = discover_exporters()
self.assertIn("retroarch", exporters)
self.assertIn("batocera", exporters)
self.assertIn("recalbox", exporters)
self.assertIn("retrobat", exporters)
if __name__ == "__main__":
unittest.main()