6 Commits

Author SHA1 Message Date
github-actions[bot]
814ad98c9c regenerate database and docs 2026-03-19 16:29:07 +00:00
monster-penguin
1fcb948a00 Add RetroDECK Platform Support (#36)
* Add files via upload

* Add files via upload

* Update _registry.yml
2026-03-19 17:10:37 +01:00
github-actions[bot]
4992af07e7 regenerate database and docs 2026-03-19 15:15:53 +00:00
Abdessamad Derraz
6a21a99c22 feat: platform-core registry for exact pack generation
resolve_platform_cores() links platforms to their cores via
three strategies: all_libretro, explicit list, system ID
fallback. Pack generation always includes core requirements
beyond platform baseline. Case-insensitive dedup prevents
conflicts on Windows/macOS. Data dir strip_components fixes
doubled paths for Dolphin and PPSSPP caches.
2026-03-19 16:10:43 +01:00
Abdessamad Derraz
257ec1a527 fix: round 2 audit fixes, updated emulator profiles
Scripts:
- fix generate_site nav regex destroying mkdocs.yml content
- fix auto_fetch comma-separated MD5 in find_missing
- fix verify print_platform_result conflating untested/missing
- fix validate_pr path traversal and symlink check
- fix batocera_scraper brace counting and escaped quotes in strings
- fix emudeck_scraper hash search crossing function boundaries
- fix pipeline.py cwd to repo root via Path(__file__)
- normalize SHA1 comparison to lowercase in generate_pack

Emulator profiles:
- emux_gb/nes/sms: reclassify from alias to standalone profiles
- ep128emu: remove .info-only files not referenced in source
- fbalpha2012 variants: full source-verified profiles
- fbneo_cps12: add new profile
2026-03-19 15:00:18 +01:00
Abdessamad Derraz
38d605c7d5 fix: audit fixes across verify, pack, security, and performance
- fix KeyError in compute_coverage (generate_readme, generate_site)
- fix comma-separated MD5 handling in generate_pack check_inside_zip
- fix _verify_file_hash to handle multi-MD5 for large files
- fix external downloads not tracked in seen_destinations/file_status
- fix tar path traversal in _is_safe_tar_member (refresh_data_dirs)
- fix predictable tmp path in download.py
- fix _sanitize_path to filter "." components
- remove blanket data_dir suppression in find_undeclared_files
- remove blanket data_dir suppression in cross_reference
- add status_counts to verify_platform return value
- add md5_composite cache for repeated ZIP hashing
2026-03-19 14:04:34 +01:00
36 changed files with 2646 additions and 437 deletions

View File

@@ -2,31 +2,33 @@
Complete, verified collection of BIOS, firmware, and system files for retrogaming emulators.
> **8085** files | **4686.1 MB** | **7** platforms | **265** emulator profiles
> **5593** files | **4681.6 MB** | **8** platforms | **275** emulator profiles
## Download
| Platform | Files | Verification | Pack |
|----------|-------|-------------|------|
| Batocera | 680 | md5 | [Download](../../releases/latest) |
| EmuDeck | 164 | md5 | [Download](../../releases/latest) |
| Lakka | 452 | existence | [Download](../../releases/latest) |
| Batocera | 359 | md5 | [Download](../../releases/latest) |
| EmuDeck | 161 | md5 | [Download](../../releases/latest) |
| Lakka | 448 | existence | [Download](../../releases/latest) |
| Recalbox | 346 | md5 | [Download](../../releases/latest) |
| RetroArch | 452 | existence | [Download](../../releases/latest) |
| RetroBat | 343 | md5 | [Download](../../releases/latest) |
| RetroPie | 452 | existence | [Download](../../releases/latest) |
| RetroArch | 448 | existence | [Download](../../releases/latest) |
| RetroBat | 331 | md5 | [Download](../../releases/latest) |
| RetroDECK | 117 | md5 | [Download](../../releases/latest) |
| RetroPie | 448 | existence | [Download](../../releases/latest) |
## Coverage
| Platform | Coverage | Verified | Untested | Missing |
|----------|----------|----------|----------|---------|
| Batocera | 680/680 (100.0%) | 679 | 1 | 0 |
| EmuDeck | 164/164 (100.0%) | 164 | 0 | 0 |
| Lakka | 452/452 (100.0%) | 452 | 0 | 0 |
| Batocera | 359/359 (100.0%) | 358 | 1 | 0 |
| EmuDeck | 161/161 (100.0%) | 161 | 0 | 0 |
| Lakka | 448/448 (100.0%) | 448 | 0 | 0 |
| Recalbox | 346/346 (100.0%) | 346 | 0 | 0 |
| RetroArch | 452/452 (100.0%) | 452 | 0 | 0 |
| RetroBat | 343/343 (100.0%) | 343 | 0 | 0 |
| RetroPie | 452/452 (100.0%) | 452 | 0 | 0 |
| RetroArch | 448/448 (100.0%) | 448 | 0 | 0 |
| RetroBat | 331/331 (100.0%) | 331 | 0 | 0 |
| RetroDECK | 102/117 (87.2%) | 99 | 3 | 15 |
| RetroPie | 448/448 (100.0%) | 448 | 0 | 0 |
## Documentation
@@ -40,4 +42,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
This repository provides BIOS files for personal backup and archival purposes.
*Auto-generated on 2026-03-18T14:18:36Z*
*Auto-generated on 2026-03-19T16:27:21Z*

View File

@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-19T12:13:36Z",
"generated_at": "2026-03-19T16:27:21Z",
"total_files": 5593,
"total_size": 4909044289,
"files": {
@@ -71649,6 +71649,192 @@
"SCPH-70004_BIOS_V12_EUR_200.ROM2": [
"1bae895fbdd658cfb56c53cc2139282cc1e778de"
],
"tos100uk.img": [
"9a6e4c88533a9eaa4d55cdc040e47443e0226eb2"
],
"tos106de.img": [
"3b8cf5ffa41b252eb67f8824f94608fa4005d6dd"
],
"tos206us.img": [
"ee58768bdfc602c9b14942ce5481e97dd24e7c83"
],
"saturn_bios.bin": [
"2b8cb4f87580683eb4d760e4ed210813d667f0a2"
],
"sega-saturn:af5828fdff51384f99b3c4926be27762": [
"2b8cb4f87580683eb4d760e4ed210813d667f0a2"
],
"bios7.bin": [
"24f67bdea115a2c847c8813a262502ee1607b7df"
],
"bios9.bin": [
"bfaac75f101c135e32e2aaf541de6b1be4c8c62d"
],
"dsi_bios7.bin": [
"a3aa751eb6bdaaf8a827ba9e03576a6f1ab0f547"
],
"dsi_bios9.bin": [
"7bf549b8be9e48ab0cdc9b0fdadd49a5131f97eb"
],
"gb_bios.bin": [
"4ed31ec6b0b175bb109c0eb5fd3d193da823339f",
"1db57a1e8b6e4096f811587f9eab0c6675fd9755"
],
"sgb_bios.bin": [
"aa2f50a77dfb4823da96ba99309085a3c6278515",
"369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de"
],
"sgb1.boot.rom": [
"aa2f50a77dfb4823da96ba99309085a3c6278515"
],
"SGB1.sfc": [
"973e10840db683cf3faf61bd443090786b3a9f04"
],
"SGB1.sfc/program.rom": [
"973e10840db683cf3faf61bd443090786b3a9f04"
],
"SGB2.sfc": [
"e5b2922ca137051059e4269b236d07a22c07bc84"
],
"SGB2.sfc/program.rom": [
"e5b2922ca137051059e4269b236d07a22c07bc84"
],
"fs-a1gt_kanjifont.rom": [
"5aff2d9b6efc723bc395b0f96f0adfa83cc54a49"
],
"fs-a1wsx_kanjifont.rom": [
"5aff2d9b6efc723bc395b0f96f0adfa83cc54a49"
],
"yrw801.rom": [
"32760893ce06dbe3930627755ba065cc3d8ec6ca"
],
"fs-a1wsx_msx2psub.rom": [
"fe0254cbfc11405b79e7c86c7769bd6322b04995"
],
"MSX2PEXT.rom": [
"fe0254cbfc11405b79e7c86c7769bd6322b04995"
],
"fs-a1wsx_kanjibasic.rom": [
"dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06"
],
"fs-a1wsx_disk.rom": [
"7ed7c55e0359737ac5e68d38cb6903f9e5d7c2b6"
],
"kick37350.A600": [
"02843c4253bbd29aba535b0aa3bd9a85034ecde4"
],
"amiga-os-120.rom": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
],
"Kickstart v1.2 rev 33.180 (1986)(Commodore)(A500-A2000)[!].rom": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
],
"Kickstart v1.2 rev 33.180 (1986)(Commodore)(A500-A1000-A2000).rom": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
],
"amiga-os-130.rom": [
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785"
],
"Kickstart v1.3 rev 34.5 (1987)(Commodore)(A500-A1000-A2000-CDTV)[!].rom": [
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785"
],
"Kickstart v1.3 rev 34.5 (1987)(Commodore)(A500-A1000-A2000-CDTV).rom": [
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785"
],
"amiga-os-204.rom": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
],
"Kickstart v2.04 rev 37.175 (1991)(Commodore)(A500+)[!].rom": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
],
"Kickstart v2.04 rev 37.175 (1991)(Commodore)(A500+).rom": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
],
"amiga-os-310-a600.rom": [
"3b7f1493b27e212830f989f26ca76c02049f09ca"
],
"Kickstart v3.1 rev 40.63 (1993)(Commodore)(A500-A600-A2000)[!].rom": [
"3b7f1493b27e212830f989f26ca76c02049f09ca"
],
"Kickstart v3.1 rev 40.63 (1993)(Commodore)(A500-A600-A2000).rom": [
"3b7f1493b27e212830f989f26ca76c02049f09ca"
],
"amiga-ext-130-cdtv.rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"amiga-os-130-cdtv-ext.rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"CDTV Extended-ROM v1.0 (1991)(Commodore)(CDTV)[!].rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"CDTV Extended-ROM v1.0 (1992)(Commodore)(CDTV).rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"amiga-os-300-a1200.rom": [
"70033828182fffc7ed106e5373a8b89dda76faa5"
],
"Kickstart v3.0 rev 39.106 (1992)(Commodore)(A1200)[!].rom": [
"70033828182fffc7ed106e5373a8b89dda76faa5"
],
"amiga-os-310-a1200.rom": [
"e21545723fe8374e91342617604f1b3d703094f1"
],
"Kickstart v3.1 rev 40.68 (1993)(Commodore)(A1200)[!].rom": [
"e21545723fe8374e91342617604f1b3d703094f1"
],
"Kickstart v3.1 rev 40.68 (1993)(Commodore)(A1200).rom": [
"e21545723fe8374e91342617604f1b3d703094f1"
],
"amiga-os-310-a4000.rom": [
"5fe04842d04a489720f0f4bb0e46948199406f49"
],
"Kickstart v3.1 rev 40.68 (1993)(Commodore)(A4000).rom": [
"5fe04842d04a489720f0f4bb0e46948199406f49"
],
"amiga-os-310-cd32.rom": [
"3525be8887f79b5929e017b42380a79edfee542d"
],
"Kickstart v3.1 rev 40.60 (1993)(Commodore)(CD32).rom": [
"3525be8887f79b5929e017b42380a79edfee542d"
],
"amiga-ext-310-cd32.rom": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
],
"amiga-os-310-cd32-ext.rom": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
],
"CD32 Extended-ROM rev 40.60 (1993)(Commodore)(CD32).rom": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
],
"custom0.sf2": [
"286b2e1fb21cc79851da01666db6c0b0e88f25e3"
],
"colecovision.rom": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
"coleco.rom": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
"boot.rom": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
"IPL.bin": [
"80b8744ff5e43585392f55546bd03a673d11ef5f",
"ef9194ab4804aa0aa8540d846caf291b28331165",
"f27c63e5394e2fd1606f70df004c4fc2d6027700",
"a1837968288253ed541f2b11440b68f5a9b33875",
"f3cd0c7c61cbcefa85e7de3aff4cfa50bc508714"
],
"basic11.rom": [
"9451a1a09d8f75944dbd6f91193fc360f1de80ac"
],
"basic21.bin": [
"03bbb386cf530e804363acdfc1d13e64cf28af2e"
],
"exos21.bin": [
"55315b20fecb4441a07ee4bc5dc7153f396e0a2e"
],
"sony-playstation:239665b1a3dade1b5a52c06338011044": [
"343883a7b555646da8cee54aadd2795b6e7dd070"
],
@@ -72060,12 +72246,6 @@
"sega-mega-cd:baca1df271d7c11fe50087c0358f4eb5": [
"2b125c0545afa089b617f2558e686ea723bdc06e"
],
"sega-saturn:af5828fdff51384f99b3c4926be27762": [
"2b8cb4f87580683eb4d760e4ed210813d667f0a2"
],
"saturn_bios.bin": [
"2b8cb4f87580683eb4d760e4ed210813d667f0a2"
],
"sega-saturn:85ec9ca47d8f6807718151cbcca8b964": [
"df94c5b4d47eb3cc404d88b33a8fda237eaf4720"
],
@@ -72087,185 +72267,24 @@
"sega-saturn:0306c0e408d6682dd2d86324bd4ac661": [
"8c031bf9908fd0142fdd10a9cdd79389f8a3f2fc"
],
"tos100uk.img": [
"9a6e4c88533a9eaa4d55cdc040e47443e0226eb2"
"gexpress.pce": [
"014881a959e045e00f4db8f52955200865d40280"
],
"tos106de.img": [
"3b8cf5ffa41b252eb67f8824f94608fa4005d6dd"
"pcfxbios.bin": [
"1a77fd83e337f906aecab27a1604db064cf10074"
],
"tos206us.img": [
"ee58768bdfc602c9b14942ce5481e97dd24e7c83"
"sgb2_bios.bin": [
"93407ea10d2f30ab96a314d8eca44fe160aea734",
"f282b3aaf98f8423dab7d77f1aa0192be630f2fb"
],
"bios7.bin": [
"24f67bdea115a2c847c8813a262502ee1607b7df"
"flash.bin": [
"94d44d7f9529ec1642ba3771ed3c5f756d5bc872"
],
"bios9.bin": [
"bfaac75f101c135e32e2aaf541de6b1be4c8c62d"
"plus3e-3.rom": [
"65f031caa8148a5493afe42c41f4929deab26b4e"
],
"dsi_bios7.bin": [
"a3aa751eb6bdaaf8a827ba9e03576a6f1ab0f547"
],
"dsi_bios9.bin": [
"7bf549b8be9e48ab0cdc9b0fdadd49a5131f97eb"
],
"gb_bios.bin": [
"4ed31ec6b0b175bb109c0eb5fd3d193da823339f",
"1db57a1e8b6e4096f811587f9eab0c6675fd9755"
],
"sgb_bios.bin": [
"aa2f50a77dfb4823da96ba99309085a3c6278515",
"369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de"
],
"sgb1.boot.rom": [
"aa2f50a77dfb4823da96ba99309085a3c6278515"
],
"SGB1.sfc": [
"973e10840db683cf3faf61bd443090786b3a9f04"
],
"SGB1.sfc/program.rom": [
"973e10840db683cf3faf61bd443090786b3a9f04"
],
"SGB2.sfc": [
"e5b2922ca137051059e4269b236d07a22c07bc84"
],
"SGB2.sfc/program.rom": [
"e5b2922ca137051059e4269b236d07a22c07bc84"
],
"fs-a1gt_kanjifont.rom": [
"5aff2d9b6efc723bc395b0f96f0adfa83cc54a49"
],
"fs-a1wsx_kanjifont.rom": [
"5aff2d9b6efc723bc395b0f96f0adfa83cc54a49"
],
"yrw801.rom": [
"32760893ce06dbe3930627755ba065cc3d8ec6ca"
],
"fs-a1wsx_msx2psub.rom": [
"fe0254cbfc11405b79e7c86c7769bd6322b04995"
],
"MSX2PEXT.rom": [
"fe0254cbfc11405b79e7c86c7769bd6322b04995"
],
"fs-a1wsx_kanjibasic.rom": [
"dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06"
],
"fs-a1wsx_disk.rom": [
"7ed7c55e0359737ac5e68d38cb6903f9e5d7c2b6"
],
"kick37350.A600": [
"02843c4253bbd29aba535b0aa3bd9a85034ecde4"
],
"amiga-os-120.rom": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
],
"Kickstart v1.2 rev 33.180 (1986)(Commodore)(A500-A2000)[!].rom": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
],
"Kickstart v1.2 rev 33.180 (1986)(Commodore)(A500-A1000-A2000).rom": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
],
"amiga-os-130.rom": [
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785"
],
"Kickstart v1.3 rev 34.5 (1987)(Commodore)(A500-A1000-A2000-CDTV)[!].rom": [
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785"
],
"Kickstart v1.3 rev 34.5 (1987)(Commodore)(A500-A1000-A2000-CDTV).rom": [
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785"
],
"amiga-os-204.rom": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
],
"Kickstart v2.04 rev 37.175 (1991)(Commodore)(A500+)[!].rom": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
],
"Kickstart v2.04 rev 37.175 (1991)(Commodore)(A500+).rom": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
],
"amiga-os-310-a600.rom": [
"3b7f1493b27e212830f989f26ca76c02049f09ca"
],
"Kickstart v3.1 rev 40.63 (1993)(Commodore)(A500-A600-A2000)[!].rom": [
"3b7f1493b27e212830f989f26ca76c02049f09ca"
],
"Kickstart v3.1 rev 40.63 (1993)(Commodore)(A500-A600-A2000).rom": [
"3b7f1493b27e212830f989f26ca76c02049f09ca"
],
"amiga-ext-130-cdtv.rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"amiga-os-130-cdtv-ext.rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"CDTV Extended-ROM v1.0 (1991)(Commodore)(CDTV)[!].rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"CDTV Extended-ROM v1.0 (1992)(Commodore)(CDTV).rom": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
],
"amiga-os-300-a1200.rom": [
"70033828182fffc7ed106e5373a8b89dda76faa5"
],
"Kickstart v3.0 rev 39.106 (1992)(Commodore)(A1200)[!].rom": [
"70033828182fffc7ed106e5373a8b89dda76faa5"
],
"amiga-os-310-a1200.rom": [
"e21545723fe8374e91342617604f1b3d703094f1"
],
"Kickstart v3.1 rev 40.68 (1993)(Commodore)(A1200)[!].rom": [
"e21545723fe8374e91342617604f1b3d703094f1"
],
"Kickstart v3.1 rev 40.68 (1993)(Commodore)(A1200).rom": [
"e21545723fe8374e91342617604f1b3d703094f1"
],
"amiga-os-310-a4000.rom": [
"5fe04842d04a489720f0f4bb0e46948199406f49"
],
"Kickstart v3.1 rev 40.68 (1993)(Commodore)(A4000).rom": [
"5fe04842d04a489720f0f4bb0e46948199406f49"
],
"amiga-os-310-cd32.rom": [
"3525be8887f79b5929e017b42380a79edfee542d"
],
"Kickstart v3.1 rev 40.60 (1993)(Commodore)(CD32).rom": [
"3525be8887f79b5929e017b42380a79edfee542d"
],
"amiga-ext-310-cd32.rom": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
],
"amiga-os-310-cd32-ext.rom": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
],
"CD32 Extended-ROM rev 40.60 (1993)(Commodore)(CD32).rom": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
],
"custom0.sf2": [
"286b2e1fb21cc79851da01666db6c0b0e88f25e3"
],
"colecovision.rom": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
"coleco.rom": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
"boot.rom": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
"IPL.bin": [
"80b8744ff5e43585392f55546bd03a673d11ef5f",
"ef9194ab4804aa0aa8540d846caf291b28331165",
"f27c63e5394e2fd1606f70df004c4fc2d6027700",
"a1837968288253ed541f2b11440b68f5a9b33875",
"f3cd0c7c61cbcefa85e7de3aff4cfa50bc508714"
],
"basic11.rom": [
"9451a1a09d8f75944dbd6f91193fc360f1de80ac"
],
"basic21.bin": [
"03bbb386cf530e804363acdfc1d13e64cf28af2e"
],
"exos21.bin": [
"55315b20fecb4441a07ee4bc5dc7153f396e0a2e"
"zx48.rom": [
"5ea7c2b824672e914525d1d5c419d71b84a426a2"
],
"apple2gs1.rom": [
"e4fc7560b69d062cb2da5b1ffbe11cd1ca03cc37"
@@ -72282,9 +72301,6 @@
"quasi88/n88knj1.rom": [
"82e11a177af6a5091dd67f50a2f4bafda84d6556"
],
"gexpress.pce": [
"014881a959e045e00f4db8f52955200865d40280"
],
"FMPAC.rom": [
"9d789166e3caf28e4742fe933d962e99618c633d"
],
@@ -72322,66 +72338,20 @@
"d64tano.rom": [
"1983b4fb398e3dd9668d424c666c5a0b3f1e2b69"
],
"fd502.rom": [
"10bdc5aa2d7d7f205f67b47b19003a4bd89defd1"
],
"d64_1.rom": [
"f119506eaa3b4b70b9aa0dd83761e8cbe043d042"
],
"fs-5500_disk.rom": [
"78cd7f847e77fd8cd51a647efb2725ba93f4c471"
],
"pcfxbios.bin": [
"1a77fd83e337f906aecab27a1604db064cf10074"
"FONT.BMP": [
"b4f14e58030ed40fff2dc312b58ea4440bdf8cc5"
],
"sgb2_bios.bin": [
"93407ea10d2f30ab96a314d8eca44fe160aea734",
"f282b3aaf98f8423dab7d77f1aa0192be630f2fb"
],
"flash.bin": [
"94d44d7f9529ec1642ba3771ed3c5f756d5bc872"
],
"plus3e-3.rom": [
"65f031caa8148a5493afe42c41f4929deab26b4e"
],
"zx48.rom": [
"5ea7c2b824672e914525d1d5c419d71b84a426a2"
],
"amiga-os-300-a4000.rom": [
"f0b4e9e29e12218c2d5bd7020e4e785297d91fd7"
],
"Kickstart v3.0 rev 39.106 (1992)(Commodore)(A4000)[!].rom": [
"f0b4e9e29e12218c2d5bd7020e4e785297d91fd7"
],
"monit10.rom": [
"4e83a94ae5155bbea14d7331a5a8db82457bd5ae"
],
"focal10.rom": [
"6386e58bc1bba5e76baec9e8a1ca4b99dc3c573f"
],
"disk_327.rom": [
"28eefbb63047b26e4aec104aeeca74e2f9d0276c"
],
"b11m_bos.rom": [
"7e9a30e38d7b78981999821640a68a201bb6df01"
],
"b11m_ext.rom": [
"f087af69044432a1ef2431a72ac06946e32f2dd3"
],
"bas11m_0.rom": [
"9d76f3eefd64e032c763fa1ebf9cd3d9bd22317a"
],
"bas11m_1.rom": [
"34fa37599f2f9eb607390ef2458a3c22d87f09a9"
],
"terak.rom": [
"273a9933b68a290c5aedcd6d69faa7b1d22c0344"
],
"upd7801g.bin": [
"6e89d1227581c76441a53d605f9e324185f1da33"
],
"upd7801g.bios": [
"6e89d1227581c76441a53d605f9e324185f1da33"
],
"BS-X.bios": [
"604556b2e62860af18db5a77f2956ebc75450020"
"2608_RYM.WAV": [
"c65592330c9dd84011151daed52f9aec926b7e56"
],
"N88_0.ROM": [
"d1ae642aed4f0584eeb81ff50180db694e5101d4"
@@ -72431,11 +72401,44 @@
"jisyo.rom": [
"deef0cc2a9734ba891a6d6c022aa70ffc66f783e"
],
"FONT.BMP": [
"b4f14e58030ed40fff2dc312b58ea4440bdf8cc5"
"BS-X.bios": [
"604556b2e62860af18db5a77f2956ebc75450020"
],
"2608_RYM.WAV": [
"c65592330c9dd84011151daed52f9aec926b7e56"
"upd7801g.bin": [
"6e89d1227581c76441a53d605f9e324185f1da33"
],
"upd7801g.bios": [
"6e89d1227581c76441a53d605f9e324185f1da33"
],
"amiga-os-300-a4000.rom": [
"f0b4e9e29e12218c2d5bd7020e4e785297d91fd7"
],
"Kickstart v3.0 rev 39.106 (1992)(Commodore)(A4000)[!].rom": [
"f0b4e9e29e12218c2d5bd7020e4e785297d91fd7"
],
"monit10.rom": [
"4e83a94ae5155bbea14d7331a5a8db82457bd5ae"
],
"focal10.rom": [
"6386e58bc1bba5e76baec9e8a1ca4b99dc3c573f"
],
"disk_327.rom": [
"28eefbb63047b26e4aec104aeeca74e2f9d0276c"
],
"b11m_bos.rom": [
"7e9a30e38d7b78981999821640a68a201bb6df01"
],
"b11m_ext.rom": [
"f087af69044432a1ef2431a72ac06946e32f2dd3"
],
"bas11m_0.rom": [
"9d76f3eefd64e032c763fa1ebf9cd3d9bd22317a"
],
"bas11m_1.rom": [
"34fa37599f2f9eb607390ef2458a3c22d87f09a9"
],
"terak.rom": [
"273a9933b68a290c5aedcd6d69faa7b1d22c0344"
],
"dmg0_rom.bin": [
"1db57a1e8b6e4096f811587f9eab0c6675fd9755"

View File

@@ -1,8 +1,54 @@
emulator: "emux_gb"
type: alias
alias_of: "gambatte"
profiled_date: "2026-03-18"
emulator: "emux (Game Boy)"
type: libretro
source: "https://github.com/libretro/emux"
profiled_date: "2026-03-19"
core_version: "0.1"
display_name: "Nintendo - Game Boy / Color (Emux GB)"
note: "This core uses the same BIOS/firmware as gambatte. See emulators/gambatte.yml for details."
files: []
cores:
- emux_gb
systems:
- nintendo-gb
notes: |
emux is a multi-system emulator by Sebastien Ronsse, supporting CHIP-8,
Game Boy, NES, and Sega Master System as separate libretro cores.
NOT an alias of Gambatte. Completely different codebase and BIOS requirements.
Gambatte uses gb_bios.bin/gbc_bios.bin (optional, HLE fallback).
emux_gb uses dmg_boot.bin (required, no HLE, core fails without it).
Boot ROM loading in controllers/mapper/gb_mapper.c:
static char *bootrom_path = "dmg_boot.bin"; (line 32)
gb_mapper->bootrom = file_map(PATH_SYSTEM, bootrom_path, 0, BOOTROM_SIZE);
If file_map returns NULL, gb_mapper_init returns false — core cannot start.
No HLE fallback, no skip logic.
Boot ROM is 256 bytes (BOOTROM_SIZE in gb_mapper.h:10), mapped at 0x0000-0x00FF.
When register 0xFF50 (BOOT_LOCK) is written with non-zero, the bootrom region
is removed from the memory map (lock_writeb, gb_mapper.c:130-138).
Memory map from mach/gb.c:
0x0000-0x00FF Boot ROM (overlays ROM0 until BOOT_LOCK)
0x0000-0x3FFF ROM bank 0 (16 KB)
0x4000-0x7FFF ROM bank 1 (switchable, 16 KB)
0x8000-0x9FFF VRAM (8 KB)
0xA000-0xBFFF External RAM (8 KB)
0xC000-0xDFFF WRAM (8 KB)
0xFE00-0xFE9F OAM (160 bytes)
0xFF80-0xFFFE HRAM (127 bytes)
The .info declares firmware_count=1, firmware0_opt=false.
is_experimental=true. Core does not support save states, rewind, or netplay.
system_directory obtained via RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY
(libretro/libretro.c:23-25), passed as "system-dir" config param.
files:
- name: "dmg_boot.bin"
system: nintendo-gb
description: "Game Boy (DMG) boot ROM"
required: true
size: 256
md5: "32fbbd84168d3482956eb3c5051637f5"
source_ref: "controllers/mapper/gb_mapper.c:32 (bootrom_path), gb_mapper.h:10 (BOOTROM_SIZE=256)"
note: "Mapped at 0x0000-0x00FF, overlays ROM0 until BOOT_LOCK (0xFF50) written. No HLE — core fails to init without this file."

View File

@@ -1,8 +1,51 @@
emulator: "emux_nes"
type: alias
alias_of: "fceumm"
profiled_date: "2026-03-18"
emulator: "emux (NES)"
type: libretro
source: "https://github.com/libretro/emux"
profiled_date: "2026-03-19"
core_version: "0.1"
display_name: "Nintendo - NES / Famicom (Emux NES)"
note: "This core uses the same BIOS/firmware as fceumm. See emulators/fceumm.yml for details."
cores:
- emux_nes
systems:
- nes
notes: |
emux is a multi-system emulator by Sebastien Ronsse, supporting CHIP-8,
Game Boy, NES, and Sega Master System as separate libretro cores.
NOT an alias of FCEUmm. Completely different codebase.
The NES core does not require any BIOS or firmware files.
The .info declares no firmware_count.
The NES has no boot ROM. CPU starts at the reset vector address
stored at 0xFFFC-0xFFFD (cpu/rp2a03.c RESET_VECTOR).
Cartridge loading in controllers/mapper/nes_mapper.c:
- iNES header parsed (magic 0x1A53454E)
- Mapper number extracted from flags6/flags7
- Supported mappers: NROM (0), MMC1 (1), MMC3 (4)
- PRG-ROM and CHR-ROM mapped via file_map(PATH_DATA, ...)
Memory map from mach/nes.c:
CPU bus:
0x0000-0x07FF WRAM (2 KB, mirrored to 0x1FFF)
0x2000-0x2007 PPU registers (mirrored to 0x3FFF)
0x4000-0x4013 APU registers
0x4014 Sprite DMA
0x4016-0x4017 Controller ports
0x6000-0x7FFF SRAM
0x8000-0xFFFF PRG-ROM
PPU bus:
0x0000-0x1FFF CHR-ROM/RAM (pattern tables)
0x2000-0x2FFF VRAM (nametables, 4 KB)
0x3F00-0x3F1F Palette RAM
system_directory obtained via RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY
(libretro/libretro.c:23-25) but not used for any file loading.
is_experimental=true. No save states, rewind, or netplay support.
Supported extensions: nes, bin, rom.
files: []
# No BIOS, firmware, or system files of any kind.

View File

@@ -1,8 +1,55 @@
emulator: "emux_sms"
type: alias
alias_of: "gearsystem"
profiled_date: "2026-03-18"
emulator: "emux (SMS)"
type: libretro
source: "https://github.com/libretro/emux"
profiled_date: "2026-03-19"
core_version: "0.1"
display_name: "Sega - Master System (Emux SMS)"
note: "This core uses the same BIOS/firmware as gearsystem. See emulators/gearsystem.yml for details."
files: []
cores:
- emux_sms
systems:
- sms
notes: |
emux is a multi-system emulator by Sebastien Ronsse, supporting CHIP-8,
Game Boy, NES, and Sega Master System as separate libretro cores.
NOT an alias of GearSystem. Completely different codebase.
BIOS loading in controllers/mapper/sms_mapper.c:
static char *bios_path = "bios.sms"; (line 33)
#define BIOS_SIZE 0x2000 (line 7, 8192 bytes)
sms_mapper->bios = file_map(PATH_SYSTEM, bios_path, 0, BIOS_SIZE);
If file_map returns NULL, init returns false — core cannot start.
No HLE fallback, no skip logic.
BIOS enable/disable via slot control register port write (lines 74-90):
bios_disable bit in control union. Reset clears bios_disable and
adds BIOS region to memory map. Writing bios_disable=1 removes
the BIOS overlay, exposing cartridge ROM underneath.
Cartridge loading delegated to sega_mapper controller:
ROM mapped via file_map(PATH_DATA, ...), 16 KB banks (BANK_SIZE=0x4000),
3 bank slots (NUM_BANKS=3), bank select registers at 0xFFFD-0xFFFF.
Memory map from mach/sms.c:
0x0000-0xBFFF Mapper (BIOS overlay + cartridge ROM banks)
0xC000-0xDFFF RAM (8 KB)
0xE000-0xFFFF RAM mirror
The .info declares firmware_count=1, firmware0_opt=false.
is_experimental=true. No save states, rewind, or netplay support.
Supported extensions: sms, bms, bin, rom.
system_directory obtained via RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY
(libretro/libretro.c:23-25), passed as "system-dir" config param.
BIOS loaded from this directory via PATH_SYSTEM.
files:
- name: "bios.sms"
system: sms
description: "Sega Master System BIOS"
required: true
size: 8192
md5: "840481177270d5642a14ca71ee72844c"
source_ref: "controllers/mapper/sms_mapper.c:7 (BIOS_SIZE=0x2000), :33 (bios_path), :167-172 (file_map)"
note: "Mapped at 0x0000, overlays cartridge ROM until bios_disable bit set in slot control register. No HLE — core fails to init without this file."

View File

@@ -135,12 +135,14 @@ files:
- name: exdos14isdos10uk.rom
path: ep128emu/roms/exdos14isdos10uk.rom
md5: f91c4a507cc6895bdd9c43df4f021df3
required: false
hle_fallback: true
system: enterprise-128
has_builtin: true
size: 32768
note: "EXDOS 1.4 + IS-DOS 1.0 (UK). Used for IS-DOS disk mode instead of exdos13.rom."
source_ref: "core/core.cpp:295-298"
source_ref: "core/core.cpp:253, roms/roms.hpp:30104 (built-in data, MD5 verified)"
# ============================================================
# Enterprise 64/128 - file I/O and extensions
@@ -183,12 +185,14 @@ files:
- name: epdos16f.rom
path: ep128emu/roms/epdos16f.rom
md5: 6593dff00ab32a4b1fc084674ededf2b
required: false
hle_fallback: true
system: enterprise-128
has_builtin: true
size: 32768
note: "EP-DOS 1.6f. Provides HFONT and CLKOFF for Hungarian locale. 32K across segments 0x06-0x07."
source_ref: "core/core.cpp:252-255"
source_ref: "core/core.cpp:236-237, roms/roms.hpp:43780 (built-in data, MD5 verified)"
- name: brd.rom
path: ep128emu/roms/brd.rom
@@ -200,25 +204,9 @@ files:
note: "German (BRD) language extension. Auto-loaded when content locale is German."
source_ref: "core/core.cpp:260-261"
# ============================================================
# Enterprise 64/128 - optional extras (in .info, not in source)
# ============================================================
- name: epd19hft.rom
path: ep128emu/roms/epd19hft.rom
md5: 12cfc9c7e48c8a16c2e09edbd926d467
required: false
system: enterprise-128
note: "EP-DOS 1.9 with Hungarian font. Listed in .info file as optional firmware."
source_ref: "ep128emu_core_libretro.info:firmware8"
- name: zt19hfnt.rom
path: ep128emu/roms/zt19hfnt.rom
md5: 653daaf7b9b29c2c4e577f489580f247
required: false
system: enterprise-128
note: "ZozoTools 1.9 with Hungarian font. Listed in .info file as optional firmware."
source_ref: "ep128emu_core_libretro.info:firmware9"
# epd19hft.rom and zt19hfnt.rom: listed in .info (firmware8, firmware9) but
# NOT referenced in core source code and NOT in builtin_rom map (roms.hpp:46516-46538).
# The core never loads these files. Excluded from profile to avoid misleading users.
# ============================================================
# Videoton TVC

View File

@@ -1,8 +1,56 @@
emulator: "fbalpha2012"
type: alias
alias_of: "fbneo"
profiled_date: "2026-03-18"
emulator: "FB Alpha 2012"
type: frozen_snapshot
source: "https://github.com/libretro/fbalpha2012"
upstream: "https://www.fbalpha.com"
profiled_date: "2026-03-19"
core_version: "v0.2.97.29"
display_name: "Arcade (FB Alpha 2012)"
note: "This core uses the same BIOS/firmware as fbneo. See emulators/fbneo.yml for details."
cores:
- fbalpha2012
systems:
- arcade
- neogeo
- cps1
- cps2
- cps3
- pgm
notes: |
Frozen snapshot of Final Burn Alpha v0.2.97.29 (circa 2012), full version.
NOT an alias of FBNeo — different codebase, different ROM set compatibility.
Supports CPS-1, CPS-2, CPS-3, Neo Geo, PGM, Sega, Cave, Toaplan, and more.
Exists for RAM-constrained platforms. Most users should use FBNeo.
Multiple arcade systems have BIOS ROMs marked BRF_BIOS in source:
Neo Geo (d_neogeo.cpp:923-1011): neogeo.zip ROM set contains ~35 BIOS variants
- 68K BIOS: asia-s3.rom (default), sp-s2.sp1, sp-s.sp1, sp-u2.sp1, sp-e.sp1,
vs-bios.rom, sp-j2.sp1, sp1.jipan.1024, sp-45.sp1, japan-j3.bin, neo-po.bin,
neo-epo.bin, neodebug.bin, sp-1v1_3db8c.bin, 16 Universe BIOS variants,
neopen.sp1, plus PCB/CD/trackball BIOS variants
- Z80 BIOS: sm1.sm1 (sound program)
- Graphics: sfix.sfix (text layer tiles)
- Data: 000-lo.lo (zoom table)
- Neo Geo CD: neocd.bin
PGM (d_pgm.cpp:303-379): per-game BIOS sets in each ROM ZIP
- pgm_t01s.rom (text tiles), pgm_m01s.rom (samples),
pgm_p01s.u20/pgm_p02s.u20 (68K BIOS v1/v2),
ddp3_bios.u37, bios.u42, svg_bios.u49 (custom per-game)
CPS-3 (d_cps3.cpp): 18 SH-2 BIOS variants, 512 KB each, region-specific
(see fbalpha2012_cps3.yml for full list)
Sega System 16B (d_sys16b.cpp:8900): ism2006v00.u1
All BIOS files are part of their respective game ROM set ZIPs (arcade standard).
None are standalone system directory files. The .info note "BIOS files must be
inside the ROM directory" refers to the ROM directory containing the game ZIPs,
which already include their BIOS — no separate action needed by the user.
ROM path: g_rom_dir from content path. g_system_dir set but unused.
Extensions: iso|zip|7z. need_fullpath=true, block_extract=true.
files: []
exclusion_note: "All BIOS ROMs (Neo Geo, PGM, CPS-3, Sega) are inside game ROM set ZIPs, not standalone system files. neogeo.zip is a parent ROM set loaded automatically by the emulator when a Neo Geo game is started."

View File

@@ -1,8 +1,39 @@
emulator: "fbalpha2012_cps1"
type: alias
alias_of: "fbneo"
profiled_date: "2026-03-18"
emulator: "FB Alpha 2012 CPS-1"
type: frozen_snapshot
source: "https://github.com/libretro/fbalpha2012_cps1"
upstream: "https://www.fbalpha.com"
profiled_date: "2026-03-19"
core_version: "v0.2.97.28"
display_name: "Arcade (FB Alpha 2012 CPS-1)"
note: "This core uses the same BIOS/firmware as fbneo. See emulators/fbneo.yml for details."
cores:
- fbalpha2012_cps1
systems:
- cps1
notes: |
Frozen snapshot of Final Burn Alpha v0.2.97.28 (circa 2012), CPS-1 only.
NOT an alias of FBNeo — different codebase, different ROM set compatibility.
Exists for RAM-constrained platforms (3DS, embedded). Most users should use FBNeo.
CPS-1 has no system BIOS. Each game's ROM set is self-contained:
68000 program ROMs + Z80 sound program + GFX ROMs + (optional) QSound samples.
QSound games (Cadillacs & Dinosaurs, etc.) include audio samples directly in
their ROM set (cd-q1.1k through cd-q4.4k), not as a separate DSP BIOS.
Board PLDs (buf1, ioa1, prg1, rom1, sou1) are listed in ROM definitions as
BRF_OPT (optional). These are GAL/PAL dumps for hardware preservation —
never loaded by the emulator.
ROM path: ROMs are loaded from the content directory (g_rom_dir), not system
directory. system_directory is obtained (libretro.cpp:1645) but never used.
The .info note "BIOS files must be inside the ROM directory" is misleading —
there are no BIOS files for CPS-1. This note is generic across all FBA cores.
244 ROM sets defined (488 STD_ROM_PICK/FN entries = 244 games).
is_experimental=false, need_fullpath=true, block_extract=true.
files: []
# CPS-1 has no BIOS. All required data is in per-game ROM sets (ZIP archives).
exclusion_note: "CPS-1 arcade hardware has no system BIOS. Each game ROM set is self-contained."

View File

@@ -1,8 +1,42 @@
emulator: "fbalpha2012_cps2"
type: alias
alias_of: "fbneo"
profiled_date: "2026-03-18"
emulator: "FB Alpha 2012 CPS-2"
type: frozen_snapshot
source: "https://github.com/libretro/fbalpha2012_cps2"
upstream: "https://www.fbalpha.com"
profiled_date: "2026-03-19"
core_version: "v0.2.97.28"
display_name: "Arcade (FB Alpha 2012 CPS-2)"
note: "This core uses the same BIOS/firmware as fbneo. See emulators/fbneo.yml for details."
cores:
- fbalpha2012_cps2
systems:
- cps2
notes: |
Frozen snapshot of Final Burn Alpha v0.2.97.28 (circa 2012), CPS-2 only.
NOT an alias of FBNeo — different codebase, different ROM set compatibility.
Exists for RAM-constrained platforms (3DS, embedded). Most users should use FBNeo.
CPS-2 has no system BIOS. Each game's ROM set is self-contained:
68000 program ROMs + Z80 sound program + GFX ROMs + QSound samples.
QSound is emulated in software (qs_c.c:QscInit, panning table + DSP emulation).
No external QSound DSP ROM (dl-1425.bin) needed — unlike MAME, FBA emulates
the QSound chip directly without requiring the DSP program ROM.
Z80 sound program loaded from game ROM set into CpsZRom buffer (cps.c:451,
BurnLoadRom). QSound samples loaded into CpsQSam. All from the game ZIP.
Board PLDs (buf1, ioa1, prg2, rom1) referenced via A_BOARD_QSOUND_PLDS
macro, all marked BRF_OPT. GAL/PAL dumps for hardware preservation only.
Zero BRF_BIOS entries in entire codebase.
ROM path: ROMs loaded from content directory (g_rom_dir, libretro.cpp).
g_system_dir is set (line 988) but never used for any file loading.
The .info note "BIOS files must be inside the ROM directory" is misleading —
there are no BIOS files for CPS-2. Generic note across all FBA cores.
need_fullpath=true, block_extract=true, extensions=zip.
files: []
exclusion_note: "CPS-2 arcade hardware has no system BIOS. QSound DSP emulated in software (qs_c.c). Each game ROM set is self-contained."

View File

@@ -1,8 +1,55 @@
emulator: "fbalpha2012_cps3"
type: alias
alias_of: "fbneo"
profiled_date: "2026-03-18"
emulator: "FB Alpha 2012 CPS-3"
type: frozen_snapshot
source: "https://github.com/libretro/fbalpha2012_cps3"
upstream: "https://www.fbalpha.com"
profiled_date: "2026-03-19"
core_version: "v0.2.97.29"
display_name: "Arcade (FB Alpha 2012 CPS-3)"
note: "This core uses the same BIOS/firmware as fbneo. See emulators/fbneo.yml for details."
cores:
- fbalpha2012_cps3
systems:
- cps3
notes: |
Frozen snapshot of Final Burn Alpha v0.2.97.29 (circa 2012), CPS-3 only.
NOT an alias of FBNeo — different codebase, different ROM set compatibility.
Exists for RAM-constrained platforms. Most users should use FBNeo.
CPS-3 uses a Hitachi SH-2 CPU with per-game, per-region BIOS ROMs.
Each BIOS is a 512 KB (0x080000) 29F400 flash chip dump, region-locked.
BIOS ROMs are marked BRF_ESS | BRF_BIOS in the ROM definitions.
BIOS is loaded into RomBios (cps3run.cpp:1048-1064), byte-swapped to
little-endian, then decrypted via cps3_decrypt_bios() (cps3run.cpp:255).
The SH-2 boots from BIOS, which initializes hardware and loads the game
program from simulated flash/CD-ROM.
BIOS files are INSIDE the game ROM set ZIP (arcade standard), not separate
system directory files. Each game variant has its own region BIOS.
Unique BIOS ROMs across all ROM sets (CRC32 from source):
sfiii_usa.29f400.u2 0xfb172a8e SF3: New Generation (USA)
sfiii_japan.29f400.u2 0x74205250 SF3: New Generation (Japan)
sfiii_hispanic.29f400.u2 0xd2b3cd48 SF3: New Generation (Hispanic)
sfiii_asia_nocd.29f400.u2 0x73e32463 SF3: New Generation (Asia NoCD)
sfiii2_usa.29f400.u2 0x75dd72e0 SF3: 2nd Impact (USA)
sfiii2_japan.29f400.u2 0xfaea0a3e SF3: 2nd Impact (Japan)
sfiii2_asia_nocd.29f400.u2 0xfd297c0d SF3: 2nd Impact (Asia NoCD)
sfiii3_euro.29f400.u2 0x30bbf293 SF3: 3rd Strike (Euro)
sfiii3_usa.29f400.u2 0xecc545c1 SF3: 3rd Strike (USA)
sfiii3_japan_nocd.29f400.u2 0x1edc6366 SF3: 3rd Strike (Japan NoCD)
jojo_usa.29f400.u2 0x8d40f7be JoJo's Venture (USA)
jojo_japan.29f400.u2 0x02778f60 JoJo's Venture (Japan)
jojo_asia_nocd.29f400.u2 0x05b4f953 JoJo's Venture (Asia NoCD)
jojoba_japan.29f400.u2 0x3085478c JoJo's Bizarre Adventure (Japan)
jojoba_japan_nocd.29f400.u2 0x4dab19f5 JoJo's Bizarre Adventure (Japan NoCD)
jojoba_euro_nocd.29f400.u2 0x1ee2d679 JoJo's Bizarre Adventure (Euro NoCD)
redearth_euro.29f400.u2 0x02e0f336 Red Earth (Euro)
warzard_japan.29f400.u2 0xf8e2f0c6 Warzard (Japan)
18 unique BIOS ROMs, all 512 KB, all region-specific.
These are arcade ROM set components, not user-supplied BIOS files.
files: []
exclusion_note: "CPS-3 BIOS ROMs (SH-2 flash, 18 variants) are part of each game's ROM set ZIP archive, not standalone system files. Users obtain them as part of the arcade ROM set, not separately."

View File

@@ -1,8 +1,59 @@
emulator: "fbalpha2012_neogeo"
type: alias
alias_of: "fbneo"
profiled_date: "2026-03-18"
emulator: "FB Alpha 2012 Neo Geo"
type: frozen_snapshot
source: "https://github.com/libretro/fbalpha2012_neogeo"
upstream: "https://www.fbalpha.com"
profiled_date: "2026-03-19"
core_version: "v0.2.97.29"
display_name: "Arcade (FB Alpha 2012 Neo Geo)"
note: "This core uses the same BIOS/firmware as fbneo. See emulators/fbneo.yml for details."
cores:
- fbalpha2012_neogeo
systems:
- neogeo
notes: |
Frozen snapshot of Final Burn Alpha v0.2.97.29 (circa 2012), Neo Geo only.
NOT an alias of FBNeo — different codebase, different ROM set compatibility.
Exists for RAM-constrained platforms. Most users should use FBNeo.
Neo Geo requires a BIOS ROM set (neogeo.zip) placed alongside game ROMs.
The BIOS set contains ~35 variants (d_neogeo.cpp:903-968), all BRF_BIOS:
68K main BIOS (BRF_SELECT, one active at a time):
asia-s3.rom 0x91b64be3 MVS Asia/Europe ver. 6 (default)
sp-s2.sp1 0x9036d879 MVS Asia/Europe ver. 5
sp-s.sp1 0xc7f2fa45 MVS Asia/Europe ver. 3 (4 slot)
sp-u2.sp1 0xe72943de MVS USA ver. 5 (2 slot)
sp-e.sp1 0x2723a5b5 MVS USA ver. 5 (6 slot)
vs-bios.rom 0xf0e8f27d MVS Japan ver. 6
sp-j2.sp1 0xacede59c MVS Japan ver. 5
sp1.jipan.1024 0x9fb0abe4 MVS Japan ver. 3 (4 slot)
sp-45.sp1 0x03cc9f6a NEO-MVH MV1C (512 KB)
japan-j3.bin 0xdff6d41f MVS Japan (J3)
neo-po.bin 0x16d0c132 AES Japan
neo-epo.bin 0xd27a71f1 AES Asia
neodebug.bin 0x698ebb7d Development Kit
sp-1v1_3db8c.bin 0x162f0ebe Deck ver. 6 (Git Ver 1.3)
uni-bios_4_0.rom 0xa7aab458 Universe BIOS 4.0
(+ Universe BIOS 1.0-3.3, 15 versions)
neopen.sp1 0xcb915e76 NeoOpen BIOS v0.1 beta
System ROMs (always required):
sm1.sm1 0x94416d67 Z80 BIOS (sound program)
sfix.sfix 0xc2ea0cfd Text layer tiles (fix layer)
000-lo.lo 0x5a86cff2 Zoom table
Special sets:
236-bios.sp1 0x853e6b96 Trackball BIOS
sp-4x.sp1 0xb4590283 PCB BIOS
spj.sp1 0x148dd727 PCB BIOS (Japan)
neocd.bin 0xdf9de490 Neo Geo CD BIOS
neogeo.zip is a parent ROM set, loaded from g_rom_dir alongside game ZIPs.
g_system_dir is set (libretro.cpp:3016) but never used for file loading.
The .info note "BIOS files must be inside the ROM directory" is accurate here:
neogeo.zip must be in the same directory as game ROM ZIPs.
files: []
exclusion_note: "Neo Geo BIOS is a parent ROM set (neogeo.zip) placed in the ROM directory alongside game ZIPs. Not a system directory file — follows arcade ROM set convention."

38
emulators/fbneo_cps12.yml Normal file
View File

@@ -0,0 +1,38 @@
emulator: "FinalBurn Neo (CPS-1/CPS-2)"
type: pure_libretro
source: "https://github.com/libretro/FBNeo"
upstream: "https://neo-source.com"
profiled_date: "2026-03-19"
core_version: "v1.0.0.03"
display_name: "Arcade (FinalBurn Neo) (CPS-1 / CPS-2)"
cores:
- fbneo_cps12
systems:
- cps1
- cps2
notes: |
CPS-1/CPS-2 subset of FinalBurn Neo, current codebase (not frozen).
Exists for platforms that cannot run the full FBNeo core.
CPS-1 and CPS-2 have no system BIOS. Each game ROM set is self-contained:
68000 program + Z80 sound + GFX + QSound samples (CPS-2).
Zero BRF_BIOS entries in d_cps1.cpp and d_cps2.cpp. QSound emulated in
software (qs_c.cpp:QscInit, panning table). No DSP ROM needed.
Board PLDs (buf1, ioa1, prg1/prg2, rom1, sou1) are BRF_OPT — GAL/PAL
dumps for hardware preservation, never loaded by the emulator.
The .info lists firmware_count=1 but it's only hiscore.dat (optional,
high score database in system/fbneo/). Not a BIOS.
ROM path: game ZIPs loaded from content directory.
system/fbneo/ used only for optional extras: hiscore.dat, samples,
cheats, blend files, IPS patches.
need_fullpath=false, extensions=zip|7z, savestate=deterministic.
files: []
exclusion_note: "CPS-1/CPS-2 arcade hardware has no system BIOS. QSound DSP emulated in software. hiscore.dat is optional high score support, not firmware."

View File

@@ -24,6 +24,7 @@ data_directories:
source_type: zip
for_platforms: [retroarch, lakka, retropie]
local_cache: data/dolphin-sys
strip_components: 2
exclude: [Themes]
description: "Dolphin system data (GameSettings, DSP, fonts, shaders)"
@@ -33,6 +34,7 @@ data_directories:
source_type: zip
for_platforms: [retroarch, lakka, retropie]
local_cache: data/ppsspp-assets
strip_components: 1
description: "PPSSPP fonts, backgrounds, shaders, lang files"
# ref: bluemsx-libretro/system/ — system/Databases/ + system/Machines/

View File

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

View File

@@ -76,11 +76,6 @@ shared_groups:
md5: "7da1e5b7c482d4108d22a5b09631d967"
crc32: "d271798b"
size: 524350
# NP2kai also accepts FONT.ROM (uppercase) — ref: libretro.c:1813
- name: FONT.ROM
destination: np2kai/FONT.ROM
required: true
md5: "2af6179d7de4893ea0b705c00e9a98d6"
- name: 2608_bd.wav
destination: np2kai/2608_bd.wav
required: true

View File

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

View File

@@ -5,6 +5,199 @@ source: "https://gitlab.com/recalbox/recalbox/-/raw/master/board/recalbox/fsover
base_destination: bios
hash_type: md5
verification_mode: md5
cores:
- "2048"
- 81
- a5200
- advancemame
- amiberry
- applewin
- arduous
- atari800
- b2
- beebem
- bk
- bluemsx
- boom3
- bsnes
- bsneshd
- cannonball
- cap32
- cdi2015
- corsixth
- craft
- crocods
- daphne
- desmume
- dice
- dinothawr
- dirksimple
- dolphin
- dolphin-gui
- dosbox
- dosbox_pure
- duckstation
- easyrpg
- ecwolf
- emuscv
- fake08
- fba2x
- fbneo
- fceumm
- flycast
- flycast-next
- fmsx
- freechaf
- freeintv
- frotz
- fuse
- gambatte
- gearcoleco
- geargrafx
- gearsystem
- genesisplusgx
- genesisplusgx_ex
- genesisplusgxwide
- geolith
- glide64mk2
- gliden64
- gliden64_20
- gong
- gpsp
- gsplus
- gw
- handy
- hatari
- hatarib
- holani
- imageviewer
- julius
- kronos
- lowresnx
- lutro
- mame0258
- mame0278
- mame2000
- mame2003
- mame2003_plus
- mame2010
- mame2015
- mame2016
- mednafen_lynx
- mednafen_ngp
- mednafen_pce_fast
- mednafen_pcfx
- mednafen_psx
- mednafen_psx_hw
- mednafen_saturn
- mednafen_supafaust
- mednafen_supergrafx
- mednafen_vb
- mednafen_wswan
- melonds
- mesen
- mesen_s
- meteor
- mgba
- minivmac
- mojozork
- moonlight
- mrboom
- mu
- mupen64plus
- mupen64plus_next
- n64_gles2
- neocd
- nestopia
- np2kai
- nxengine
- o2em
- openbor
- openlara
- opera
- oricutron
- parallel_n64
- pcsx2
- pcsx_rearmed
- pico8
- picodrive
- pisnes
- pokemini
- potator
- ppsspp
- prboom
- prosystem
- ps2
- puae
- px68k
- quasi88
- quicknes
- race
- rb5000
- reicast
- reminiscence
- retro8
- retrodream
- rice
- rice_gles2
- sameboy
- same_cdi
- sameduck
- scummvm
- sdlpop
- simcoupe
- snes9x
- snes9x2002
- snes9x2005
- snes9x2010
- solarus
- stella
- stella2014
- stonesoup
- supermodel
- swanstation
- tamalibretro
- tgbdual
- theodore
- thepowdertoy
- ti99sim
- tic80
- tyrquake
- uae4all
- uae4arm
- uzem
- vecx
- vice_x128
- vice_x64
- vice_x64sc
- vice_xcbm2
- vice_xcbm5x0
- vice_xpet
- vice_xplus4
- vice_xscpu64
- vice_xvic
- virtualjaguar
- vitaquake2
- vitaquake3
- vitavoyager
- vpinball
- vvvvvv
- wasm4
- x1
- x128
- x64
- x64sx
- xcbm2
- xcbm5x0
- xemu
- xpet
- xplus4
- xrick
- xroar
- xscpu64
- xvic
- yabasanshiro
- yabause
systems:
commodore-amiga:
files:

View File

@@ -4,6 +4,7 @@ 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
systems:

View File

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

486
platforms/retrodeck.yml Normal file
View File

@@ -0,0 +1,486 @@
platform: RetroDECK
version: ''
homepage: "https://retrodeck.net"
source: "https://github.com/RetroDECK/components"
base_destination: bios
hash_type: md5
verification_mode: md5
systems:
sony-playstation:
files:
- name: psxonpsp660.bin
destination: psxonpsp660.bin
required: true
md5: c53ca5908936d412331790f4426c6c33
- name: scph5500.bin
destination: scph5500.bin
required: true
md5: 8dd7d5296a650fac7319bce665a6a53c
- name: scph5501.bin
destination: scph5501.bin
required: true
md5: 490f666e1afb15b7362b406ed1cea246
- name: scph5502.bin
destination: scph5502.bin
required: true
md5: 32736f17079d0b2b7024407c39bd3050
- name: scph7001.bin
destination: scph7001.bin
required: true
md5: 1e68c231d0896b7eadcad1d7d8e76129
- name: scph7002.bin
destination: scph7002.bin
required: true
md5: b9d9a0286c33dc6b7237bb13cd46fdee
- name: scph7003.bin
destination: scph7003.bin
required: true
md5: 490f666e1afb15b7362b406ed1cea246
- name: scph7502.bin
destination: scph7502.bin
required: true
md5: b9d9a0286c33dc6b7237bb13cd46fdee
- name: "scph9002(7502).bin"
destination: "scph9002(7502).bin"
required: true
md5: b9d9a0286c33dc6b7237bb13cd46fdee
- name: ps1_rom.bin
destination: ps1_rom.bin
required: true
md5: 81bbe60ba7a3d1cea1d48c14cbcc647b
- name: scph1000.bin
destination: scph1000.bin
required: true
md5: 239665b1a3dade1b5a52c06338011044
- name: scph1001.bin
destination: scph1001.bin
required: true
md5: 924e392ed05558ffdb115408c263dccf
- name: scph1002.bin
destination: scph1002.bin
required: true
md5: 54847e693405ffeb0359c6287434cbef
- name: scph100.bin
destination: scph100.bin
required: true
md5: 8abc1b549a4a80954addc48ef02c4521
- name: scph101.bin
destination: scph101.bin
required: true
md5: 6e3735ff4c7dc899ee98981385f6f3d0
- name: scph102A.bin
destination: scph102A.bin
required: true
md5: b10f5e0e3d9eb60e5159690680b1e774
- name: scph102B.bin
destination: scph102B.bin
required: true
md5: de93caec13d1a141a40a79f5c86168d6
- name: scph102C.bin
destination: scph102C.bin
required: true
md5: de93caec13d1a141a40a79f5c86168d6
- name: scph3000.bin
destination: scph3000.bin
required: true
md5: 849515939161e62f6b866f6853006780
- name: scph3500.bin
destination: scph3500.bin
required: true
md5: cba733ceeff5aef5c32254f1d617fa62
- name: scph5000.bin
destination: scph5000.bin
required: true
md5: eb201d2d98251a598af467d4347bb62f
nintendo-ds:
files:
- name: bios7.bin
destination: bios7.bin
required: true
md5: df692a80a5b1bc90728bc3dfc76cd948
- name: bios9.bin
destination: bios9.bin
required: true
md5: a392174eb3e572fed6447e956bde4b25
- name: firmware.bin
destination: firmware.bin
required: true
md5: e45033d9b0fa6b0de071292bba7c9d13
- name: biosdsi7.bin
destination: biosdsi7.bin
required: true
sha256: 2946281e730e71f7cafdb125f5cb60fed944ca5d610ee1e082c441b602b5f4e2
- name: biosdsi9.bin
destination: biosdsi9.bin
required: true
sha256: 47538922a8e8a8e79b922ff1203863ef5c40d9c54656a8d2c89c56ece52029ce
- name: dsifirmware.bin
destination: dsifirmware.bin
required: true
sha256: 11a150e3729bdde3ae8f5e7fc8be67d8bfbc548a1d2e523da58aa826ca0ffa99
sony-playstation-2:
files:
- name: ps2-0100j-20000117.bin
destination: ps2-0100j-20000117.bin
required: true
md5: acf4730ceb38ac9d8c7d8e21f2614600
- name: ps2-0101j-20000217.bin
destination: ps2-0101j-20000217.bin
required: true
md5: b1459d7446c69e3e97e6ace3ae23dd1c
- name: ps2-0120j-20001027-185015.bin
destination: ps2-0120j-20001027-185015.bin
required: true
md5: f63bc530bd7ad7c026fcd6f7bd0d9525
- name: ps2-0120j-20001027-191435.bin
destination: ps2-0120j-20001027-191435.bin
required: true
md5: cee06bd68c333fc5768244eae77e4495
- name: ps2-0150j-20010118.bin
destination: ps2-0150j-20010118.bin
required: true
md5: 815ac991d8bc3b364696bead3457de7d
- name: ps2-0160j-20010427.bin
destination: ps2-0160j-20010427.bin
required: true
md5: ab55cceea548303c22c72570cfd4dd71
- name: ps2-0170j-20030206.bin
destination: ps2-0170j-20030206.bin
required: true
md5: 312ad4816c232a9606e56f946bc0678a
- name: ps2-0200j-20040614.bin
destination: ps2-0200j-20040614.bin
required: true
md5: 0eee5d1c779aa50e94edd168b4ebf42e
- name: ps2-0210j-20040917.bin
destination: ps2-0210j-20040917.bin
required: true
md5: 1ad977bb539fc9448a08ab276a836bbc
- name: ps2-0110a-20000727.bin
destination: ps2-0110a-20000727.bin
required: true
md5: a20c97c02210f16678ca3010127caf36
- name: ps2-0120a-20000902.bin
destination: ps2-0120a-20000902.bin
required: true
md5: 8db2fbbac7413bf3e7154c1e0715e565
- name: ps2-0150a-20001228.bin
destination: ps2-0150a-20001228.bin
required: true
md5: 8accc3c49ac45f5ae2c5db0adc854633
- name: ps2-0160a-20010427.bin
destination: ps2-0160a-20010427.bin
required: true
md5: b107b5710042abe887c0f6175f6e94bb
- name: ps2-0160a-20011004.bin
destination: ps2-0160a-20011004.bin
required: true
md5: 7200a03d51cacc4c14fcdfdbc4898431
- name: ps2-0160a-20020207.bin
destination: ps2-0160a-20020207.bin
required: true
md5: d5ce2c7d119f563ce04bc04dbc3a323e
- name: ps2-0170a-20030325.bin
destination: ps2-0170a-20030325.bin
required: true
md5: 8aa12ce243210128c5074552d3b86251
- name: ps2-0190a-20030623.bin
destination: ps2-0190a-20030623.bin
required: true
md5: 35461cecaa51712b300b2d6798825048
- name: ps2-0200a-20040614.bin
destination: ps2-0200a-20040614.bin
required: true
md5: d333558cc14561c1fdc334c75d5f37b7
- name: ps2-0220a-20050620.bin
destination: ps2-0220a-20050620.bin
required: true
md5: 929a14baca1776b00869f983aa6e14d2
- name: ps2-0220a-20060210.bin
destination: ps2-0220a-20060210.bin
required: true
md5: cb801b7920a7d536ba07b6534d2433ca
- name: ps2-0220a-20060905.bin
destination: ps2-0220a-20060905.bin
required: true
md5: 40c11c063b3b9409aa5e4058e984e30c
- name: ps2-0230a-20080220.bin
destination: ps2-0230a-20080220.bin
required: true
md5: 21038400dc633070a78ad53090c53017
- name: ps2-0120e-20000902.bin
destination: ps2-0120e-20000902.bin
required: true
md5: b7fa11e87d51752a98b38e3e691cbf17
- name: ps2-0150e-20001228.bin
destination: ps2-0150e-20001228.bin
required: true
md5: 838544f12de9b0abc90811279ee223c8
- name: ps2-0160e-20010704.bin
destination: ps2-0160e-20010704.bin
required: true
md5: 491209dd815ceee9de02dbbc408c06d6
- name: ps2-0160e-20011004.bin
destination: ps2-0160e-20011004.bin
required: true
md5: 8359638e857c8bc18c3c18ac17d9cc3c
- name: ps2-0160e-20020319.bin
destination: ps2-0160e-20020319.bin
required: true
md5: 0d2228e6fd4fb639c9c39d077a9ec10c
- name: ps2-0170e-20030227.bin
destination: ps2-0170e-20030227.bin
required: true
md5: 6e69920fa6eef8522a1d688a11e41bc6
- name: ps2-0190e-20030623.bin
destination: ps2-0190e-20030623.bin
required: true
md5: bd6415094e1ce9e05daabe85de807666
- name: ps2-0200e-20040614.bin
destination: ps2-0200e-20040614.bin
required: true
md5: dc752f160044f2ed5fc1f4964db2a095
- name: ps2-0220e-20050620.bin
destination: ps2-0220e-20050620.bin
required: true
md5: 573f7d4a430c32b3cc0fd0c41e104bbd
- name: ps2-0220e-20060210.bin
destination: ps2-0220e-20060210.bin
required: true
md5: af60e6d1a939019d55e5b330d24b1c25
- name: ps2-0220e-20060905.bin
destination: ps2-0220e-20060905.bin
required: true
md5: 80bbb237a6af9c611df43b16b930b683
- name: ps2-0230e-20080220.bin
destination: ps2-0230e-20080220.bin
required: true
md5: dc69f0643a3030aaa4797501b483d6c4
- name: ps2-0160h-20010730.bin
destination: ps2-0160h-20010730.bin
required: true
md5: 352d2ff9b3f68be7e6fa7e6dd8389346
- name: ps2-0160h-20020426.bin
destination: ps2-0160h-20020426.bin
required: true
md5: 315a4003535dfda689752cb25f24785c
- name: ps2-0190h-20030623.bin
destination: ps2-0190h-20030623.bin
required: true
md5: 07b562a3f0c4b9a55834df9bbc9bd0c3
- name: ps2-0200h-20040614.bin
destination: ps2-0200h-20040614.bin
required: true
md5: 3e3e030c0f600442fa05b94f87a1e238
- name: ps2-0220h-20060905.bin
destination: ps2-0220h-20060905.bin
required: true
md5: c37bce95d32b2be480f87dd32704e664
- name: ps2-0220h-20060210.bin
destination: ps2-0220h-20060210.bin
required: true
md5: 549a66d0c698635ca9fa3ab012da7129
- name: ps2-0190c-20030623.bin
destination: ps2-0190c-20030623.bin
required: true
md5: 1b6e631b536247756287b916f9396872
- name: ps2-0190r-20030623.bin
destination: ps2-0190r-20030623.bin
required: true
md5: 0c13357c01a25a886db2356bbe73d9f0
pico8:
files:
- name: pico8
destination: pico-8/pico8
required: true
- name: pico8.dat
destination: pico-8/pico8.dat
required: true
- name: pico8_dyn
destination: pico-8/pico8_dyn
required: true
sony-psp:
files:
- name: ppge_atlas.zim
destination: ppge_atlas.zim
required: false
md5: 866855cc330b9b95cc69135fb7b41d38
xbox:
files:
- name: mcpx_1.0.bin
destination: mcpx_1.0.bin
required: true
md5: d49c52a4102f6df7bcf8d0617ac475ed
- name: Complex.bin
destination: Complex.bin
required: true
- name: Complex_4627v1.03.bin
destination: Complex_4627v1.03.bin
required: true
- name: Complex_4627.bin
destination: Complex_4627.bin
required: true
dragon32:
files:
- name: d32.rom
destination: d32.rom
required: true
md5: d35177f73cf303c5565aa13ef8ca5251,3420b96031078a4ef408cad7bf21a33f
- name: d64rom1.rom
destination: d64rom1.rom
required: true
md5: 5f0bee59710e55f5880e74890912ed78,6ab639e65c6e8fd832cb0d8ad4da1b60
- name: d64tano.rom
destination: d64tano.rom
required: true
md5: be9bc86ee5eb401d0a40d0377f65fefa
- name: d64tano2.rom
destination: d64tano2.rom
required: true
md5: fd91edce7be5e7c2d88e46b76956a8aa
- name: d200rom1.rom
destination: d200rom1.rom
required: true
md5: be9bc86ee5eb401d0a40d0377f65fefa
- name: d200rom2.rom
destination: d200rom2.rom
required: true
md5: fd91edce7be5e7c2d88e46b76956a8aa
- name: ddos10.rom
destination: ddos10.rom
required: true
md5: 1c965da49b6c5459b8353630aa1482e7
- name: ddos11c.rom
destination: ddos11c.rom
required: true
md5: d8429af1a12f7438a4bf88a5b934cb3a
- name: ddos12a.rom
destination: ddos12a.rom
required: true
md5: 55e2535dbbed7f1a26b5f263d7c72c63
- name: ddos40.rom
destination: ddos40.rom
required: true
md5: 9ddc388632cd3c376b164ba5cfc64329
- name: ddos42.rom
destination: ddos42.rom
required: true
md5: c956a854cbc4b9d1e69c000f78368668
- name: deltados.rom
destination: deltados.rom
required: true
md5: 024eac3db20f1b5cf98c30a0e4743201
- name: dplus48.rom
destination: dplus48.rom
required: true
md5: ee6f24d893a52b8efea9f787855456b5
- name: dplus49b.rom
destination: dplus49b.rom
required: true
md5: 56f1b97314e4ca82491c465bb887059e
- name: dplus50.rom
destination: dplus50.rom
required: true
md5: 35de5d28da507ebb213a26e04241d940
- name: sdose6.rom
destination: sdose6.rom
required: true
md5: 9d85e6b7133f915c021156f4b9cdb512
- name: sdose8.rom
destination: sdose8.rom
required: true
md5: 167f409b7a4b992faabb784b061ab4c6
- name: cp400bas.rom
destination: cp400bas.rom
required: true
md5: f73da4d73d6db5cdb8b3cb6a50415e38
trs80coco:
files:
- name: cp400extbas.rom
destination: cp400extbas.rom
required: true
md5: 091581001577b4a83ccfd511829de0f1
- name: cp400dsk.rom
destination: cp400dsk.rom
required: true
md5: 16d3ab9bc935f0d5651ca3f0e3030846
- name: color64extbas.rom
destination: color64extbas.rom
required: true
md5: 0d9264ffa95ba493f2b5b0d488a49e13
- name: xroarbios.rom
destination: xroarbios.rom
required: true
md5: 28dc97df470fb8660ef61b81dfd34f4a
- name: bas10.rom
destination: bas10.rom
required: true
md5: a74f3d95b395dad7cdca19d560eeea74
- name: bas11.rom
destination: bas11.rom
required: true
md5: c73fb4bff9621c5ab17f6220b20db82f
- name: bas12.rom
destination: bas12.rom
required: true
md5: c933316c7d939532a13648850c1c2aa6
- name: bas13.rom
destination: bas13.rom
required: true
md5: c2fc43556eb6b7b25bdf5955bd9df825
- name: bas14.rom
destination: bas14.rom
required: true
md5: ac33e16f677b4db52548d426174b1aaa
- name: coco3.rom
destination: coco3.rom
required: true
md5: 7233c6c429f3ce1c7392f28a933e0b6f
- name: extbas10.rom
destination: extbas10.rom
required: true
md5: fda72f415afe99b36f953bb9bc1253da
- name: extbas11.rom
destination: extbas11.rom
required: true
md5: 21070aa0496142b886c562bf76d7c113
- name: disk10.rom
destination: disk10.rom
required: true
md5: a64b3ef9efcc066b18d35b134068d1cc
- name: disk11.rom
destination: disk11.rom
required: true
md5: 8cab28f4b7311b8df63c07bb3b59bfd5
- name: fd502.rom
destination: fd502.rom
required: true
md5: 8cab28f4b7311b8df63c07bb3b59bfd5
- name: fd502ds.rom
destination: fd502ds.rom
required: true
md5: b2d43757dc6851d866021ff6c4f59205
- name: coco3p.rom
destination: coco3p.rom
required: true
md5: 4ae57e5a8e7494e5485446fefedb580b
- name: CCNPATCH.cas
destination: CCNPATCH.cas
required: true
md5: 2c395ad58a842711931679b554483a90
- name: CCNPATCH.wav
destination: CCNPATCH.wav
required: true
md5: a17397fb5408647a11d23bab959d1f97
- name: DBPATCH.cas
destination: DBPATCH.cas
required: true
md5: 65e1aab5fc5cba1b7374d9dddec25d62
- name: DBPATCH.wav
destination: DBPATCH.wav
required: true
md5: 6a07aeee664d81047672e6ff3541a12a
- name: disk.rom
destination: disk.rom
required: true
md5: 5bfcb1ae090159c8dea542b5a7c0840f

View File

@@ -81,8 +81,10 @@ def find_missing(config: dict, db: dict) -> list[dict]:
found = False
if sha1 and sha1 in db.get("files", {}):
found = True
elif md5 and md5 in db.get("indexes", {}).get("by_md5", {}):
found = True
elif md5:
by_md5 = db.get("indexes", {}).get("by_md5", {})
md5_list = [m.strip() for m in md5.split(",") if m.strip()]
found = any(m in by_md5 for m in md5_list)
if not found:
missing.append({

View File

@@ -58,19 +58,28 @@ def md5sum(source: str | Path | object) -> str:
return h.hexdigest()
_md5_composite_cache: dict[str, str] = {}
def md5_composite(filepath: str | Path) -> str:
"""Compute composite MD5 of a ZIP - matches Recalbox's Zip::Md5Composite().
Sorts filenames alphabetically, reads each file's contents in order,
feeds everything into a single MD5 hasher. The result is independent
of ZIP compression level or metadata.
of ZIP compression level or metadata. Results are cached per path.
"""
key = str(filepath)
cached = _md5_composite_cache.get(key)
if cached is not None:
return cached
with zipfile.ZipFile(filepath) as zf:
names = sorted(n for n in zf.namelist() if not n.endswith("/"))
h = hashlib.md5()
for name in names:
h.update(zf.read(name))
return h.hexdigest()
result = h.hexdigest()
_md5_composite_cache[key] = result
return result
def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -> dict:
@@ -116,9 +125,14 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
(f.get("name"), f.get("destination", f.get("name")))
for f in system.get("files", [])
}
existing_lower = {
f.get("destination", f.get("name", "")).lower()
for f in system.get("files", [])
}
for gf in shared_groups[group_name]:
key = (gf.get("name"), gf.get("destination", gf.get("name")))
if key not in existing:
dest_lower = gf.get("destination", gf.get("name", "")).lower()
if key not in existing and dest_lower not in existing_lower:
system.setdefault("files", []).append(gf)
existing.add(key)
@@ -339,6 +353,44 @@ def group_identical_platforms(
return [(group, representatives[fp]) for fp, group in fingerprints.items()]
def resolve_platform_cores(
config: dict, profiles: dict[str, dict],
) -> set[str]:
"""Resolve which emulator profiles are relevant for a platform.
Resolution strategies (by priority):
1. cores: "all_libretro" — all profiles with libretro in type
2. cores: [list] — profiles whose dict key matches a core name
3. cores: absent — fallback to systems intersection
Alias profiles are always excluded (they point to another profile).
"""
cores_config = config.get("cores")
if cores_config == "all_libretro":
return {
name for name, p in profiles.items()
if "libretro" in p.get("type", "")
and p.get("type") != "alias"
}
if isinstance(cores_config, list):
core_set = set(cores_config)
return {
name for name in profiles
if name in core_set
and profiles[name].get("type") != "alias"
}
# Fallback: system ID intersection
platform_systems = set(config.get("systems", {}).keys())
return {
name for name, p in profiles.items()
if set(p.get("systems", [])) & platform_systems
and p.get("type") != "alias"
}
def safe_extract_zip(zip_path: str, dest_dir: str) -> None:
"""Extract a ZIP file safely, preventing zip-slip path traversal."""
dest = os.path.realpath(dest_dir)

View File

@@ -93,15 +93,6 @@ def cross_reference(
for sys_id in systems:
platform_names.update(declared.get(sys_id, set()))
# data_directories: check if the emulator's data_dir refs are provided
# by ANY platform for ANY system (not limited to matching system IDs,
# since emulator profiles and platforms use different ID conventions)
all_plat_dd_refs = set()
for dd_set in platform_data_dirs.values():
all_plat_dd_refs.update(dd_set)
emu_dd_refs = {dd.get("ref", "") for dd in profile.get("data_directories", [])}
covered_dd = emu_dd_refs & all_plat_dd_refs
gaps = []
covered = []
for f in emu_files:
@@ -117,9 +108,6 @@ def cross_reference(
continue
in_platform = fname in platform_names
# files covered by shared data_directories are effectively in the platform pack
if not in_platform and covered_dd:
in_platform = True
in_repo = _find_in_repo(fname, by_name, by_name_lower)
entry = {

View File

@@ -233,7 +233,8 @@ Examples:
sys.exit(1)
import tempfile
zip_path = os.path.join(tempfile.gettempdir(), asset["name"])
fd, zip_path = tempfile.mkstemp(suffix=".zip")
os.close(fd)
print(f"Downloading {asset['name']} ({asset['size']:,} bytes)...")
download_file(asset["browser_download_url"], zip_path, asset["size"])

View File

@@ -53,8 +53,9 @@ def _verify_file_hash(path: str, expected_sha1: str = "",
return True
hashes = compute_hashes(path)
if expected_sha1:
return hashes["sha1"] == expected_sha1
return hashes["md5"] == expected_md5
return hashes["sha1"].lower() == expected_sha1.lower()
md5_list = [m.strip().lower() for m in expected_md5.split(",") if m.strip()]
return hashes["md5"].lower() in md5_list
def fetch_large_file(name: str, dest_dir: str = ".cache/large",
@@ -94,7 +95,7 @@ def fetch_large_file(name: str, dest_dir: str = ".cache/large",
def _sanitize_path(raw: str) -> str:
"""Strip path traversal components from a relative path."""
raw = raw.replace("\\", "/")
parts = [p for p in raw.split("/") if p and p != ".."]
parts = [p for p in raw.split("/") if p and p not in ("..", ".")]
return "/".join(parts)
@@ -181,7 +182,7 @@ def _collect_emulator_extras(
base_dest: str,
emu_profiles: dict | None = None,
) -> list[dict]:
"""Collect extra files from emulator profiles not in the platform pack.
"""Collect core requirement files from emulator profiles not in the platform pack.
Uses the same system-overlap matching as verify.py cross-reference:
- Matches emulators by shared system IDs with the platform
@@ -236,15 +237,15 @@ def generate_pack(
platform_display = config.get("platform", platform_name)
base_dest = config.get("base_destination", "")
suffix = "Complete_Pack" if include_extras else "BIOS_Pack"
zip_name = f"{platform_display.replace(' ', '_')}_{suffix}.zip"
zip_name = f"{platform_display.replace(' ', '_')}_BIOS_Pack.zip"
zip_path = os.path.join(output_dir, zip_name)
os.makedirs(output_dir, exist_ok=True)
total_files = 0
missing_files = []
user_provided = []
seen_destinations = set()
seen_destinations: set[str] = set()
seen_lower: set[str] = set() # case-insensitive dedup for Windows/macOS
# Per-file status: worst status wins (missing > untested > ok)
file_status: dict[str, str] = {}
file_reasons: dict[str, str] = {}
@@ -276,6 +277,7 @@ def generate_pack(
if already_packed:
continue
seen_destinations.add(dedup_key)
seen_lower.add(dedup_key.lower())
file_status.setdefault(dedup_key, "ok")
instructions = file_entry.get("instructions", "Please provide this file manually.")
instr_name = f"INSTRUCTIONS_{file_entry['name']}.txt"
@@ -299,9 +301,13 @@ def generate_pack(
_extract_zip_to_archive(tmp_path, full_dest, zf)
else:
zf.write(tmp_path, full_dest)
seen_destinations.add(dedup_key)
seen_lower.add(dedup_key.lower())
file_status.setdefault(dedup_key, "ok")
total_files += 1
else:
missing_files.append(file_entry["name"])
file_status[dedup_key] = "missing"
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
@@ -316,16 +322,26 @@ def generate_pack(
if status == "hash_mismatch" and verification_mode != "existence":
zf_name = file_entry.get("zipped_file")
if zf_name and local_path:
inner_md5 = file_entry.get("md5", "")
inner_result = check_inside_zip(local_path, zf_name, inner_md5)
if inner_result == "ok":
inner_md5_raw = file_entry.get("md5", "")
inner_md5_list = (
[m.strip() for m in inner_md5_raw.split(",") if m.strip()]
if inner_md5_raw else [""]
)
zip_ok = False
last_result = "not_in_zip"
for md5_candidate in inner_md5_list:
last_result = check_inside_zip(local_path, zf_name, md5_candidate)
if last_result == "ok":
zip_ok = True
break
if zip_ok:
file_status.setdefault(dedup_key, "ok")
elif inner_result == "not_in_zip":
elif last_result == "not_in_zip":
file_status[dedup_key] = "untested"
file_reasons[dedup_key] = f"{zf_name} not found inside ZIP"
elif inner_result == "error":
elif last_result == "error":
file_status[dedup_key] = "untested"
file_reasons[dedup_key] = f"cannot read ZIP"
file_reasons[dedup_key] = "cannot read ZIP"
else:
file_status[dedup_key] = "untested"
file_reasons[dedup_key] = f"{zf_name} MD5 mismatch inside ZIP"
@@ -338,6 +354,7 @@ def generate_pack(
if already_packed:
continue
seen_destinations.add(dedup_key)
seen_lower.add(dedup_key.lower())
extract = file_entry.get("extract", False)
if extract and local_path.endswith(".zip"):
@@ -346,30 +363,33 @@ def generate_pack(
zf.write(local_path, full_dest)
total_files += 1
# Tier 2: emulator extras (files cores need but platform doesn't declare)
extra_count = 0
if include_extras:
emu_profiles = load_emulator_profiles(emulators_dir)
extras = _collect_emulator_extras(
config, emulators_dir, db,
seen_destinations, base_dest, emu_profiles,
)
for fe in extras:
dest = _sanitize_path(fe.get("destination", fe["name"]))
if not dest:
continue
full_dest = f"{base_dest}/{dest}" if base_dest else dest
if full_dest in seen_destinations:
continue
# Core requirements: files platform's cores need but YAML doesn't declare
emu_profiles = load_emulator_profiles(emulators_dir)
core_files = _collect_emulator_extras(
config, emulators_dir, db,
seen_destinations, base_dest, emu_profiles,
)
core_count = 0
for fe in core_files:
dest = _sanitize_path(fe.get("destination", fe["name"]))
if not dest:
continue
full_dest = f"{base_dest}/{dest}" if base_dest else dest
if full_dest in seen_destinations:
continue
# Skip case-insensitive duplicates (Windows/macOS FS safety)
if full_dest.lower() in seen_lower:
continue
local_path, status = resolve_file(fe, db, bios_dir, zip_contents)
if status in ("not_found", "external", "user_provided"):
continue
local_path, status = resolve_file(fe, db, bios_dir, zip_contents)
if status in ("not_found", "external", "user_provided"):
continue
zf.write(local_path, full_dest)
seen_destinations.add(full_dest)
extra_count += 1
total_files += 1
zf.write(local_path, full_dest)
seen_destinations.add(full_dest)
seen_lower.add(full_dest.lower())
core_count += 1
total_files += 1
# Data directories from _data_dirs.yml
for sys_id, system in sorted(config.get("systems", {}).items()):
@@ -392,9 +412,10 @@ def generate_pack(
src = os.path.join(root, fname)
rel = os.path.relpath(src, local_path)
full = f"{dd_prefix}/{rel}"
if full in seen_destinations:
if full in seen_destinations or full.lower() in seen_lower:
continue
seen_destinations.add(full)
seen_lower.add(full.lower())
zf.write(src, full)
total_files += 1
@@ -408,8 +429,8 @@ def generate_pack(
parts.append(f"{files_untested} untested")
if files_miss:
parts.append(f"{files_miss} missing")
extras_msg = f", {extra_count} extras" if extra_count else ""
print(f" {zip_path}: {total_files} files packed{extras_msg}, {', '.join(parts)} [{verification_mode}]")
baseline = total_files - core_count
print(f" {zip_path}: {total_files} files packed ({baseline} baseline + {core_count} from cores), {', '.join(parts)} [{verification_mode}]")
for key, reason in sorted(file_reasons.items()):
status = file_status.get(key, "")
@@ -453,8 +474,9 @@ def main():
parser.add_argument("--db", default=DEFAULT_DB_FILE, help="Path to database.json")
parser.add_argument("--bios-dir", default=DEFAULT_BIOS_DIR)
parser.add_argument("--output-dir", "-o", default=DEFAULT_OUTPUT_DIR)
# --include-extras is now a no-op: core requirements are always included
parser.add_argument("--include-extras", action="store_true",
help="Include emulator-recommended files not declared by platform")
help="(no-op) Core requirements are always included")
parser.add_argument("--emulators-dir", default="emulators")
parser.add_argument("--offline", action="store_true",
help="Skip data directory freshness check, use cache only")

View File

@@ -24,14 +24,19 @@ from verify import verify_platform
def compute_coverage(platform_name: str, platforms_dir: str, db: dict) -> dict:
config = load_platform_config(platform_name, platforms_dir)
result = verify_platform(config, db)
present = result["ok"] + result["untested"]
pct = (present / result["total"] * 100) if result["total"] > 0 else 0
sc = result.get("status_counts", {})
ok = sc.get("ok", 0)
untested = sc.get("untested", 0)
missing = sc.get("missing", 0)
total = result["total_files"]
present = ok + untested
pct = (present / total * 100) if total > 0 else 0
return {
"platform": config.get("platform", platform_name),
"total": result["total"],
"verified": result["ok"],
"untested": result["untested"],
"missing": result["missing"],
"total": total,
"verified": ok,
"untested": untested,
"missing": missing,
"present": present,
"percentage": pct,
"mode": config.get("verification_mode", "existence"),

View File

@@ -68,14 +68,19 @@ def _status_icon(pct: float) -> str:
def compute_coverage(platform_name: str, platforms_dir: str, db: dict) -> dict:
config = load_platform_config(platform_name, platforms_dir)
result = verify_platform(config, db)
present = result["ok"] + result["untested"]
pct = (present / result["total"] * 100) if result["total"] > 0 else 0
sc = result.get("status_counts", {})
ok = sc.get("ok", 0)
untested = sc.get("untested", 0)
missing = sc.get("missing", 0)
total = result["total_files"]
present = ok + untested
pct = (present / total * 100) if total > 0 else 0
return {
"platform": config.get("platform", platform_name),
"total": result["total"],
"verified": result["ok"],
"untested": result["untested"],
"missing": result["missing"],
"total": total,
"verified": ok,
"untested": untested,
"missing": missing,
"present": present,
"percentage": pct,
"mode": config.get("verification_mode", "existence"),
@@ -792,7 +797,7 @@ def main():
# Replace nav section (everything from \nnav: to the next top-level key or EOF)
import re
if "\nnav:" in content:
content = re.sub(r'\nnav:.*', '\n' + nav_yaml.rstrip(), content, count=1, flags=re.DOTALL)
content = re.sub(r'\nnav:\n(?:[ \t]+.*\n?)*', '\n' + nav_yaml, content, count=1)
else:
content += "\n" + nav_yaml
with open("mkdocs.yml", "w") as f:

View File

@@ -21,13 +21,15 @@ import json
import subprocess
import sys
import time
from pathlib import Path
def run(cmd: list[str], label: str) -> tuple[bool, str]:
"""Run a command. Returns (success, captured_output)."""
print(f"\n--- {label} ---", flush=True)
start = time.monotonic()
result = subprocess.run(cmd, capture_output=True, text=True, cwd=".")
repo_root = str(Path(__file__).resolve().parent.parent)
result = subprocess.run(cmd, capture_output=True, text=True, cwd=repo_root)
elapsed = time.monotonic() - start
output = result.stdout
@@ -71,9 +73,18 @@ def parse_pack_counts(output: str) -> dict[str, tuple[int, int]]:
if m:
current_label = m.group(1)
continue
frac_m = re.search(r"(\d+)/(\d+) files OK", line)
if frac_m and "files packed" in line:
ok, total = int(frac_m.group(1)), int(frac_m.group(2))
if "files packed" not in line:
continue
# New format: "622 files packed (359 baseline + 263 from cores), 358/359 files OK"
base_m = re.search(r"\((\d+) baseline", line)
ok_m = re.search(r"(\d+)/(\d+) files OK", line)
if base_m and ok_m:
baseline = int(base_m.group(1))
ok, total = int(ok_m.group(1)), int(ok_m.group(2))
counts[current_label] = (ok, total)
elif ok_m:
# Fallback: old format without baseline
ok, total = int(ok_m.group(1)), int(ok_m.group(2))
counts[current_label] = (ok, total)
return counts
@@ -121,8 +132,9 @@ def main():
help="Skip data directory refresh")
parser.add_argument("--output-dir", default="dist",
help="Pack output directory (default: dist/)")
# --include-extras is now a no-op: core requirements are always included
parser.add_argument("--include-extras", action="store_true",
help="Include Tier 2 emulator extras in packs")
help="(no-op) Core requirements are always included")
args = parser.parse_args()
results = {}

View File

@@ -120,7 +120,8 @@ def _is_safe_tar_member(member: tarfile.TarInfo, dest: Path) -> bool:
if member.name.startswith("/") or ".." in member.name.split("/"):
return False
resolved = (dest / member.name).resolve()
if not str(resolved).startswith(str(dest.resolve())):
dest_str = str(dest.resolve()) + os.sep
if not str(resolved).startswith(dest_str) and str(resolved) != str(dest.resolve()):
return False
return True

View File

@@ -14,6 +14,8 @@ import sys
import urllib.request
import urllib.error
import yaml
from .base_scraper import BaseScraper, BiosRequirement, fetch_github_latest_tag
PLATFORM_NAME = "batocera"
@@ -23,6 +25,12 @@ SOURCE_URL = (
"master/package/batocera/core/batocera-scripts/scripts/batocera-systems"
)
CONFIGGEN_DEFAULTS_URL = (
"https://raw.githubusercontent.com/batocera-linux/batocera.linux/"
"master/package/batocera/core/batocera-configgen/configs/"
"configgen-defaults.yml"
)
SYSTEM_SLUG_MAP = {
"atari800": "atari-400-800",
"atari5200": "atari-5200",
@@ -91,6 +99,28 @@ class Scraper(BaseScraper):
def __init__(self, url: str = SOURCE_URL):
super().__init__(url=url)
def _fetch_cores(self) -> list[str]:
"""Extract core names from Batocera configgen-defaults.yml."""
try:
req = urllib.request.Request(
CONFIGGEN_DEFAULTS_URL,
headers={"User-Agent": "retrobios-scraper/1.0"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read().decode("utf-8")
except urllib.error.URLError as e:
raise ConnectionError(
f"Failed to fetch {CONFIGGEN_DEFAULTS_URL}: {e}"
) from e
data = yaml.safe_load(raw)
cores: set[str] = set()
for system, cfg in data.items():
if system == "default" or not isinstance(cfg, dict):
continue
core = cfg.get("core")
if core:
cores.add(core)
return sorted(cores)
def _extract_systems_dict(self, raw: str) -> dict:
"""Extract and parse the 'systems' dict from the Python source via ast.literal_eval."""
@@ -101,14 +131,26 @@ class Scraper(BaseScraper):
start = match.start() + raw[match.start():].index("{")
depth = 0
i = start
in_str = False
str_ch = None
while i < len(raw):
if raw[i] == "{":
ch = raw[i]
if in_str:
if ch == '\\':
i += 2
continue
if ch == str_ch:
in_str = False
elif ch in ('"', "'"):
in_str = True
str_ch = ch
elif ch == "{":
depth += 1
elif raw[i] == "}":
elif ch == "}":
depth -= 1
if depth == 0:
break
elif raw[i] == "#":
elif ch == "#":
while i < len(raw) and raw[i] != "\n":
i += 1
i += 1
@@ -120,10 +162,15 @@ class Scraper(BaseScraper):
in_string = False
string_char = None
clean = []
for j, ch in enumerate(line):
if ch in ('"', "'") and j > 0 and line[j - 1] == '\\':
j = 0
while j < len(line):
ch = line[j]
if ch == '\\' and j + 1 < len(line):
clean.append(ch)
elif ch in ('"', "'") and not in_string:
clean.append(line[j + 1])
j += 2
continue
if ch in ('"', "'") and not in_string:
in_string = True
string_char = ch
clean.append(ch)
@@ -134,6 +181,7 @@ class Scraper(BaseScraper):
break
else:
clean.append(ch)
j += 1
lines.append("".join(clean))
clean_dict_str = "\n".join(lines)
@@ -226,6 +274,7 @@ class Scraper(BaseScraper):
"base_destination": "bios",
"hash_type": "md5",
"verification_mode": "md5",
"cores": self._fetch_cores(),
"systems": systems,
}

View File

@@ -227,8 +227,10 @@ class Scraper(BaseScraper):
continue
system = FUNCTION_HASH_MAP[func_name]
func_start = func_match.start()
remaining = script[func_start:]
local_match = _RE_LOCAL_HASHES.search(remaining)
next_func = _RE_FUNC.search(script, func_match.end())
func_end = next_func.start() if next_func else len(script)
func_body = script[func_start:func_end]
local_match = _RE_LOCAL_HASHES.search(func_body)
if local_match:
hashes_raw = local_match.group(1)
hashes = [h.strip() for h in hashes_raw.split() if h.strip()]

View File

@@ -88,6 +88,20 @@ class Scraper(BaseScraper):
def __init__(self, url: str = SOURCE_URL):
super().__init__(url=url)
def _fetch_cores(self) -> list[str]:
"""Extract unique core names from es_bios.xml bios elements."""
raw = self._fetch_raw()
root = ET.fromstring(raw)
cores: set[str] = set()
for bios_elem in root.findall(".//system/bios"):
raw_core = bios_elem.get("core", "").strip()
if not raw_core:
continue
for part in raw_core.split(","):
name = part.strip()
if name:
cores.add(name)
return sorted(cores)
def fetch_requirements(self) -> list[BiosRequirement]:
"""Parse es_bios.xml and return BIOS requirements."""
@@ -214,6 +228,7 @@ class Scraper(BaseScraper):
"base_destination": "bios",
"hash_type": "md5",
"verification_mode": "md5",
"cores": self._fetch_cores(),
"systems": systems,
}

View File

@@ -0,0 +1,579 @@
#!/usr/bin/env python3
"""Scraper for RetroDECK BIOS requirements.
Source: https://github.com/RetroDECK/components
Format: component_manifest.json committed at <component>/component_manifest.json
Hash: MD5 primary, SHA256 for some entries (melonDS DSi BIOS)
RetroDECK verification logic:
- MD5 or SHA256 checked against expected value per file
- MD5 may be a list of multiple accepted hashes (xroar ROM variants) — joined
as comma-separated string per retrobios convention
- Files may declare paths via $bios_path, $saves_path, or $roms_path tokens
- $saves_path entries (GameCube memory card directories) are excluded —
these are directory placeholders, not BIOS files
- $roms_path entries are included with a roms/ prefix in destination,
consistent with Batocera's saves/ destination convention
- Entries with no hash are emitted without an md5 field (existence-only),
which is valid per the platform schema (e.g. pico-8 executables)
Component structure:
RetroDECK/components (GitHub, main branch)
├── <component>/component_manifest.json <- fetched directly via raw URL
├── archive_later/ <- skipped
└── archive_old/ <- skipped
BIOS may appear in three locations within a manifest:
- top-level 'bios' key (melonDS, xemu, xroar, pico-8)
- preset_actions.bios (duckstation, dolphin, pcsx2, ppsspp)
- cores.bios (not yet seen in practice, kept for safety)
ppsspp quirk: preset_actions.bios is a bare dict, not a list.
Adding to watch.yml (maintainer step):
from scraper.retrodeck_scraper import Scraper as RDS
config = RDS().generate_platform_yaml()
with open('platforms/retrodeck.yml', 'w') as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
print(f'RetroDECK: {len(config["systems"])} systems, version={config["version"]}')
"""
from __future__ import annotations
import json
import os
import re
import sys
import urllib.request
import urllib.error
from pathlib import Path
try:
from .base_scraper import BaseScraper, BiosRequirement, fetch_github_latest_version
except ImportError:
from base_scraper import BaseScraper, BiosRequirement, fetch_github_latest_version
PLATFORM_NAME = "retrodeck"
COMPONENTS_REPO = "RetroDECK/components"
COMPONENTS_BRANCH = "main"
COMPONENTS_API_URL = (
f"https://api.github.com/repos/{COMPONENTS_REPO}"
f"/git/trees/{COMPONENTS_BRANCH}?recursive=0"
)
RAW_BASE_URL = (
f"https://raw.githubusercontent.com/{COMPONENTS_REPO}"
f"/{COMPONENTS_BRANCH}"
)
# Top-level directories to ignore when enumerating components
SKIP_DIRS = {"archive_later", "archive_old", "automation-tools", ".github"}
# Default local path for --manifests-dir (standard flatpak install)
DEFAULT_LOCAL_MANIFESTS = (
"/var/lib/flatpak/app/net.retrodeck.retrodeck"
"/current/active/files/retrodeck/components"
)
# RetroDECK system ID -> retrobios slug.
# IDs absent from this map pass through unchanged (maintainer decides on slug).
# IDs mapped to None are skipped entirely (no retrobios equivalent).
SYSTEM_SLUG_MAP: dict[str, str | None] = {
# Nintendo
"nes": "nintendo-nes",
"snes": "nintendo-snes",
"snesna": "nintendo-snes",
"n64": "nintendo-64",
"n64dd": "nintendo-64dd",
"gc": "nintendo-gamecube",
"wii": "wii", # no retrobios slug yet — passes through
"wiiu": "nintendo-wii-u",
"switch": "nintendo-switch",
"gb": "nintendo-gb",
"gbc": "nintendo-gbc",
"gba": "nintendo-gba",
"nds": "nintendo-ds",
"3ds": "nintendo-3ds",
"n3ds": "nintendo-3ds", # azahar uses n3ds
"fds": "nintendo-fds",
"sgb": "nintendo-sgb",
"virtualboy": "nintendo-virtual-boy",
# Sony
"psx": "sony-playstation",
"ps2": "sony-playstation-2",
"ps3": "sony-playstation-3",
"psp": "sony-psp",
"psvita": "sony-psvita",
# Sega
"megadrive": "sega-mega-drive",
"genesis": "sega-mega-drive",
"megacd": "sega-mega-cd",
"megacdjp": "sega-mega-cd",
"segacd": "sega-mega-cd",
"saturn": "sega-saturn",
"saturnjp": "sega-saturn",
"dreamcast": "sega-dreamcast",
"naomi": "sega-dreamcast-arcade",
"naomi2": "sega-dreamcast-arcade",
"atomiswave": "sega-dreamcast-arcade",
"sega32x": "sega32x",
"sega32xjp": "sega32x",
"sega32xna": "sega32x",
"gamegear": "sega-game-gear",
"mastersystem": "sega-master-system",
# NEC
"tg16": "nec-pc-engine",
"tg-cd": "nec-pc-engine",
"pcengine": "nec-pc-engine",
"pcenginecd": "nec-pc-engine",
"pcfx": "nec-pc-fx",
# SNK
"neogeo": "snk-neogeo",
"neogeocd": "snk-neogeo-cd",
"neogeocdjp": "snk-neogeo-cd",
# Atari
"atari2600": "atari2600", # no retrobios slug yet — passes through
"atari800": "atari-400-800",
"atari5200": "atari-5200",
"atari7800": "atari-7800",
"atarilynx": "atari-lynx",
"atarist": "atari-st",
"atarijaguar": "jaguar",
# Panasonic / Philips
"3do": "panasonic-3do",
"cdimono1": "cdi",
"cdtv": "amigacdtv",
# Microsoft
"xbox": "xbox",
# Commodore
"amiga": "commodore-amiga",
"amigacd32": "amigacd32",
"c64": "commodore-c64",
# Tandy / Dragon
"coco": "trs80coco",
"dragon32": "dragon32",
"tanodragon": "dragon32", # Tano Dragon is a Dragon 32 clone
# Other
"colecovision": "coleco-colecovision",
"intellivision": "mattel-intellivision",
"o2em": "magnavox-odyssey2",
"msx": "microsoft-msx",
"msx2": "microsoft-msx",
"fmtowns": "fmtowns",
"scummvm": "scummvm",
"dos": "dos",
# Explicitly skipped — no retrobios equivalent
"mess": None,
}
# Matches all saves_path typo variants seen in the wild:
# $saves_path, $saves_paths_path, $saves_paths_paths_path, etc.
_SAVES_PATH_RE = re.compile(r"^\$saves_\w+/")
def _fetch_bytes(url: str, token: str | None = None) -> bytes:
headers = {"User-Agent": "retrobios-scraper/1.0"}
if token:
headers["Authorization"] = f"token {token}"
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read()
except urllib.error.URLError as e:
raise ConnectionError(f"Failed to fetch {url}: {e}") from e
def _fetch_json(url: str, token: str | None = None) -> dict | list:
return json.loads(_fetch_bytes(url, token).decode("utf-8"))
def _resolve_destination(raw_path: str, filename: str) -> str | None:
"""Resolve a RetroDECK path token to a retrobios destination string.
Returns None if the entry should be excluded ($saves_path variants).
$bios_path -> strip prefix; destination is bios-relative.
$roms_path -> preserve roms/ prefix (Batocera saves/ convention).
Bare directory paths get filename appended.
"""
if _SAVES_PATH_RE.match(raw_path):
return None
if raw_path.startswith("$bios_path/"):
remainder = raw_path[len("$bios_path/"):].strip("/")
if not remainder or remainder == filename:
return filename
# Subdirectory path — append filename if path looks like a directory
if not remainder.endswith(tuple(".bin .rom .zip .img .bin ".split())):
return remainder.rstrip("/") + "/" + filename
return remainder
if raw_path.startswith("$roms_path/"):
remainder = raw_path[len("$roms_path/"):].strip("/")
base = ("roms/" + remainder) if remainder else "roms"
return base.rstrip("/") + "/" + filename
# No recognised token — treat as bios-relative
remainder = raw_path.strip("/")
if not remainder:
return filename
return remainder.rstrip("/") + "/" + filename
def _normalise_md5(raw: str | list) -> str:
"""Return a comma-separated MD5 string.
xroar declares a list of accepted hashes for ROM variants;
retrobios platform schema accepts comma-separated MD5 strings.
"""
if isinstance(raw, list):
return ",".join(str(h).strip().lower() for h in raw if h)
return str(raw).strip().lower() if raw else ""
def _coerce_bios_to_list(val: object) -> list:
"""Ensure a bios value is always a list of dicts.
ppsspp declares preset_actions.bios as a bare dict, not a list.
"""
if isinstance(val, list):
return val
if isinstance(val, dict):
return [val]
return []
def _parse_required(raw: object) -> bool:
"""Coerce RetroDECK required field to bool.
Values seen: 'Required', 'Optional', 'At least one BIOS file required',
'Optional, for boot logo', True, False, absent (None).
Absent is treated as required.
"""
if isinstance(raw, bool):
return raw
if raw is None:
return True
return str(raw).strip().lower() not in ("optional", "false", "no", "0")
def _parse_manifest(data: dict) -> list[BiosRequirement]:
"""Parse one component_manifest.json into BiosRequirement objects."""
requirements: list[BiosRequirement] = []
seen: set[tuple[str, str]] = set()
for _component_key, component_val in data.items():
if not isinstance(component_val, dict):
continue
# Component-level system fallback (may be a list for multi-system components)
comp_system = component_val.get("system", "")
if isinstance(comp_system, list):
comp_system = comp_system[0] if comp_system else ""
comp_system = str(comp_system).strip().lower()
# Collect bios entries from all known locations
bios_sources: list[list] = []
if "bios" in component_val:
bios_sources.append(_coerce_bios_to_list(component_val["bios"]))
pa = component_val.get("preset_actions", {})
if isinstance(pa, dict) and "bios" in pa:
bios_sources.append(_coerce_bios_to_list(pa["bios"]))
cores = component_val.get("cores", {})
if isinstance(cores, dict) and "bios" in cores:
bios_sources.append(_coerce_bios_to_list(cores["bios"]))
if not bios_sources:
continue
for bios_list in bios_sources:
for entry in bios_list:
if not isinstance(entry, dict):
continue
filename = str(entry.get("filename", "")).strip()
if not filename:
continue
# System slug — entry-level preferred, component-level fallback
entry_system = entry.get("system", comp_system)
if isinstance(entry_system, list):
entry_system = entry_system[0] if entry_system else comp_system
entry_system = str(entry_system).strip().lower()
if entry_system in SYSTEM_SLUG_MAP:
slug = SYSTEM_SLUG_MAP[entry_system]
if slug is None:
continue # explicitly skipped (e.g. mess)
else:
slug = entry_system # unknown — pass through
# Destination resolution
paths_raw = entry.get("paths")
if paths_raw is None:
destination = filename
elif isinstance(paths_raw, list):
destination = None
for p in paths_raw:
resolved = _resolve_destination(str(p), filename)
if resolved is not None:
destination = resolved
break
if destination is None:
continue # all paths were saves_path variants — skip
else:
destination = _resolve_destination(str(paths_raw), filename)
if destination is None:
continue # saves_path — skip
# Hash fields
md5_val: str | None = None
sha256_val: str | None = None
raw_md5 = entry.get("md5")
if raw_md5:
md5_val = _normalise_md5(raw_md5) or None
raw_sha256 = entry.get("sha256")
if raw_sha256:
sha256_val = str(raw_sha256).strip().lower() or None
required = _parse_required(entry.get("required"))
dedup_key = (slug, filename.lower())
if dedup_key in seen:
continue
seen.add(dedup_key)
req = BiosRequirement(
name=filename,
system=slug,
md5=md5_val,
sha1=None,
destination=destination,
required=required,
)
req._sha256 = sha256_val # type: ignore[attr-defined]
requirements.append(req)
return requirements
class Scraper(BaseScraper):
"""Scraper for RetroDECK component_manifest.json files.
Two modes:
remote (default): fetches manifests directly from RetroDECK/components
via GitHub raw URLs, enumerating components via the
GitHub API tree endpoint
local: reads manifests from a directory on disk
(--manifests-dir or pass manifests_dir= to __init__)
"""
def __init__(
self,
manifests_dir: str | None = None,
github_token: str | None = None,
):
super().__init__()
self.manifests_dir = manifests_dir
self.github_token = github_token or os.environ.get("GITHUB_TOKEN")
self._release_version: str | None = None
# ── Remote ───────────────────────────────────────────────────────────────
def _list_component_dirs(self) -> list[str]:
"""Return top-level component directory names from the GitHub API."""
tree = _fetch_json(COMPONENTS_API_URL, self.github_token)
return [
item["path"]
for item in tree.get("tree", [])
if item["type"] == "tree" and item["path"] not in SKIP_DIRS
]
def _fetch_remote_manifests(self) -> list[dict]:
component_dirs = self._list_component_dirs()
manifests: list[dict] = []
for component in sorted(component_dirs):
url = f"{RAW_BASE_URL}/{component}/component_manifest.json"
print(f" Fetching {component}/component_manifest.json ...", file=sys.stderr)
try:
raw = _fetch_bytes(url, self.github_token)
manifests.append(json.loads(raw.decode("utf-8")))
except ConnectionError:
pass # component has no manifest — skip silently
except json.JSONDecodeError as e:
print(f" WARNING: parse error in {component}: {e}", file=sys.stderr)
return manifests
# ── Local ─────────────────────────────────────────────────────────────────
def _fetch_local_manifests(self) -> list[dict]:
root = Path(self.manifests_dir)
if not root.is_dir():
raise FileNotFoundError(f"Manifests directory not found: {root}")
manifests: list[dict] = []
# Only scan top-level component directories; skip archive and hidden dirs
for component_dir in sorted(root.iterdir()):
if not component_dir.is_dir():
continue
if component_dir.name in SKIP_DIRS or component_dir.name.startswith("."):
continue
manifest_path = component_dir / "component_manifest.json"
if not manifest_path.exists():
continue
try:
with open(manifest_path) as f:
manifests.append(json.load(f))
except (json.JSONDecodeError, OSError) as e:
print(f" WARNING: Could not parse {manifest_path}: {e}", file=sys.stderr)
return manifests
# ── BaseScraper interface ─────────────────────────────────────────────────
def fetch_requirements(self) -> list[BiosRequirement]:
manifests = (
self._fetch_local_manifests()
if self.manifests_dir
else self._fetch_remote_manifests()
)
requirements: list[BiosRequirement] = []
seen: set[tuple[str, str]] = set()
for manifest in manifests:
for req in _parse_manifest(manifest):
key = (req.system, req.name.lower())
if key not in seen:
seen.add(key)
requirements.append(req)
return requirements
def validate_format(self, raw_data: str) -> bool:
try:
return isinstance(json.loads(raw_data), dict)
except json.JSONDecodeError:
return False
def generate_platform_yaml(self) -> dict:
requirements = self.fetch_requirements()
systems: dict[str, dict] = {}
for req in requirements:
systems.setdefault(req.system, {"files": []})
entry: dict = {
"name": req.name,
"destination": req.destination,
"required": req.required,
}
if req.md5:
entry["md5"] = req.md5
sha256 = getattr(req, "_sha256", None)
if sha256 and not req.md5:
entry["sha256"] = sha256
systems[req.system]["files"].append(entry)
version = self._release_version or ""
if not version:
try:
version = fetch_github_latest_version(COMPONENTS_REPO) or ""
except (ConnectionError, OSError):
pass
return {
"platform": "RetroDECK",
"version": version,
"homepage": "https://retrodeck.net",
"source": f"https://github.com/{COMPONENTS_REPO}",
"base_destination": "bios",
"hash_type": "md5",
"verification_mode": "md5",
"systems": systems,
}
def main() -> None:
import argparse
parser = argparse.ArgumentParser(
description="Scrape RetroDECK component_manifest.json BIOS requirements"
)
parser.add_argument(
"--manifests-dir", metavar="DIR",
help=(
"Read manifests from a local directory instead of fetching from GitHub. "
f"Live install path: {DEFAULT_LOCAL_MANIFESTS}"
),
)
parser.add_argument(
"--token", metavar="TOKEN",
help="GitHub personal access token (or set GITHUB_TOKEN env var)",
)
parser.add_argument(
"--dry-run", action="store_true",
help="Print per-system summary without generating output",
)
parser.add_argument(
"--output", "-o", metavar="FILE",
help="Write generated platform YAML to FILE",
)
parser.add_argument(
"--json", action="store_true",
help="Print platform config as JSON (for debugging)",
)
args = parser.parse_args()
scraper = Scraper(manifests_dir=args.manifests_dir, github_token=args.token)
try:
reqs = scraper.fetch_requirements()
except (ConnectionError, FileNotFoundError) as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if args.dry_run:
by_system: dict[str, list] = {}
for r in reqs:
by_system.setdefault(r.system, []).append(r)
for system, files in sorted(by_system.items()):
req_c = sum(1 for f in files if f.required)
opt_c = len(files) - req_c
print(f" {system}: {req_c} required, {opt_c} optional")
print(f"\nTotal: {len(reqs)} entries across {len(by_system)} systems")
return
config = scraper.generate_platform_yaml()
if args.json:
print(json.dumps(config, indent=2))
return
if args.output:
try:
import yaml
except ImportError:
print("Error: PyYAML required (pip install pyyaml)", file=sys.stderr)
sys.exit(1)
def _str_representer(dumper, data):
if any(c in data for c in "()[]{}:#"):
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"')
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
yaml.add_representer(str, _str_representer)
with open(args.output, "w") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
total = sum(len(s["files"]) for s in config["systems"].values())
print(
f"Written {total} entries across "
f"{len(config['systems'])} systems to {args.output}"
)
return
systems = len(set(r.system for r in reqs))
print(f"Scraped {len(reqs)} entries across {systems} systems. Use --dry-run, --json, or --output FILE.")
if __name__ == "__main__":
main()

View File

@@ -174,14 +174,17 @@ def validate_file(
else:
result.add_warning("File not referenced in any platform config - needs manual review")
if filepath.startswith("bios/"):
parts = filepath.split("/")
normalized = os.path.normpath(filepath)
if os.path.islink(filepath):
result.add_check(False, "Symlinks are not allowed")
elif normalized.startswith("bios" + os.sep):
parts = normalized.split(os.sep)
if len(parts) >= 4:
result.add_check(True, f"Correct placement: bios/{parts[1]}/{parts[2]}/")
else:
result.add_warning("File should be in bios/Manufacturer/Console/ structure")
else:
result.add_warning(f"File is not under bios/ directory")
result.add_warning("File is not under bios/ directory")
if name_known and not sha1_known and not md5_known:
result.add_info(

View File

@@ -37,7 +37,7 @@ sys.path.insert(0, os.path.dirname(__file__))
from common import (
build_zip_contents_index, check_inside_zip, group_identical_platforms,
load_emulator_profiles, load_platform_config, md5sum, md5_composite,
resolve_local_file,
resolve_local_file, resolve_platform_cores,
)
DEFAULT_DB = "database.json"
@@ -198,9 +198,7 @@ def find_undeclared_files(
"""Find files needed by cores but not declared in platform config."""
# Collect all filenames declared by this platform
declared_names: set[str] = set()
platform_systems: set[str] = set()
for sys_id, system in config.get("systems", {}).items():
platform_systems.add(sys_id)
for fe in system.get("files", []):
name = fe.get("name", "")
if name:
@@ -217,21 +215,15 @@ def find_undeclared_files(
by_name = db.get("indexes", {}).get("by_name", {})
profiles = emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir)
relevant = resolve_platform_cores(config, profiles)
undeclared = []
seen = set()
for emu_name, profile in sorted(profiles.items()):
# Skip launchers — they don't use system_dir for BIOS
if profile.get("type") == "launcher":
if profile.get("type") in ("launcher", "alias"):
continue
emu_systems = set(profile.get("systems", []))
# Only check emulators whose systems overlap with this platform
if not emu_systems & platform_systems:
if emu_name not in relevant:
continue
# Skip if emulator's data_directories cover the files
emu_dd = {dd.get("ref", "") for dd in profile.get("data_directories", [])}
covered_by_dd = bool(emu_dd & declared_dd)
for f in profile.get("files", []):
fname = f.get("name", "")
if not fname or fname in seen:
@@ -241,8 +233,6 @@ def find_undeclared_files(
continue
if fname in declared_names:
continue
if covered_by_dd:
continue
in_repo = fname in by_name or fname.rsplit("/", 1)[-1] in by_name
seen.add(fname)
@@ -274,10 +264,12 @@ def find_exclusion_notes(
for sys_id in config.get("systems", {}):
platform_systems.add(sys_id)
relevant = resolve_platform_cores(config, profiles)
notes = []
for emu_name, profile in sorted(profiles.items()):
emu_systems = set(profile.get("systems", []))
if not emu_systems & platform_systems:
# Match by core resolution OR system intersection (documents all potential emulators)
if emu_name not in relevant and not (emu_systems & platform_systems):
continue
emu_display = profile.get("emulator", emu_name)
@@ -381,6 +373,11 @@ def verify_platform(
for s in file_severity.values():
counts[s] = counts.get(s, 0) + 1
# Count by file status (ok/untested/missing)
status_counts: dict[str, int] = {}
for s in file_status.values():
status_counts[s] = status_counts.get(s, 0) + 1
# Cross-reference undeclared files
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles)
exclusions = find_exclusion_notes(config, emulators_dir, emu_profiles)
@@ -390,6 +387,7 @@ def verify_platform(
"verification_mode": mode,
"total_files": len(file_status),
"severity_counts": counts,
"status_counts": status_counts,
"undeclared_files": undeclared,
"exclusion_notes": exclusions,
"details": details,
@@ -421,8 +419,9 @@ def print_platform_result(result: dict, group: list[str]) -> None:
else:
parts = [f"{ok_count}/{total} present"]
else:
untested = c.get(Severity.WARNING, 0)
missing = c.get(Severity.CRITICAL, 0)
sc = result.get("status_counts", {})
untested = sc.get(Status.UNTESTED, 0)
missing = sc.get(Status.MISSING, 0)
parts = [f"{ok_count}/{total} OK"]
if untested:
parts.append(f"{untested} untested")

View File

@@ -31,9 +31,9 @@ import yaml
from common import (
build_zip_contents_index, check_inside_zip, group_identical_platforms,
load_emulator_profiles, load_platform_config, md5_composite, md5sum,
resolve_local_file,
resolve_local_file, resolve_platform_cores,
)
from verify import Severity, Status, verify_platform, find_undeclared_files
from verify import Severity, Status, verify_platform, find_undeclared_files, find_exclusion_notes
def _h(data: bytes) -> dict:
@@ -471,13 +471,13 @@ class TestE2E(unittest.TestCase):
profiles = load_emulator_profiles(self.emulators_dir)
self.assertNotIn("test_alias", profiles)
def test_43_cross_ref_data_dir_suppresses_gaps(self):
def test_43_cross_ref_data_dir_does_not_suppress_files(self):
config = load_platform_config("test_md5", 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}
# dd_covered.bin from TestEmuDD should NOT appear (data_dir match)
self.assertNotIn("dd_covered.bin", names)
# dd_covered.bin is a file entry, not data_dir content — still undeclared
self.assertIn("dd_covered.bin", names)
def test_44_cross_ref_skips_launchers(self):
config = load_platform_config("test_existence", self.platforms_dir)
@@ -555,5 +555,91 @@ class TestE2E(unittest.TestCase):
self.assertEqual(status, "user_provided")
def test_resolve_cores_all_libretro(self):
"""all_libretro resolves to all libretro-type profiles, excludes alias/standalone."""
config = {"cores": "all_libretro", "systems": {"nes": {"files": []}}}
profiles = {
"fceumm": {"type": "libretro", "systems": ["nes"], "files": []},
"dolphin_standalone": {"type": "standalone", "systems": ["gc"], "files": []},
"gambatte": {"type": "pure_libretro", "systems": ["gb"], "files": []},
"mednafen_psx_hw": {"type": "alias", "alias_of": "beetle_psx", "files": []},
}
result = resolve_platform_cores(config, profiles)
self.assertEqual(result, {"fceumm", "gambatte"})
def test_resolve_cores_explicit_list(self):
"""Explicit cores list matches against profile dict keys."""
config = {"cores": ["fbneo", "opera"], "systems": {"arcade": {"files": []}}}
profiles = {
"fbneo": {"type": "pure_libretro", "systems": ["arcade"], "files": []},
"opera": {"type": "libretro", "systems": ["3do"], "files": []},
"mame": {"type": "libretro", "systems": ["arcade"], "files": []},
}
result = resolve_platform_cores(config, profiles)
self.assertEqual(result, {"fbneo", "opera"})
def test_resolve_cores_fallback_systems(self):
"""Missing cores: field falls back to system ID intersection."""
config = {"systems": {"nes": {"files": []}}}
profiles = {
"fceumm": {"type": "libretro", "systems": ["nes"], "files": []},
"dolphin": {"type": "libretro", "systems": ["gc"], "files": []},
}
result = resolve_platform_cores(config, profiles)
self.assertEqual(result, {"fceumm"})
def test_resolve_cores_excludes_alias(self):
"""Alias profiles never included even if name matches cores list."""
config = {"cores": ["mednafen_psx_hw"], "systems": {}}
profiles = {
"mednafen_psx_hw": {"type": "alias", "alias_of": "beetle_psx", "files": []},
}
result = resolve_platform_cores(config, profiles)
self.assertEqual(result, set())
def test_cross_reference_uses_core_resolution(self):
"""Cross-reference matches by cores: field, not system intersection."""
config = {
"cores": ["fbneo"],
"systems": {
"arcade": {"files": [{"name": "neogeo.zip", "md5": "abc"}]}
}
}
profiles = {
"fbneo": {
"emulator": "FBNeo", "systems": ["snk-neogeo-mvs"],
"type": "pure_libretro",
"files": [
{"name": "neogeo.zip", "required": True},
{"name": "neocdz.zip", "required": True},
],
},
}
db = {"indexes": {"by_name": {"neocdz.zip": {"sha1": "x"}}}}
undeclared = find_undeclared_files(config, self.emulators_dir, db, profiles)
names = [u["name"] for u in undeclared]
self.assertIn("neocdz.zip", names)
self.assertNotIn("neogeo.zip", names)
def test_exclusion_notes_uses_core_resolution(self):
"""Exclusion notes match by cores: field, not system intersection."""
config = {
"cores": ["desmume2015"],
"systems": {"nds": {"files": []}}
}
profiles = {
"desmume2015": {
"emulator": "DeSmuME 2015", "type": "frozen_snapshot",
"systems": ["nintendo-ds"],
"files": [],
"exclusion_note": "Frozen snapshot, code never loads BIOS",
},
}
notes = find_exclusion_notes(config, self.emulators_dir, profiles)
emu_names = [n["emulator"] for n in notes]
self.assertIn("DeSmuME 2015", emu_names)
if __name__ == "__main__":
unittest.main()