mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-13 04:12:33 -05:00
Compare commits
20 Commits
a8430940f9
...
caf6285a04
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf6285a04 | ||
|
|
529cb8a915 | ||
|
|
1146fdf177 | ||
|
|
4fbb3571f8 | ||
|
|
0be68edad0 | ||
|
|
1ffc4f89ca | ||
|
|
f1ebfff5bd | ||
|
|
425ea064ae | ||
|
|
6818a18a42 | ||
|
|
c11de6dba6 | ||
|
|
c4f3192020 | ||
|
|
e2d0510f4e | ||
|
|
74269bab84 | ||
|
|
1e6b499602 | ||
|
|
9b785ec785 | ||
|
|
d415777f2c | ||
|
|
eafabd20f3 | ||
|
|
2aca4927c0 | ||
|
|
17777f315b | ||
|
|
692484d32d |
@@ -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*
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
111
scripts/exporter/batocera_exporter.py
Normal file
111
scripts/exporter/batocera_exporter.py
Normal 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
|
||||
207
scripts/exporter/emudeck_exporter.py
Normal file
207
scripts/exporter/emudeck_exporter.py
Normal 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
|
||||
17
scripts/exporter/lakka_exporter.py
Normal file
17
scripts/exporter/lakka_exporter.py
Normal 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"
|
||||
130
scripts/exporter/recalbox_exporter.py
Normal file
130
scripts/exporter/recalbox_exporter.py
Normal 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
|
||||
114
scripts/exporter/retrobat_exporter.py
Normal file
114
scripts/exporter/retrobat_exporter.py
Normal 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
|
||||
208
scripts/exporter/retrodeck_exporter.py
Normal file
208
scripts/exporter/retrodeck_exporter.py
Normal 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
|
||||
17
scripts/exporter/retropie_exporter.py
Normal file
17
scripts/exporter/retropie_exporter.py
Normal 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"
|
||||
160
scripts/exporter/romm_exporter.py
Normal file
160
scripts/exporter/romm_exporter.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user