12 Commits

Author SHA1 Message Date
Abdessamad Derraz
8f29e0e85c Update DB timestamp; add ti99sim emulator
Bump database.json generated_at timestamp and add a new emulator metadata file emulators/ti99sim.yml. The new YAML registers the TI-99/Sim standalone emulator (ti99sim) with system info, core version, notes on ROM handling, and multiple required/optional ROM entries (with SHA1s, sizes and validation notes) for TI-99/4A support.
2026-03-29 23:33:12 +02:00
Abdessamad Derraz
f39b11955f feat: add ROM_OS_B_NTSC and fs-5500_disk.rom, fix retrodeck
Add Atari 800 OS Rev B NTSC (CRC32 0e86d61d, canonical sysrom.c
match) and National FS-5500 disk controller ROM for openMSX.
Remove ROM_400/800_CUSTOM from retrodeck.yml (config slot key with
forward slash in name, not a real file).
2026-03-29 23:23:52 +02:00
Abdessamad Derraz
83ccf17b11 feat: add ti99 and msx BIOS entries, add fs-5500 disk rom 2026-03-29 23:20:44 +02:00
Abdessamad Derraz
56bff1d013 feat: add ti99 system to mame profile 2026-03-29 23:16:33 +02:00
Abdessamad Derraz
3bd9c0ebef chore: regenerate database, add atari 400/800 BIOS 2026-03-29 23:16:19 +02:00
Abdessamad Derraz
3824193a11 feat: add socrates and vc4000 systems to mame profiles 2026-03-29 23:15:25 +02:00
Abdessamad Derraz
2e21d64a08 refactor: harden codebase and remove unicode artifacts
- fix urllib.parse.quote import (was urllib.request.quote)
- add operator precedence parens in generate_pack dedup check
- narrow bare except to specific types in batocera target scraper
- cache load_platform_config and build_zip_contents_index results
- add selective algorithm support to compute_hashes
- atomic write for fetch_large_file (tmp + rename)
- add response size limit to base scraper fetch
- extract build_target_cores_cache to common.py (dedup verify/pack)
- hoist _build_supplemental_index out of per-platform loop
- migrate function-attribute caches to module-level dicts
- add @abstractmethod to BaseTargetScraper.fetch_targets
- remove backward-compat re-exports from common.py
- replace em-dashes and unicode arrows with ASCII equivalents
- remove decorative section dividers and obvious comments
2026-03-29 23:15:20 +02:00
Abdessamad Derraz
0c5cde83e1 Add TRS-80, RX-78, Sega AI entries; refactor tools
Add many MAME/MESS BIOS entries (TRS-80 family, Bandai RX-78, Sega AI) and update docs/navigation counts (README, mkdocs). Remove empty supplemental file references from database.json and update generated timestamps and totals. Harden and refactor tooling: add MAX_RESPONSE_SIZE limited reader in base_scraper, make target scrapers an abstract base, narrow exception handling in the Batocera targets parser, and switch generate_pack.py and verify.py to use build_target_cores_cache (simplifies target config loading and error handling). verify.py also loads supplemental cross-reference names and accepts them through verify_platform. Update tests to import from updated modules (validation/truth). Misc: small bugfix for case-insensitive path conflict check.
2026-03-29 23:04:30 +02:00
Abdessamad Derraz
a08c730805 fix: case-insensitive data dir basename resolution 2026-03-29 23:01:32 +02:00
Abdessamad Derraz
84decad08d chore: remove zero-byte placeholder files from bios/ 2026-03-29 22:57:07 +02:00
Abdessamad Derraz
f3de3ead20 Add PV-2000 & Supracan BIOS; simplify Vita3K
Add Casio PV-2000 BIOS entry (pv2000.zip) to MAME and MESS profiles and update system lists/counts. Add Funtech Super A'Can BIOS entries (supracan.zip and umc6650.zip) with ROM contents to mamemess. Simplify and condense Vita3K emulator profile (rename fields, update profiled_date, add PSVUPDAT.PUP and optional PSP2UPDAT.PUP file entries, and clarify install/partition behavior). Bump database generated_at timestamp and add a system alias mapping "psvita" -> "sony-playstation-vita" in scripts/common.py.
2026-03-29 22:51:30 +02:00
Abdessamad Derraz
463fca7e7d Regenerate database and update emulator profiles
Regenerate database.json and update README counts/timestamps; add and normalize numerous BIOS entries and hashes. Key changes: update generated_at timestamp and system count (355→357) in README; add OpenBIOS / HLE fallback and additional aliases to beetle_psx, include beetle_psx core name and profiled_date update; add laseractive to ares systems; adjust atari800 systems and source_ref line numbers; mark dinothawr as a system and expand its note; update gsplus upstream/profile date, add apple-iie system and tweak source_refs; add pcsx2 core to lrps2; refresh mame profiled_date and add multiple systems and BIOS root sets. Miscellaneous script changes and other JSON normalization to reflect newly discovered/merged BIOS files.
2026-03-29 22:41:01 +02:00
40 changed files with 3806 additions and 1018 deletions

View File

@@ -2,7 +2,7 @@
Complete BIOS and firmware packs for Batocera, BizHawk, EmuDeck, Lakka, Recalbox, RetroArch, RetroBat, RetroDECK, RetroPie, and RomM.
**7,245** verified files across **355** systems, ready to extract into your emulator's BIOS directory.
**7,244** verified files across **387** systems, ready to extract into your emulator's BIOS directory.
## Quick Install
@@ -45,13 +45,13 @@ Each file is checked against the emulator's source code to match what the code a
- **10 platforms** supported with platform-specific verification
- **328 emulators** profiled from source (RetroArch cores + standalone)
- **355 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **7,245 files** verified with MD5, SHA1, CRC32 checksums
- **387 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **7,244 files** verified with MD5, SHA1, CRC32 checksums
- **9266 MB** total collection size
## Supported systems
NES, SNES, Nintendo 64, GameCube, Wii, Game Boy, Game Boy Advance, Nintendo DS, Nintendo 3DS, Switch, PlayStation, PlayStation 2, PlayStation 3, PSP, PS Vita, Mega Drive, Saturn, Dreamcast, Game Gear, Master System, Neo Geo, Atari 2600, Atari 7800, Atari Lynx, Atari ST, MSX, PC Engine, TurboGrafx-16, ColecoVision, Intellivision, Commodore 64, Amiga, ZX Spectrum, Arcade (MAME), and 321+ more.
NES, SNES, Nintendo 64, GameCube, Wii, Game Boy, Game Boy Advance, Nintendo DS, Nintendo 3DS, Switch, PlayStation, PlayStation 2, PlayStation 3, PSP, PS Vita, Mega Drive, Saturn, Dreamcast, Game Gear, Master System, Neo Geo, Atari 2600, Atari 7800, Atari Lynx, Atari ST, MSX, PC Engine, TurboGrafx-16, ColecoVision, Intellivision, Commodore 64, Amiga, ZX Spectrum, Arcade (MAME), and 353+ more.
Full list with per-file details: **[https://abdess.github.io/retrobios/](https://abdess.github.io/retrobios/)**
@@ -66,7 +66,7 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
| Recalbox | 276/346 (79.8%) | 273 | 3 | 70 |
| RetroArch | 443/448 (98.9%) | 443 | 0 | 5 |
| RetroBat | 330/331 (99.7%) | 326 | 4 | 1 |
| RetroDECK | 1958/2007 (97.6%) | 1876 | 82 | 49 |
| RetroDECK | 1958/2007 (97.6%) | 1932 | 26 | 49 |
| RetroPie | 443/448 (98.9%) | 443 | 0 | 5 |
| RomM | 372/374 (99.5%) | 372 | 0 | 2 |
@@ -130,4 +130,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
This repository provides BIOS files for personal backup and archival purposes.
*Auto-generated on 2026-03-29T14:04:16Z*
*Auto-generated on 2026-03-29T21:00:40Z*

Binary file not shown.

Binary file not shown.

View File

View File

@@ -1,7 +1,7 @@
{
"generated_at": "2026-03-29T14:00:48Z",
"total_files": 7245,
"total_size": 9715681216,
"generated_at": "2026-03-29T21:32:22Z",
"total_files": 7239,
"total_size": 8539795099,
"files": {
"520d3d1b5897800af47f92efd2444a26b7a7dead": {
"path": "bios/3DO Company/3DO/3do_arcade_saot.bin",
@@ -27064,8 +27064,8 @@
"adler32": "9147003d"
},
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b": {
"path": "bios/Atari/400-800/.variants/ATARIOSB.ROM.0e86d61d",
"name": "ATARIOSB.ROM",
"path": "bios/Atari/400-800/ROM_OS_B_NTSC",
"name": "ROM_OS_B_NTSC",
"size": 10240,
"sha1": "db1031585968cfc6ec2ecda5c9a5a52f61444a3b",
"md5": "4177f386a3bac989a981d3fe3388cb6c",
@@ -35283,6 +35283,16 @@
"crc32": "5bf38e13",
"adler32": "240e704d"
},
"78cd7f847e77fd8cd51a647efb2725ba93f4c471": {
"path": "bios/Microsoft/MSX/share/systemroms/fs-5500_disk.rom",
"name": "fs-5500_disk.rom",
"size": 16384,
"sha1": "78cd7f847e77fd8cd51a647efb2725ba93f4c471",
"md5": "86269da485e852d9f581ac27f4ba32ff",
"sha256": "954e13823e232e745bf527fc5b2d80d6dd55df28593f3d71fc2f4ba6a569ab21",
"crc32": "1e7d6512",
"adler32": "550f3e4c"
},
"3a9a942ed888dd641cddf8deada1879c454df3c6": {
"path": "bios/Microsoft/MSX/share/systemroms/fs-5500_kanjibasic.rom",
"name": "fs-5500_kanjibasic.rom",
@@ -36133,16 +36143,6 @@
"crc32": "c1815325",
"adler32": "c48a690f"
},
"da39a3ee5e6b4b0d3255bfef95601890afd80709": {
"path": "bios/NEC/PC-98/key.txt",
"name": "key.txt",
"size": 0,
"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"md5": "d41d8cd98f00b204e9800998ecf8427e",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"crc32": "00000000",
"adler32": "00000001"
},
"3518193b8207bdebf22c1380c2db8c554baff329": {
"path": "bios/NEC/PC-98/n88.rom",
"name": "n88.rom",
@@ -72392,66 +72392,6 @@
"sha256": "7dc407fbccbf684dc677bed81120f45f0d3406ff7945eaf207a5c38b036c30e0",
"crc32": "a42ef0fd",
"adler32": "0d15827f"
},
"ac4b78d53c7a97da2451ca35498395d8dd1e3024": {
"path": ".cache/large/Firmware.19.0.0.zip",
"name": "Firmware.19.0.0.zip",
"size": 338076508,
"sha1": "ac4b78d53c7a97da2451ca35498395d8dd1e3024",
"md5": "72d6c73306c7f0b76723f989e7e1bdd1",
"sha256": "2f3791655e6c1b56f07a309b69ce8ea35d8412695599bbb6d4b0e29d1b044b66",
"crc32": "77228c84",
"adler32": "471a3291"
},
"add40c002084e8e25768671877b2aa603aaf5cb1": {
"path": ".cache/large/maclc3.zip",
"name": "maclc3.zip",
"size": 189428461,
"sha1": "add40c002084e8e25768671877b2aa603aaf5cb1",
"md5": "aff722788800df5b22d5a07cf8e558ee",
"sha256": "e663e456e88f475b3cacc06e75f50605e700789aa327b6648c627a560762a5d6",
"crc32": "81f21918",
"adler32": "dbd42440"
},
"b48f44194fe918aaaec5298861479512b581d661": {
"path": ".cache/large/dsi_nand.bin",
"name": "dsi_nand.bin",
"size": 251658304,
"sha1": "b48f44194fe918aaaec5298861479512b581d661",
"md5": "dfafb1908da8f527df7a372e649b50be",
"sha256": "f57d9bf00529bec35d58404faff029a193fd2ccda0a83403ec4e6cc32626721b",
"crc32": "416bf51a",
"adler32": "3b3e7d56"
},
"093f8698b54b78dcb701de2043f82639de51d63b": {
"path": ".cache/large/PS3UPDAT.PUP",
"name": "PS3UPDAT.PUP",
"size": 206126236,
"sha1": "093f8698b54b78dcb701de2043f82639de51d63b",
"md5": "05fe32f5dc8c78acbcd84d36ee7fdc5b",
"sha256": "69070a95780f59fc9e0d82bcf53eb9b28fd4ed4a7d54d0a40045f80422fd98d6",
"crc32": "24bdb2db",
"adler32": "1ec0b1c3"
},
"ed3a4cb264fff283209f10ae58c96c6090fed187": {
"path": ".cache/large/PSP2UPDAT.PUP",
"name": "PSP2UPDAT.PUP",
"size": 56778752,
"sha1": "ed3a4cb264fff283209f10ae58c96c6090fed187",
"md5": "59dcf059d3328fb67be7e51f8aa33418",
"sha256": "c3c03fc7363dd573d90e5157629bf11551f434b283cc898d9ffc71dd716b791c",
"crc32": "082ecf86",
"adler32": "620a2ff1"
},
"cc72dfcc964577cc29112ef368c28f55277c237c": {
"path": ".cache/large/PSVUPDAT.PUP",
"name": "PSVUPDAT.PUP",
"size": 133834240,
"sha1": "cc72dfcc964577cc29112ef368c28f55277c237c",
"md5": "f2c7b12fe85496ec88a0391b514d6e3b",
"sha256": "6ef6dc8da6db026f28647713e473486d770087a605c52a8d751bfca7478386cf",
"crc32": "39075d41",
"adler32": "75d71010"
}
},
"indexes": {
@@ -75984,6 +75924,7 @@
"f873f1260b14f1468fa118778ae1c3d2": "c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c",
"318b6aa76da71c54ccad7734356e1902": "f1525de4e0b60a6687156c2a96f8a8b2044b6c56",
"5bf4fdfd3c3ffea3e573b386550cb3fa": "44e0dd215b2a9f0770dd76fb49187c05b083eed9",
"86269da485e852d9f581ac27f4ba32ff": "78cd7f847e77fd8cd51a647efb2725ba93f4c471",
"afbe6ba903453902540ae988cc89dc7b": "3a9a942ed888dd641cddf8deada1879c454df3c6",
"090539674630c1338a90a1df943a93e2": "9ed3ab6d893632b9246e91b412cd5db519e7586b",
"dcd5e2388115172f2fb48875b2089dbf": "4be8371f3b03e70ddaca495958345f3c4f8e2d36",
@@ -76069,7 +76010,6 @@
"38d32748ae49d1815b0614970849fd40": "78ba9960f135372825ab7244b5e4e73a810002ff",
"8b52de9032ea62153dc783151306595f": "0877ffb4b4d1c18283468be3579b72ed8c22e3ac",
"72ea51443070f0e9212bfc9b793ee28e": "a2fb11c000ed7c976520622cfb7940ed6ddc904e",
"d41d8cd98f00b204e9800998ecf8427e": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"22be239bc0c4298bc0561252eed98633": "3518193b8207bdebf22c1380c2db8c554baff329",
"e28fe3f520bea594350ea8fb00395370": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
"a8e298da7ac947669bcb1ff25cee0a83": "4ae4d37409ff99411a623da9f6a44192170a854e",
@@ -79694,13 +79634,7 @@
"a9082f02d4f93c1f6c4e428e06b834e8": "d07114a9f3490338a265fb30d16b052c8da3bb7d",
"8d4abc7dd31a64f2ddd811c19ae8c09e": "b3730071e789877bea3373ffa59ca673a4b1f4c9",
"6e7e391c629332cc9d29902b98e52f94": "48024e2f5943ed86cb1b8e9443603991cdb05808",
"bbd27768c16e6077b1a90dc0eb8558a3": "24a487f22f3da292e179b3edd6c30222a8ff933d",
"72d6c73306c7f0b76723f989e7e1bdd1": "ac4b78d53c7a97da2451ca35498395d8dd1e3024",
"aff722788800df5b22d5a07cf8e558ee": "add40c002084e8e25768671877b2aa603aaf5cb1",
"dfafb1908da8f527df7a372e649b50be": "b48f44194fe918aaaec5298861479512b581d661",
"05fe32f5dc8c78acbcd84d36ee7fdc5b": "093f8698b54b78dcb701de2043f82639de51d63b",
"59dcf059d3328fb67be7e51f8aa33418": "ed3a4cb264fff283209f10ae58c96c6090fed187",
"f2c7b12fe85496ec88a0391b514d6e3b": "cc72dfcc964577cc29112ef368c28f55277c237c"
"bbd27768c16e6077b1a90dc0eb8558a3": "24a487f22f3da292e179b3edd6c30222a8ff933d"
},
"by_name": {
"3do_arcade_saot.bin": [
@@ -79996,8 +79930,7 @@
"1365b42d35d80feac9050caea8d6bd9d374fd1d2"
],
"maclc3.zip": [
"f454095619834dbfb9e8de0111bb3ee5fc21622d",
"add40c002084e8e25768671877b2aa603aaf5cb1"
"f454095619834dbfb9e8de0111bb3ee5fc21622d"
],
"macos3.img": [
"0546dd0fa34f4e6d913e4254ddb5350e1e42800c"
@@ -87355,9 +87288,8 @@
"1540OS3.V0": [
"454fdd9005c43142f827e1e506bf75fda0fe2a65"
],
"ATARIOSB.ROM": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b",
"f1f0741b1d34fb4350cf7cb8ab3b6ea11cdd8174"
"ROM_OS_B_NTSC": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"ATARIBAS.ROM": [
"3693c9cb9bf3b41bae1150f7a8264992468fc8c0"
@@ -87365,6 +87297,10 @@
"ATARIOSA.ROM": [
"6dd53356159a129ed12367beb3b24a771d41adb0"
],
"ATARIOSB.ROM": [
"f1f0741b1d34fb4350cf7cb8ab3b6ea11cdd8174",
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"ATARIXL.ROM": [
"ae4f523ba08b6fd59f3cae515a2b2410bbd98f55"
],
@@ -89768,6 +89704,9 @@
"fs-5500_basic-bios2.rom": [
"44e0dd215b2a9f0770dd76fb49187c05b083eed9"
],
"fs-5500_disk.rom": [
"78cd7f847e77fd8cd51a647efb2725ba93f4c471"
],
"fs-5500_kanjibasic.rom": [
"3a9a942ed888dd641cddf8deada1879c454df3c6"
],
@@ -89999,9 +89938,6 @@
"ide.rom": [
"0877ffb4b4d1c18283468be3579b72ed8c22e3ac"
],
"key.txt": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
],
"n88_1.rom": [
"4ae4d37409ff99411a623da9f6a44192170a854e"
],
@@ -99572,8 +99508,7 @@
"41d8c5c89f72206b873633ff31bcf4f82608e5a4"
],
"PSP2UPDAT.PUP": [
"3ae832c9800fcaa007eccfc48f24242967c111f8",
"ed3a4cb264fff283209f10ae58c96c6090fed187"
"3ae832c9800fcaa007eccfc48f24242967c111f8"
],
"coco.zip": [
"567c5b5054552a2771eafa7966844a146f0dde96",
@@ -99850,18 +99785,6 @@
"Tortuga.dat": [
"24a487f22f3da292e179b3edd6c30222a8ff933d"
],
"Firmware.19.0.0.zip": [
"ac4b78d53c7a97da2451ca35498395d8dd1e3024"
],
"dsi_nand.bin": [
"b48f44194fe918aaaec5298861479512b581d661"
],
"PS3UPDAT.PUP": [
"093f8698b54b78dcb701de2043f82639de51d63b"
],
"PSVUPDAT.PUP": [
"cc72dfcc964577cc29112ef368c28f55277c237c"
],
"goldstar_fc1_enc.bin": [
"8ef7503c948314d242da47b7fdc272f68dac2aee"
],
@@ -100070,40 +99993,94 @@
"2ae988416d74273b0213e0be6513eabc3d974d49"
],
"c64_geocable.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"cgenie_printer.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"dmv_k210.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"fdc37c93x.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"isa_lpt.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"pc_lpt.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"pofo_hpc101.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"sv802.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"vtech_printer.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"82c606.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"bk_ay.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"bk_covox.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"bk_printer.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"cocopakram.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"distomeb_rtime.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"electron_fbprint.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"fdc37c665gt.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"fdc37m707.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"isa_bblue2.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"it8703f_device.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"it8705f.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"pc87306.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"pc97338.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"psion_parallel.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"psion_3link_par.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"spectrum_kempcentrs.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"upc82c710.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"upc82c711.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"w83977tf.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"w83787f.zip": [
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c"
],
"atom_econet.zip": [
"2d7d999e2acb4da55c26d1517934d39e7d0a0c86",
@@ -100114,16 +100091,22 @@
"eb7ff2179f102be63f217466f643bd93b4910f9e"
],
"c64_music64.zip": [
"122dea22f51ad868ca111e045d2ae6d1bec4fc91",
"1927797dd8fba39987906d8c90c8b182968783eb"
"122dea22f51ad868ca111e045d2ae6d1bec4fc91"
],
"c64_sfxse.zip": [
"122dea22f51ad868ca111e045d2ae6d1bec4fc91",
"1927797dd8fba39987906d8c90c8b182968783eb"
"122dea22f51ad868ca111e045d2ae6d1bec4fc91"
],
"c64_supercpu.zip": [
"122dea22f51ad868ca111e045d2ae6d1bec4fc91",
"1927797dd8fba39987906d8c90c8b182968783eb"
"122dea22f51ad868ca111e045d2ae6d1bec4fc91"
],
"c64_buscard.zip": [
"122dea22f51ad868ca111e045d2ae6d1bec4fc91"
],
"c64_ieee488.zip": [
"122dea22f51ad868ca111e045d2ae6d1bec4fc91"
],
"c64_buscard2.zip": [
"122dea22f51ad868ca111e045d2ae6d1bec4fc91"
],
"c64_swiftlink.zip": [
"17409b32b33cd1474b1aa1417dd3467b15589e16",
@@ -100282,24 +100265,19 @@
"da272af2fb1da8883c539b19c1bda97c5301dc80"
],
"cpc_mface2.zip": [
"d3b68c28975704af68fb2016e1fe611d10177495",
"949b9b362e1dc615a2e5783016207ff0d87de465"
"d3b68c28975704af68fb2016e1fe611d10177495"
],
"cpc_playcity.zip": [
"d3b68c28975704af68fb2016e1fe611d10177495",
"949b9b362e1dc615a2e5783016207ff0d87de465"
"d3b68c28975704af68fb2016e1fe611d10177495"
],
"cpc_ser.zip": [
"d3b68c28975704af68fb2016e1fe611d10177495",
"949b9b362e1dc615a2e5783016207ff0d87de465"
"d3b68c28975704af68fb2016e1fe611d10177495"
],
"cpc_serams.zip": [
"d3b68c28975704af68fb2016e1fe611d10177495",
"949b9b362e1dc615a2e5783016207ff0d87de465"
"d3b68c28975704af68fb2016e1fe611d10177495"
],
"cpc_ssa1.zip": [
"d3b68c28975704af68fb2016e1fe611d10177495",
"949b9b362e1dc615a2e5783016207ff0d87de465"
"d3b68c28975704af68fb2016e1fe611d10177495"
],
"d2fdc.zip": [
"af56c948598291b284a528f3fce06b961dba55e3",
@@ -100341,16 +100319,20 @@
"75de4163ec975c736f71eff860d5d656e2806333"
],
"pofo_hpc104.zip": [
"e92c1797f962812e911c85cbfd7f91169738e9ac",
"9f9ab9a092e0936758ad2fb93a537533231468c8"
"e92c1797f962812e911c85cbfd7f91169738e9ac"
],
"pofo_hpc104_2.zip": [
"e92c1797f962812e911c85cbfd7f91169738e9ac",
"9f9ab9a092e0936758ad2fb93a537533231468c8"
"e92c1797f962812e911c85cbfd7f91169738e9ac"
],
"to7_io_line.zip": [
"e92c1797f962812e911c85cbfd7f91169738e9ac"
],
"psion_serpar.zip": [
"e92c1797f962812e911c85cbfd7f91169738e9ac"
],
"psion_siena_ssd.zip": [
"e92c1797f962812e911c85cbfd7f91169738e9ac"
],
"isa_hdc_ec1841.zip": [
"2561d3c19ab5fcf397a46af37cff1097555a7464"
],
@@ -100378,22 +100360,20 @@
],
"peribox_genmod.zip": [
"827512b74c2d90c34a992036fb974ddd4e9454bd",
"e037089e7f53382950a6e8d3939af2cae5d4a7d5"
"b58fe2dbbc254d363c2ec4a459e6ec1d91b2ac86"
],
"peribox_sg.zip": [
"0a6aa44fe3d86af0ecbf428b49fd8053e4da8e11",
"e037089e7f53382950a6e8d3939af2cae5d4a7d5"
"b58fe2dbbc254d363c2ec4a459e6ec1d91b2ac86"
],
"popn9.zip": [
"207e34befb36e945bbbaf9012156b70a1be97819"
],
"ql_sqboard512.zip": [
"ce84f25384217a1280f1fddbf8919967d6ddca14",
"7e37e3dae0c4ddff1912bf02fca3fb2019326b78"
"ce84f25384217a1280f1fddbf8919967d6ddca14"
],
"ql_sqmouse512.zip": [
"ce84f25384217a1280f1fddbf8919967d6ddca14",
"7e37e3dae0c4ddff1912bf02fca3fb2019326b78"
"ce84f25384217a1280f1fddbf8919967d6ddca14"
],
"ql_trump256.zip": [
"c34662ee1d51ae0ae2899f923694f1d8024559fa"
@@ -100414,16 +100394,88 @@
"634008f34b031c0e5d0186936c065e6e20808264"
],
"spectrum_intf1.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3",
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_melodik.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3",
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_uslot.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3",
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_betacbi.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_betaplus.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_betav3.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_betav2.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_beta128.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_betaclone.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_disciple.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_gamma.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_flpone.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_kempdisc.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mface1.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mface128v1.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mface128.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mface1v1.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mface1v2.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mface1v3.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_lprint3.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_mprint.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_speccydos.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_spdos.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_specmate.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_swiftdisc2.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_vtx5000.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_swiftdisc.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"spectrum_wafa.zip": [
"0cb430aea9354d8c151933a12c33fa2bb69c16d3"
],
"sv602.zip": [
"befce08c839e414535478e4efd44e1b30dbec598",
@@ -100603,6 +100655,12 @@
"kvshared.wav": [
"9adf10cdf1de833b194c7d8797ad1f041ad98dd3"
],
"atari_osb.rom": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"atari_os_b.rom": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"ROM_BASIC_C": [
"3693c9cb9bf3b41bae1150f7a8264992468fc8c0"
],
@@ -102325,9 +102383,6 @@
"custom0.sf2": [
"286b2e1fb21cc79851da01666db6c0b0e88f25e3"
],
"Custom.dat": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
],
"tos106de.img": [
"3b8cf5ffa41b252eb67f8824f94608fa4005d6dd"
],
@@ -102532,75 +102587,15 @@
"MSX2R2.ROM": [
"04990aa1c3a3fc7294ec884b81deaa89832df614"
],
"NATIONALDISK.rom": [
"78cd7f847e77fd8cd51a647efb2725ba93f4c471"
],
"PHILIPSDISK.rom": [
"c3efedda7ab947a06d9345f7b8261076fa7ceeef"
],
"flash.bin": [
"94d44d7f9529ec1642ba3771ed3c5f756d5bc872"
],
"82c606.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"bk_ay.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"bk_covox.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"bk_printer.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"cocopakram.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"distomeb_rtime.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"electron_fbprint.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"fdc37c665gt.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"fdc37m707.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"isa_bblue2.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"it8703f_device.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"it8705f.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"pc87306.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"pc97338.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"psion_parallel.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"psion_3link_par.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"spectrum_kempcentrs.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"upc82c710.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"upc82c711.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"w83977tf.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"w83787f.zip": [
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea"
],
"a2bufgrapplerplusa.zip": [
"7c54fb94853478d23ec155a8e38b76d830f52e46"
],
@@ -102838,15 +102833,6 @@
"bbc_usersplit.zip": [
"90f1c144839c5269ff8567dd6685ae356027f146"
],
"c64_buscard.zip": [
"1927797dd8fba39987906d8c90c8b182968783eb"
],
"c64_ieee488.zip": [
"1927797dd8fba39987906d8c90c8b182968783eb"
],
"c64_buscard2.zip": [
"1927797dd8fba39987906d8c90c8b182968783eb"
],
"centronics_chessmec.zip": [
"e908b954f3c4b25f59de1e17e51cbd020540c243"
],
@@ -103034,19 +103020,13 @@
"eaabc1ef9448c297ef7cbae90278b265acd48169"
],
"peribox_ev1.zip": [
"e037089e7f53382950a6e8d3939af2cae5d4a7d5"
"b58fe2dbbc254d363c2ec4a459e6ec1d91b2ac86"
],
"ti99_iosplit.zip": [
"e037089e7f53382950a6e8d3939af2cae5d4a7d5"
"b58fe2dbbc254d363c2ec4a459e6ec1d91b2ac86"
],
"ti99_speechconn.zip": [
"e037089e7f53382950a6e8d3939af2cae5d4a7d5"
],
"psion_serpar.zip": [
"9f9ab9a092e0936758ad2fb93a537533231468c8"
],
"psion_siena_ssd.zip": [
"9f9ab9a092e0936758ad2fb93a537533231468c8"
"b58fe2dbbc254d363c2ec4a459e6ec1d91b2ac86"
],
"profighterqb.zip": [
"631ccd946400978b10ee225e008eed199027fd8c"
@@ -103075,81 +103055,6 @@
"sis85c496_host.zip": [
"7d3363b91d27ac3ff9fb91aee36d798a5331e2be"
],
"spectrum_betacbi.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_betaplus.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_betav3.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_betav2.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_beta128.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_betaclone.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_disciple.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_gamma.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_flpone.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_kempdisc.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mface1.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mface128v1.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mface128.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mface1v1.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mface1v2.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mface1v3.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_lprint3.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_mprint.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_speccydos.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_spdos.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_specmate.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_swiftdisc2.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_vtx5000.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_swiftdisc.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_wafa.zip": [
"8aaa6099ccaa133cb8f54846f65348dff3851c19"
],
"spectrum_d80.zip": [
"28fce4b27babe26a6bf5d139df489302764c5ad4"
],
@@ -103373,6 +103278,9 @@
"MCX_Basic_21_AZERTY": [
"c8fd92705fc42deb6a0ffac6274e27fd61ecd4cc"
],
"OPENBIOS.bin": [
"389df7981873d9e6e46c84c20cd43af0e4226cf8"
],
"N88_0.ROM": [
"d1ae642aed4f0584eeb81ff50180db694e5101d4"
],
@@ -103433,12 +103341,6 @@
"EXDOS14.ROM": [
"cb43ab3676b93c279f1ed8ffcb0d4dcd4b34e631"
],
"atari_osb.rom": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"atari_os_b.rom": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"FONT.BMP": [
"b4f14e58030ed40fff2dc312b58ea4440bdf8cc5"
],
@@ -107023,6 +106925,7 @@
"5ad03407": "c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c",
"549f1d90": "f1525de4e0b60a6687156c2a96f8a8b2044b6c56",
"5bf38e13": "44e0dd215b2a9f0770dd76fb49187c05b083eed9",
"1e7d6512": "78cd7f847e77fd8cd51a647efb2725ba93f4c471",
"b2db6bf5": "3a9a942ed888dd641cddf8deada1879c454df3c6",
"956dc96d": "9ed3ab6d893632b9246e91b412cd5db519e7586b",
"3c42c367": "4be8371f3b03e70ddaca495958345f3c4f8e2d36",
@@ -107108,7 +107011,6 @@
"456d9fc7": "78ba9960f135372825ab7244b5e4e73a810002ff",
"d402842f": "0877ffb4b4d1c18283468be3579b72ed8c22e3ac",
"c1815325": "a2fb11c000ed7c976520622cfb7940ed6ddc904e",
"00000000": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"ffd68be0": "3518193b8207bdebf22c1380c2db8c554baff329",
"61984bab": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
"7ad5d943": "4ae4d37409ff99411a623da9f6a44192170a854e",
@@ -110733,13 +110635,7 @@
"4a39a474": "d07114a9f3490338a265fb30d16b052c8da3bb7d",
"bf1e4b9b": "b3730071e789877bea3373ffa59ca673a4b1f4c9",
"e6b9ebfb": "48024e2f5943ed86cb1b8e9443603991cdb05808",
"a42ef0fd": "24a487f22f3da292e179b3edd6c30222a8ff933d",
"77228c84": "ac4b78d53c7a97da2451ca35498395d8dd1e3024",
"81f21918": "add40c002084e8e25768671877b2aa603aaf5cb1",
"416bf51a": "b48f44194fe918aaaec5298861479512b581d661",
"24bdb2db": "093f8698b54b78dcb701de2043f82639de51d63b",
"082ecf86": "ed3a4cb264fff283209f10ae58c96c6090fed187",
"39075d41": "cc72dfcc964577cc29112ef368c28f55277c237c"
"a42ef0fd": "24a487f22f3da292e179b3edd6c30222a8ff933d"
},
"by_path_suffix": {
".variants/aa310.zip": [
@@ -113916,9 +113812,6 @@
".variants/1540OS3.V0": [
"454fdd9005c43142f827e1e506bf75fda0fe2a65"
],
".variants/ATARIOSB.ROM.0e86d61d": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"hatari/BOOT.ST": [
"5bcabba35bb8fbfe5a65b85efccf5ed657388308"
],
@@ -115347,6 +115240,9 @@
"share/systemroms/fs-5500_basic-bios2.rom": [
"44e0dd215b2a9f0770dd76fb49187c05b083eed9"
],
"share/systemroms/fs-5500_disk.rom": [
"78cd7f847e77fd8cd51a647efb2725ba93f4c471"
],
"share/systemroms/fs-5500_kanjibasic.rom": [
"3a9a942ed888dd641cddf8deada1879c454df3c6"
],
@@ -124358,20 +124254,26 @@
"e35eda0cc2c11da92c0a6c222f314d84e623b29e",
"9ecb4cae3fe19cd3faef4a22fe5d3a189ac8810c",
"3a5718ea19c8e4c900a77bfd6bee701597feaa56",
"bbf1cb3e776129524d54d1f885c54d0f1b8d489c",
"122dea22f51ad868ca111e045d2ae6d1bec4fc91",
"17409b32b33cd1474b1aa1417dd3467b15589e16",
"9ae8707d814dc197315fb1d49571209e48ab29f3",
"893deaeff7ac79fbde19817678b63905d9f9b9cb",
"da272af2fb1da8883c539b19c1bda97c5301dc80",
"d3b68c28975704af68fb2016e1fe611d10177495",
"af56c948598291b284a528f3fce06b961dba55e3",
"d375f64dc02703eee6751bd9723978c6633348c9",
"eda0107f44a9a5b15471aea99e847701f4899a96",
"e92c1797f962812e911c85cbfd7f91169738e9ac",
"87ecc7a33627b1fac62ffb87e79a5aa36fe746cf",
"4fc37b52d4313ff57f5557c2c3191e885b0e3fcb",
"4d217c4a72a4450b75faaf7da871e8fe7b64ff60",
"ce84f25384217a1280f1fddbf8919967d6ddca14",
"c34662ee1d51ae0ae2899f923694f1d8024559fa",
"a0e6f65f2eca69c22c8d405af11c5d9c262efa1d",
"b06ad209ef30db63711cc5bc46a5371465223ed0",
"634008f34b031c0e5d0186936c065e6e20808264",
"0cb430aea9354d8c151933a12c33fa2bb69c16d3",
"9139c00e5986b7a86c7e8dcbce39986126ab7db4",
"14a5ff2c8ca68d35a77ebfbc227ed9c8e19d7524",
"c4d0f820dbfa135db40ce94c2253cae7814cf2f2",
@@ -124399,6 +124301,7 @@
"dc594fea200ce87d1bd33a4a1e73f0221b68b9eb",
"afcc9c605420a2029b66e97e9f719f15ebb53d64",
"82aeaace3231022fc158e96c9042745f6482aa93",
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b",
"3693c9cb9bf3b41bae1150f7a8264992468fc8c0",
"d9d134bb6b36907c615a594cc7688f7bfcef5b43",
"5a140136a16d1d83e4ff32a19409ca376a8df874",
@@ -124635,10 +124538,10 @@
"e90f80a61d94c617850c415e12ad70ac41e66bb7",
"df48902f5f12af8867ae1a87f255145f0e5e0774",
"04990aa1c3a3fc7294ec884b81deaa89832df614",
"78cd7f847e77fd8cd51a647efb2725ba93f4c471",
"c3efedda7ab947a06d9345f7b8261076fa7ceeef",
"94d44d7f9529ec1642ba3771ed3c5f756d5bc872",
"f119506eaa3b4b70b9aa0dd83761e8cbe043d042",
"b22847ad1d8c5ee0fff41b3bd31aab6cbf8778ea",
"7c54fb94853478d23ec155a8e38b76d830f52e46",
"1a1826d7962e6bf6b94fc1bc2b17b8633eaaa0f5",
"270c84df833b196e214738bbe158f4ea2203c272",
@@ -124653,12 +124556,10 @@
"67b9199bc22c6fe2b3a4725ec291c158edd01151",
"b1abcb49eded4fcafa76198b5f7efc65fcc940e6",
"90f1c144839c5269ff8567dd6685ae356027f146",
"1927797dd8fba39987906d8c90c8b182968783eb",
"275b6409633ffaf3c3fa5e191de9aa7294b8cf64",
"0013eb127aa3af9dee5a881c590af5ebda0edef5",
"e908b954f3c4b25f59de1e17e51cbd020540c243",
"2ed5c4f7350701a4eb279f7111e22f12afbf51b4",
"949b9b362e1dc615a2e5783016207ff0d87de465",
"9d73e22ee590cec56a8a1f0af0735b586f50602f",
"e9aee50fcf87177493095237dc2446d67fe57252",
"d5a80dd0b4ef0d22b533a3771f374dc446b63e11",
@@ -124685,17 +124586,14 @@
"18889ed623d8c635966de0947076066f3732d2aa",
"06fc753d015b43ca1787f4cfd9331b1674202e64",
"eaabc1ef9448c297ef7cbae90278b265acd48169",
"e037089e7f53382950a6e8d3939af2cae5d4a7d5",
"9f9ab9a092e0936758ad2fb93a537533231468c8",
"b58fe2dbbc254d363c2ec4a459e6ec1d91b2ac86",
"631ccd946400978b10ee225e008eed199027fd8c",
"7e37e3dae0c4ddff1912bf02fca3fb2019326b78",
"bf946b98e9314ac877e67af84c82418604f4bade",
"bfa36e6517ba0744618e6778581beb85067be171",
"134760c0544d633cb475ad58b87f92483826cb2a",
"b8b8f5030a64769d27b784aeb5efca94cd72149a",
"b32303333a66101a83d008af1a8c1966751ed156",
"7d3363b91d27ac3ff9fb91aee36d798a5331e2be",
"8aaa6099ccaa133cb8f54846f65348dff3851c19",
"28fce4b27babe26a6bf5d139df489302764c5ad4",
"d9d692484446ac37e992478d60d9478bfe1fd9a6",
"423b488154c36a97a160cae7d43e79089ddee16d",
@@ -124749,6 +124647,7 @@
"9513091f37adf330f66a0c08f4e200344ad2e082",
"36c30d0f198a1bffee88ef29d92f2401447a91f4",
"c8fd92705fc42deb6a0ffac6274e27fd61ecd4cc",
"389df7981873d9e6e46c84c20cd43af0e4226cf8",
"d1ae642aed4f0584eeb81ff50180db694e5101d4",
"4ae4d37409ff99411a623da9f6a44192170a854e",
"e94278682ef9e9bbb82201f72c50382748dcea2a",
@@ -124764,7 +124663,6 @@
"05ba161aa8796c04f227eb2d52496943ada814f2",
"388e3721b94cd074d6ba0eca8616523d2118a6c3",
"cb43ab3676b93c279f1ed8ffcb0d4dcd4b34e631",
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b",
"b4f14e58030ed40fff2dc312b58ea4440bdf8cc5",
"c65592330c9dd84011151daed52f9aec926b7e56",
"0877ffb4b4d1c18283468be3579b72ed8c22e3ac",
@@ -124791,6 +124689,9 @@
"samples/samesame.zip": [
"cdb09d3ffaa867ff9e7387cf1934e9011565d546"
],
".variants/ATARIOSB.ROM.0e86d61d": [
"db1031585968cfc6ec2ecda5c9a5a52f61444a3b"
],
"openmsx/COLECO.ROM": [
"45bedc4cbdeac66c7df59e9e599195c778d86a92"
],
@@ -127206,9 +127107,6 @@
"zc210/sf2/custom0.sf2": [
"286b2e1fb21cc79851da01666db6c0b0e88f25e3"
],
"zc210/sfx/Custom.dat": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
],
"zc210/zcdata.dat": [
"33b50cec88bc1569431a7885c0cc2692fddb004b"
]

View File

@@ -26,6 +26,7 @@ systems:
- mega-cd
- mega-cd-32x
- mega-ld
- laseractive
- msx
- msx2
- neo-geo

View File

@@ -7,7 +7,7 @@ profiled_date: "2026-03-23"
core_version: "3.1.0"
display_name: "Atari - 400/800/600XL/800XL/130XE/5200 (Atari800)"
cores: [atari800]
systems: [atari-400, atari-800, atari-800xl, atari-130xe, atari-5200, atari-xegs]
systems: [atari-400-800, atari-5200]
# Atari800 emulates the Atari 8-bit family (400/800/XL/XE) and the 5200 console.
# All BIOS files are optional -- the core ships built-in Altirra OS replacements
@@ -43,7 +43,7 @@ files:
note: >
Atari 5200 BIOS (original). Mapped at $F800-$FFFF.
Altirra 5200 OS is used when this file is absent.
source_ref: "atari800/src/sysrom.c:101-102,254-255"
source_ref: "atari800/src/sysrom.c:101-102,255"
# -- Atari BASIC ROM --
# Atari BASIC interpreter (8 KB). Three known revisions:
@@ -63,7 +63,7 @@ files:
note: >
Atari BASIC Rev C. Required for 400/800 software that needs BASIC.
Altirra BASIC is used as fallback. Enable via core option atari800_opt2.
source_ref: "atari800/src/sysrom.c:103-105,256"
source_ref: "atari800/src/sysrom.c:103-105,257"
# -- Atari 400/800 OS A --
# Original Atari 400/800 OS (10 KB). Two known CRC32 values:

View File

@@ -3,18 +3,19 @@ type: libretro
core_classification: community_fork
source: "https://github.com/libretro/beetle-psx-libretro"
upstream: "https://mednafen.github.io/"
profiled_date: "2026-03-24"
profiled_date: "2026-03-29"
core_version: "v0.9.44.1"
display_name: "Sony - PlayStation (Beetle PSX)"
cores: [mednafen_psx, mednafen_psx_hw]
cores: [beetle_psx, mednafen_psx, mednafen_psx_hw]
systems: [sony-playstation]
notes: >
Region-based BIOS selection: the core picks JP/NA/EU firmware based on disc region.
SHA1 validated with warning on mismatch (does not block loading).
SHA1 and alternate filenames sourced from MAME psx.cpp (libretro.cpp:184).
Override option allows using PSP or PS3 extracted PS1 BIOS as region-free alternative.
SHA1 and alternate filenames sourced from MAME psx.cpp (libretro.cpp:186).
Override option allows using PSP, PS3, or OpenBIOS as region-free alternative.
Embedded OpenBIOS (512KB) compiled into the binary serves as HLE fallback when no BIOS found.
"Skip BIOS" option patches BIOS ROM but causes compatibility issues.
Upstream Mednafen loads the same 3 region BIOS; override BIOS are libretro additions.
Upstream Mednafen loads the same 3 region BIOS; override BIOS and HLE fallback are libretro additions.
files:
# -- Region: Japan (REGION_JP) --
@@ -23,12 +24,13 @@ files:
description: "SCPH-5500 (v3.0 09-09-96 J)"
region: "NTSC-J"
required: true
hle_fallback: true
size: 524288
sha1: "b05def971d8ec59f346f2d9ac21fb742e3eb6917"
md5: "8dd7d5296a650fac7319bce665a6a53c"
validation: [sha1]
mode: both
source_ref: "libretro.cpp:252-256"
source_ref: "libretro.cpp:261-269"
aliases: ["SCPH5500.bin", "SCPH-5500.bin"]
# -- Region: North America (REGION_NA) --
@@ -37,12 +39,13 @@ files:
description: "SCPH-5501, 5503, 7003 (v3.0 11-18-96 A)"
region: "NTSC-U"
required: true
hle_fallback: true
size: 524288
sha1: "0555c6fae8906f3f09baf5988f00e55f88e9f30b"
md5: "490f666e1afb15b7362b406ed1cea246"
validation: [sha1]
mode: both
source_ref: "libretro.cpp:258-270"
source_ref: "libretro.cpp:271-289"
aliases:
- "SCPH5501.bin"
- "SCPH-5501.bin"
@@ -59,12 +62,13 @@ files:
description: "SCPH-5502, 5552 (v3.0 01-06-97 E)"
region: "PAL"
required: true
hle_fallback: true
size: 524288
sha1: "f6bc2d1f5eb6593de7d089c425ac681d6fffd3f0"
md5: "32736f17079d0b2b7024407c39bd3050"
validation: [sha1]
mode: both
source_ref: "libretro.cpp:272-282"
source_ref: "libretro.cpp:291-305"
aliases:
- "SCPH5502.bin"
- "SCPH-5502.bin"
@@ -83,7 +87,7 @@ files:
md5: "c53ca5908936d412331790f4426c6c33"
validation: [sha1]
mode: libretro
source_ref: "libretro.cpp:190-195"
source_ref: "libretro.cpp:192-198"
aliases: ["PSXONPSP660.bin"]
note: "override_bios=1. Falls back to region BIOS if not found."
@@ -96,6 +100,16 @@ files:
md5: "81bbe60ba7a3d1cea1d48c14cbcc647b"
validation: [sha1]
mode: libretro
source_ref: "libretro.cpp:198-203"
source_ref: "libretro.cpp:200-206"
aliases: ["PS1_ROM.bin"]
note: "override_bios=2. Falls back to region BIOS if not found."
- name: "openbios.bin"
description: "OpenBIOS (open-source PS1 HLE BIOS, region-free override)"
region: "Auto"
required: false
size: 524288
mode: libretro
source_ref: "libretro.cpp:208-214"
aliases: ["OPENBIOS.bin"]
note: "override_bios=3. No SHA1 validation. Falls back to region BIOS if not found. Also embedded in binary as HLE fallback (libretro.cpp:2157-2159)."

View File

@@ -7,10 +7,10 @@ profiled_date: "2026-03-23"
core_version: "v1.0"
display_name: "Dinothawr"
cores: [dinothawr]
systems: []
systems: [dinothawr]
notes: |
Puzzle game by the libretro team. Push blocks on ice.
Puzzle game by the libretro team (Themaister, Agnes Heyer). Push blocks on ice.
Game data (TMX levels, sprites, music) needed for the core to work.
Two loading modes:

View File

@@ -1,14 +1,16 @@
emulator: GSplus
type: standalone
core_classification: community_fork
source: "https://github.com/digarok/gsplus"
upstream: "https://github.com/digarok/gsplus"
profiled_date: "2026-03-26"
upstream: "https://kegs.sourceforge.net/"
profiled_date: "2026-03-29"
core_version: "KEGS 1.38"
display_name: "Apple - Apple IIGS (GSplus)"
cores:
- gsplus
systems:
- apple-iigs
- apple-iie
notes: |
Fork of KEGS (Kent's Emulated GS) by digarok. Cross-platform Apple IIGS
@@ -28,7 +30,7 @@ files:
aliases: [ROM.01, ROM.03, APPLE2GS.ROM, APPLE2GS.ROM2, xgs.rom, XGS.ROM, Rom03gd, 342-0077-b]
required: true
description: "Apple IIGS system ROM"
source_ref: "config.c:131,403-407,1092-1149"
source_ref: "config.c:131,403-406,1092-1149"
note: "Accepted sizes: 32768 (Apple //e), 131072 (ROM 01), 262144 (ROM 03)"
validation: [size]
@@ -39,5 +41,5 @@ files:
size: 256
description: "Disk II controller PROM (slot 6)"
validation: [size]
source_ref: "config.c:414-415,1188-1220"
source_ref: "config.c:414-415,1168-1220"
note: "Built-in PROM generated from XOR diffs against main ROM"

View File

@@ -10,7 +10,7 @@ upstream: "https://github.com/PCSX2/pcsx2"
profiled_date: "2026-03-25"
core_version: "Git"
display_name: "Sony - PlayStation 2 (LRPS2)"
cores: [lrps2]
cores: [lrps2, pcsx2]
systems: [sony-playstation-2]
bios_directory: "pcsx2/bios/"

View File

@@ -4,7 +4,7 @@ core_classification: official_port
source: "https://github.com/libretro/mame"
upstream: "https://github.com/mamedev/mame"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-03-22"
profiled_date: "2026-03-29"
core_version: "0.286"
display_name: "Arcade (MAME)"
@@ -43,13 +43,36 @@ systems:
- konami-twinkle
- panasonic-3do
- hyper-neogeo64
- apple2gs
- apf-m1000
- bally-astrocade
- coleco-adam
- crvision
- entex-advision
- sega-ai
- sega-beena
- bandai-rx78
- camplynx
- tiger-game-com
- gamepark-gp32
- gamate
- gamepock
- hartung-game-master
- fm7
- laser310
- vtech-socrates
- casio-loopy
- casio-pv1000
- casio-pv2000
- pegasus
- pcw
- interton-vc4000
- ti99
- trs80
notes: |
Rolling release tracking mamedev/mame upstream (currently 0.286).
80 BIOS root sets + 3 system ROM sets (adam, advision, apfm1000). romload.cpp
80 BIOS root sets + 24 system ROM sets (apple2gs, astrocade, adam, advision, apfm1000, beena, camplynx, casloopy, crvision, fm7, gamate, gamecom, gamepock, gmaster, gp32, laser310, pcw, pegasus, pv2000, rx78, segaai, socrates, ti99_4a, trs80). romload.cpp
unmodified. No hiscore support.
Paths under system_dir/mame/ (artwork, cheat, hash, ini, plugins,
samples, crosshair). ROM search: content_dir + system_dir/mame/bios +
@@ -59,6 +82,16 @@ notes: |
renamed from legacy names (cpzn1 → coh1000c, atpsx → coh1000a, etc.).
File structure uses vendor-based paths (src/mame/{vendor}/).
TI-99/4A: 6 machines in ti99_4x.cpp — ti99_4 parent (TI-99/4 1979),
ti99_4e clone (Europe), ti99_4a parent (TI-99/4A 1981), ti99_4ae clone
(Europe), ti99_4qi clone (QI version 1983), ti99_4ev clone (with EVPC
1994). 5 ROMs per parent set: 3 GROMs (u500-u502) + 2 CPU ROMs
(u610-u611, 16-bit interleaved). Speech Synthesizer sidecar device
(speechsyn.cpp) with 2 TMS6100 vocabulary ROMs. Software list
"ti99_cart". Peripheral Expansion Box with optional device ROMs (FDC,
RS232, HFDC, P-Code, EVPC, etc.) — all PEB devices require user
configuration, none loaded by default.
files:
# SNK Neo Geo MVS/AES
- name: neogeo.zip
@@ -515,6 +548,92 @@ files:
category: bios_zip
source_ref: "src/mame/misc/xtom3d.cpp:996"
# Bally Astrocade
- name: astrocde.zip
required: true
category: bios_zip
system: astrocde
source_ref: "src/mame/midway/astrohome.cpp:244-247"
contents:
- name: astro.bin
description: "On-board BIOS ROM (Bally Professional Arcade)"
size: 8192
crc32: ebc77f3a
- name: astrocdl.zip
required: false
category: bios_zip
system: astrocde
source_ref: "src/mame/midway/astrohome.cpp:249-251"
contents:
- name: ballyhlc.bin
description: "Bally Home Library Computer BIOS"
size: 8192
crc32: d7c517ba
- name: astrocdw.zip
required: false
category: bios_zip
system: astrocde
source_ref: "src/mame/midway/astrohome.cpp:254-257"
contents:
- name: bioswhit.bin
description: "Bally Computer System BIOS"
size: 8192
crc32: 6eb53e79
# Apple IIgs
- name: apple2gs.zip
required: true
category: bios_zip
system: apple2gs
source_ref: "src/mame/apple/apple2gs.cpp:3898-3912"
contents:
- name: 341s0632-2.bin
description: "ADB microcontroller ROM (M50740/50741)"
size: 4096
crc32: e1c11fb0
- name: 344s0047.bin
description: "Mega II character ROM"
size: 16384
crc32: 2d541944
- name: 341-0728
description: "IIgs ROM03 FC-FD main CPU"
size: 131072
crc32: 8d410067
- name: 341-0748
description: "IIgs ROM03 FE-FF main CPU"
size: 131072
crc32: "18190283"
# Casio Loopy
- name: casloopy.zip
required: true
category: bios_zip
system: casio-loopy
source_ref: "src/mame/casio/casloopy.cpp:2456-2462"
contents:
- name: hd6437021.lsi302
description: "SH-1 CPU internal mask ROM"
size: 32768
crc32: 8c57ff9f
- name: hn62434fa.lsi352
description: "HN62434 sound/wave data mask ROM"
size: 524288
crc32: 8f51fa17
# Casio PV-2000
- name: pv2000.zip
required: true
category: bios_zip
system: casio-pv2000
source_ref: "src/mame/casio/pv2000.cpp:416-419"
contents:
- name: hn613128pc64.bin
description: "Z80 BASIC ROM"
size: 16384
crc32: 8f31f297
# Coleco Adam
- name: adam.zip
required: true
@@ -661,3 +780,794 @@ files:
size: 4096
crc32: f320aba6
# Sega Advanced Pico BEENA
- name: beena.zip
required: true
category: bios_zip
system: sega-beena
source_ref: "src/mame/sega/sega_beena.cpp:2233-2245"
contents:
- name: 9h0-0008.bios.ic1
description: "SoC internal BIOS (ARM7, dumped via JTAG)"
size: 131072
crc32: 5471aaf8
- name: 9h0-0008.midipcm.ic1
description: "SoC MIDI synthesizer PCM data"
size: 32768
crc32: ed336d29
# Bandai RX-78
- name: rx78.zip
required: true
category: bios_zip
system: bandai-rx78
source_ref: "src/mame/bandai/rx78.cpp:559"
note: "MACHINE_NOT_WORKING."
contents:
- name: ipl.rom
description: "IPL boot ROM"
size: 8192
crc32: a194ea53
# Sega AI Computer
- name: segaai.zip
required: true
category: bios_zip
system: sega-ai
source_ref: "src/mame/sega/segaai.cpp:730-739"
contents:
- name: mpr-7689.ic5
description: "OS ROM with SEGA PROLOG (128KB)"
size: 131072
crc32: 62402ac9
- name: e000 8_24.ic3
description: "EPROM bank E000"
size: 65536
crc32: c8b6a539
- name: f000 7_21.ic4
description: "EPROM bank F000"
size: 65536
crc32: 64d6cd8c
- name: mpr-7619.ic14
description: "UPD7759 speech ROM bank 0"
size: 131072
crc32: d1aea002
- name: mpr-7620.ic15
description: "UPD7759 speech ROM bank 1"
size: 131072
crc32: e042754b
- name: segaai_soundbox.zip
required: false
category: bios_zip
system: sega-ai
source_ref: "src/devices/bus/segaai/soundbox.cpp:152-155"
note: "Sound Box expansion (AI-2002) with YM2151 FM + music keyboard"
contents:
- name: ai-snd-2002-cecb.bin
description: "Sound Box expansion ROM"
size: 65536
crc32: ef2dabc0
# Camputers Lynx 48K
- name: lynx48k.zip
required: true
category: bios_zip
system: camplynx
source_ref: "src/mame/camputers/camplynx.cpp:995-1004"
contents:
- name: lynx48-1.ic46
description: "BASIC ROM bank 1 (Set1)"
size: 8192
crc32: 56feec44
- name: lynx48-2.ic45
description: "BASIC ROM bank 2 (Set1)"
size: 8192
crc32: d894562e
- name: lynx4811.ic46
description: "BASIC ROM bank 1 (Set2)"
size: 8192
crc32: a933e577
- name: lynx4812.ic45
description: "BASIC ROM bank 2 (Set2)"
size: 8192
crc32: 3d3fdd0e
# Camputers Lynx 96K
- name: lynx96k.zip
required: true
category: bios_zip
system: camplynx
source_ref: "src/mame/camputers/camplynx.cpp:1006-1018"
contents:
- name: lynx9646.ic46
description: "BASIC ROM bank 1"
size: 8192
crc32: f86c5514
- name: lynx9645.ic45
description: "BASIC ROM bank 2"
size: 8192
crc32: f596b9a3
- name: lynx9644.ic44
description: "Extension ROM (original)"
size: 4096
crc32: 4b96b0de
- name: skorprom.ic44
description: "Scorpion ROM v2.1 (RLUG)"
size: 8192
crc32: 698d3de9
- name: danish96k3.ic44
description: "Danish extension ROM"
size: 8192
crc32: 795c22ea
- name: dosrom.rom
description: "Floppy DOS ROM"
size: 8192
crc32: 011e106a
# Camputers Lynx 128K
- name: lynx128k.zip
required: true
category: bios_zip
system: camplynx
source_ref: "src/mame/camputers/camplynx.cpp:1020-1026"
contents:
- name: lynx128-1.ic1
description: "BASIC ROM bank 1"
size: 8192
crc32: 65d292ce
- name: lynx128-2.ic2
description: "BASIC ROM bank 2"
size: 8192
crc32: 23288773
- name: lynx128-3.ic3
description: "BASIC ROM bank 3"
size: 8192
crc32: 9827b9e9
- name: dosrom.rom
description: "Floppy DOS ROM"
size: 8192
crc32: 011e106a
# VTech CreatiVision
- name: crvision.zip
required: true
category: bios_zip
system: crvision
source_ref: "src/mame/vtech/crvision.cpp:934-937"
contents:
- name: crvision.u20
description: "Microsoft BASIC ROM"
size: 2048
crc32: c3c590c6
- name: fnvision.zip
required: false
category: bios_zip
system: crvision
source_ref: "src/mame/vtech/crvision.cpp:939-942"
note: "FunVision clone with alternate BIOS"
contents:
- name: funboot.rom
description: "FunVision alternate BIOS ROM"
size: 2048
crc32: "05602697"
- name: lasr2001.zip
required: false
category: bios_zip
system: crvision
source_ref: "src/mame/vtech/crvision.cpp:950-953"
note: "VTech Laser 2001 home computer (CreatiVision successor)"
contents:
- name: laser2001.rom
description: "Laser 2001 BASIC + OS ROM"
size: 16384
crc32: 4dc35c39
- name: manager.zip
required: false
category: bios_zip
system: crvision
source_ref: "src/mame/vtech/crvision.cpp:955-959"
note: "Salora Manager (Finnish variant)"
contents:
- name: "01"
description: "ROM bank 0-1"
size: 8192
crc32: 702f4cf5
- name: "23"
description: "ROM bank 2-3"
size: 8192
crc32: 46489d88
# VTech Laser 310 (VZ-300)
- name: laser310.zip
required: true
category: bios_zip
system: laser310
source_ref: "src/mame/vtech/vtech1.cpp:602-608"
note: "Z80-based home computer (1984). Clones: VZ-300, Laser 310 SHRG."
contents:
- name: vtechv20.u12
description: "BASIC V2.0 ROM (default)"
size: 16384
crc32: 613de12c
- name: vtechv21.u12
description: "BASIC V2.1 ROM (hack)"
size: 16384
crc32: f7df980f
# VTech Socrates Educational Video System
- name: socrates.zip
required: true
category: bios_zip
system: vtech-socrates
source_ref: "src/mame/vtech/socrates.cpp:1546-1596"
note: "MACHINE_NOT_WORKING | MACHINE_IMPERFECT_SOUND. Clones: socratfc (French Canadian), profweis (German PAL)."
contents:
- name: 27-00817-000-000.u1
description: "Main CPU ROM (256KB)"
size: 262144
crc32: 80f5aa20
- name: speech_eng_internal.bin
description: "TC8802AF speech chip internal data (optional, English)"
size: 8192
crc32: edc1fb3f
- name: speech_eng_vsm1.bin
description: "T6684F VSM serial ROM 1 (optional, English)"
size: 16384
crc32: 888e3ddd
- name: speech_eng_vsm2.bin
description: "T6684F VSM serial ROM 2 (optional, English)"
size: 16384
crc32: de4ac89d
- name: speech_eng_vsm3.bin
description: "T6684F VSM serial ROM 3 (optional, English)"
size: 16384
crc32: 972384aa
# Bit Corporation Gamate
- name: gamate.zip
required: true
category: bios_zip
system: gamate
source_ref: "src/mame/bitcorp/gamate.cpp:228-234"
contents:
- name: gamate_bios_umc.bin
description: "UMC/NCR ICASC00002 BIOS (default)"
size: 4096
crc32: "07090415"
- name: gamate_bios_bit.bin
description: "BIT ICASC00001 BIOS (1994)"
size: 4096
crc32: 03a5f3a7
# Epoch Game Pocket Computer
- name: gamepock.zip
required: true
category: bios_zip
system: gamepock
source_ref: "src/mame/epoch/gamepock.cpp:248-251"
contents:
- name: egpcboot.bin
description: "NEC uPD78C06AG internal ROM"
size: 4096
crc32: ee1ea65d
# Hartung Game Master
- name: gmaster.zip
required: true
category: bios_zip
system: hartung-game-master
source_ref: "src/mame/handheld/gmaster.cpp:261-263"
contents:
- name: d78c11agf_e19.u1
description: "NEC D78C11AGF internal ROM"
size: 4096
crc32: 05cc45e5
# Tiger game.com
- name: gamecom.zip
required: true
category: bios_zip
system: tiger-game-com
source_ref: "src/mame/tiger/gamecom.cpp:293-299"
contents:
- name: internal.bin
description: "SM8521 CPU internal ROM"
size: 4096
crc32: a0cec361
- name: external.bin
description: "External flash ROM (PDA software)"
size: 262144
crc32: e235a589
# GamePark GP32
- name: gp32.zip
required: true
category: bios_zip
system: gamepark-gp32
source_ref: "src/mame/gamepark/gp32.cpp:1710-1727"
contents:
- name: gp32157e.bin
description: "Firmware 1.5.7 (English, default)"
size: 524288
crc32: b1e35643
- name: gp32100k.bin
description: "Firmware 1.0.0 (Korean)"
size: 524288
crc32: d9925ac9
- name: gp32156k.bin
description: "Firmware 1.5.6 (Korean)"
size: 524288
crc32: "667fb1c8"
- name: gp32166m.bin
description: "Firmware 1.6.6 (European)"
size: 524288
crc32: "4548a840"
- name: gp32mfv2.bin
description: "Mr. Spiv Multi Firmware V2"
size: 524288
crc32: "7ddaaaeb"
- name: x2c32.jed
description: "32 Macrocell CoolRunner-II CPLD (JEDEC)"
size: 15291
crc32: eeec10d8
# Fujitsu FM-7 family
- name: fm7.zip
required: true
category: bios_zip
system: fm7
source_ref: "src/mame/fujitsu/fm7.cpp:2188-2209"
contents:
- name: fbasic300.rom
description: "F-BASIC 3.00 ROM"
size: 31744
crc32: 87c98494
- name: subsys_c.rom
description: "Sub-CPU system ROM"
size: 10240
crc32: 24cec93f
- name: boot_bas.rom
description: "Boot BASIC ROM"
size: 512
crc32: c70f0c74
- name: boot_dos_a.rom
description: "Boot DOS-A ROM"
size: 512
crc32: bf441864
- name: kanji.rom
description: "Kanji ROM (optional, JIS level 1)"
size: 131072
crc32: 62402ac9
- name: fm77av.zip
required: true
category: bios_zip
system: fm7
source_ref: "src/mame/fujitsu/fm7.cpp:2211-2232"
contents:
- name: initiate.rom
description: "Initiate ROM (boot sequencer)"
size: 8192
crc32: 785cb06c
- name: fbasic30.rom
description: "F-BASIC 3.0 ROM"
size: 31744
crc32: a96d19b6
- name: subsys_a.rom
description: "Sub system A ROM"
size: 8192
crc32: e8014fbb
- name: subsys_b.rom
description: "Sub system B ROM"
size: 8192
crc32: 9be69fac
- name: subsyscg.rom
description: "Sub system CG ROM (character generator)"
size: 8192
crc32: e9f16c42
- name: fmnew7.zip
required: false
category: bios_zip
system: fm7
source_ref: "src/mame/fujitsu/fm7.cpp:2170-2186"
note: "FM-NEW7 clone, inherits shared ROMs from fm7.zip parent"
contents:
- name: fbasic302.rom
description: "F-BASIC 3.02 ROM"
size: 31744
crc32: a96d19b6
- name: boot_dos.rom
description: "Boot DOS ROM"
size: 512
crc32: 198614ff
# Technosys Aamber Pegasus
- name: pegasus.zip
required: true
category: bios_zip
system: pegasus
source_ref: "src/mame/ausnz/pegasus.cpp:ROM_START"
note: "6809-based home computer (1981, New Zealand). 8 monitor BIOS variants, 5 cartridge expansion slots."
contents:
- name: mon11_2674.bin
description: "Monitor 1.1 r2674 (default)"
size: 4096
crc32: 1640ff7e
- name: mon10_2569.bin
description: "Monitor 1.0 r2569"
size: 4096
crc32: 910fc930
- name: mon11_2569.bin
description: "Monitor 1.1 r2569"
size: 4096
crc32: "07b92002"
- name: mon11_2669.bin
description: "Monitor 1.1 r2669"
size: 4096
crc32: f3ee23c8
- name: mon22_2856.bin
description: "Monitor 2.2 r2856"
size: 4096
crc32: 5f5f688a
- name: mon22b_2856.bin
description: "Monitor 2.2B r2856"
size: 4096
crc32: a47b0308
- name: mon23_2601.bin
description: "Monitor 2.3 r2601"
size: 4096
crc32: 0e024222
- name: mon23a_2569.bin
description: "Monitor 2.3A r2569"
size: 4096
crc32: 248e62c9
- name: 6571.bin
description: "MCM6571A character generator"
size: 2048
crc32: 5a25144b
# Amstrad PCW (Joyce)
- name: pcw8256.zip
required: true
category: bios_zip
system: pcw
source_ref: "src/mame/amstrad/pcw.cpp:1402"
note: |
Amstrad PCW8256 parent ROM set. Z80-based word processor/computer (1985).
6 machines: pcw8256 (parent), pcw8512, pcw9256, pcw9512, pcw9512+, pcw10.
All MACHINE_NOT_WORKING. No main CPU ROM (boots from CP/M floppy).
Printer MCU boot code copied into Z80 RAM at machine_reset.
Clones pcw8512/pcw9256/pcw10 share parent ROMs. pcw9512/pcw9512+ use
separate daisywheel printer MCU (pcw9512.zip).
contents:
- name: 40026.ic701
description: "i8041 9-pin dot-matrix printer MCU"
size: 1024
crc32: ee8890ae
- name: 40027.ic801
description: "i8048 keyboard MCU"
size: 1024
crc32: "25260958"
- name: pcw9512.zip
required: true
category: bios_zip
system: pcw
source_ref: "src/mame/amstrad/pcw.cpp:1429"
note: |
Amstrad PCW9512 clone ROM set. Daisywheel printer variant (1987).
Uses different printer MCU (40103.ic109) than 9-pin models.
Keyboard MCU (40027.ic801) inherited from parent pcw8256.zip.
contents:
- name: 40103.ic109
description: "i8041 daisywheel printer MCU"
size: 8192
crc32: a64d450a
# Tandy TRS-80 family
# Model I (trs80.cpp): trs80 (Level I, parent, WORKING), trs80l2 (Level II, parent),
# eg3003 (EACA Video Genie, parent), sys80/sys80p (clones), ht1080z/ht1080z2/ht108064 (clones).
# Model III/4/4P (trs80m3.cpp): trs80m3 (parent), trs80m4/trs80m4p/cp500 (clones).
# Z80-based home computers (1977-1983). Only trs80 (Level I) is MACHINE_SUPPORTS_SAVE;
# all others MACHINE_NOT_WORKING. Software lists: trs80_cass, trs80_flop, trs80_quik.
- name: trs80.zip
required: true
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:567-584"
note: "TRS-80 Model I Level I Basic (1977). Only fully working TRS-80 machine in MAME."
contents:
- name: level1.rom
description: "Level I BASIC ROM (4KB)"
size: 4096
crc32: 70d06dff
- name: mcm6670p.z29
description: "MCM6670P character generator ROM"
size: 1024
crc32: 0033f2b9
- name: trs80l2.zip
required: true
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:587-603"
note: |
TRS-80 Model I Level II Basic (1978). Separate parent machine from trs80 (Level I).
2 BIOS variants: Radio Shack Level II and R/S L2 (alternate dumps).
MACHINE_NOT_WORKING.
contents:
- name: rom-a.z1
description: "Level II ROM A (default)"
size: 4096
crc32: 37c59db2
- name: rom-b.z2
description: "Level II ROM B (default)"
size: 4096
crc32: "05818718"
- name: rom-c.z3
description: "Level II ROM C (default)"
size: 4096
crc32: 306e5d66
- name: rom-a_alt.z1
description: "Level II ROM A (alternate dump)"
size: 4096
crc32: be46faf5
- name: rom-b_alt.z2
description: "Level II ROM B (alternate dump)"
size: 4096
crc32: 6c791c2d
- name: rom-c_alt.z3
description: "Level II ROM C (alternate dump)"
size: 4096
crc32: 55b3ad13
- name: mcm6670p.z29
description: "MCM6670P character generator ROM"
size: 1024
crc32: 0033f2b9
- name: eg3003.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:608-617"
note: |
EACA Video Genie EG3003 (1980). TRS-80 Level II compatible clone.
Parent for sys80/sys80p/ht1080z/ht1080z2/ht108064 clones.
MACHINE_NOT_WORKING.
contents:
- name: 3001.z10
description: "ROM A (BASIC part 1)"
size: 4096
crc32: 8f5214de
- name: 3002.z11
description: "ROM B (BASIC part 2)"
size: 4096
crc32: 46e88fbf
- name: 3003.z12
description: "ROM C (BASIC part 3)"
size: 4096
crc32: 306e5d66
- name: tcs-ext.z13
description: "TCS extension ROM"
size: 2048
crc32: 8f2ac112
- name: tcs-ext.z25
description: "TCS character generator ROM"
size: 2048
crc32: 150c5f1f
- name: sys80.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:620-632"
note: "EACA System-80 (1980). Clone of eg3003 with different extension ROM. sys80p (50 Hz) shares ROMs."
contents:
- name: sys80.z13
description: "System-80 extension ROM"
size: 2048
crc32: 2a851e33
- name: 2513.z25
description: "Character generator ROM"
size: 1024
crc32: 0033f2b9
- name: ht1080z.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:637-646"
note: "Hiradastechnika HT-1080Z Series I (1983). Hungarian clone of eg3003."
contents:
- name: ht1080z.z25
description: "HT-1080Z character generator ROM"
size: 2048
crc32: e8c59d4f
- name: ht1080z2.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:649-658"
note: "Hiradastechnika HT-1080Z Series II (1984). Clone of eg3003."
contents:
- name: ht1080z2.z13
description: "HT-1080Z II extension ROM"
size: 2048
crc32: "07415ac6"
- name: ht1080z2.z25
description: "HT-1080Z II character generator ROM"
size: 2048
crc32: 6728f0ab
- name: ht108064.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80.cpp:661-670"
note: "Hiradastechnika HT-1080Z/64 (1985). 64-column clone of eg3003."
contents:
- name: 3001_64.z10
description: "ROM A (64-col BASIC part 1)"
size: 4096
crc32: 59ec132e
- name: 3002_64.z11
description: "ROM B (64-col BASIC part 2)"
size: 4096
crc32: a7a73e8c
- name: ht108064.z13
description: "HT-1080Z/64 extension ROM"
size: 2048
crc32: fc12bd28
- name: ht108064.z25
description: "HT-1080Z/64 character generator ROM"
size: 2048
crc32: e76b73a4
- name: trs80m3.zip
required: true
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80m3.cpp:483-519"
note: |
TRS-80 Model III (1980). Parent for trs80m4/trs80m4p/cp500 clones.
4 BIOS variants: Level 2 ROM C Rev C (default), Rev B, Network III v2 (student),
Level 1. MACHINE_NOT_WORKING.
contents:
- name: 8041364.u104
description: "Level 2 ROM A"
size: 8192
crc32: ec0c6daa
- name: 8040332.u105
description: "Level 2 ROM B"
size: 4096
crc32: ed4ee921
- name: 8040316c.u106
description: "Level 2 ROM C Rev C (default)"
size: 2048
crc32: c8f79433
- name: 8040316b.u106
description: "Level 2 ROM C Rev B"
size: 2048
crc32: 84a5702d
- name: 276a.u106
description: "Network III v2 ROM C"
size: 2048
crc32: 7d38720a
- name: 8040032.u104
description: "Level 1 BIOS"
size: 4096
crc32: 6418d641
- name: 8044316a.u36
description: "Character generator ROM (rev A)"
size: 2048
crc32: 444c8b60
- name: trs80m4.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80m3.cpp:522-528"
note: |
TRS-80 Model 4 (1980). Clone of trs80m3. BAD_DUMP combined ROM
(should be split into 3 like trs80m3). MACHINE_NOT_WORKING.
contents:
- name: trs80m4.rom
description: "Combined system ROM (BAD_DUMP)"
size: 14336
crc32: 1a92d54d
- name: 8044316a.u36
description: "Character generator ROM"
size: 2048
crc32: 444c8b60
- name: trs80m4p.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80m3.cpp:530-539"
note: |
TRS-80 Model 4P (1983). Clone of trs80m3. Completely different memory map;
Model III ROMs loaded from boot disk, only a bootloader ROM on board.
2 BIOS variants: gate array (default) and disk loader hack.
MACHINE_NOT_WORKING.
contents:
- name: 8075332.u69
description: "Bootloader ROM"
size: 4096
crc32: 3a738aa9
- name: trs80m4p_loader_hack.rom
description: "Disk loader hack ROM"
size: 504
crc32: 7ff336f4
- name: 8049007.u103
description: "Character generator ROM"
size: 2048
crc32: 1ac44bea
- name: cp500.zip
required: false
category: bios_zip
system: trs80
source_ref: "src/mame/trs/trs80m3.cpp:541-550"
note: "Prologica CP-500 (1982). Brazilian TRS-80 Model III clone. MACHINE_NOT_WORKING."
contents:
- name: s_8407_cn62516n_cp500a_prologica_83.ci111
description: "Combined system + boot ROM"
size: 16384
crc32: c2fc1b92
- name: 100.105.ci36
description: "Character generator ROM"
size: 2048
crc32: 1765931e
# TI-99/4A
- name: ti99_4a.zip
required: true
category: bios_zip
system: ti99
source_ref: "src/mame/ti/ti99_4x.cpp:1153-1166"
note: "TI-99/4A Home Computer (1981). Parent machine ROM set."
contents:
- name: 994a_grom0.u500
description: "Console GROM 0"
size: 6144
crc32: 2445a5e8
- name: 994a_grom1.u501
description: "Console GROM 1"
size: 6144
crc32: b8f367ab
- name: 994a_grom2.u502
description: "Console GROM 2"
size: 6144
crc32: e0bb5341
- name: 994a_rom_hb.u610
description: "CPU ROM high byte"
size: 4096
crc32: ee859c5f
- name: 994a_rom_lb.u611
description: "CPU ROM low byte"
size: 4096
crc32: 37859301
- name: ti99_speech.zip
required: false
category: bios_zip
system: ti99
source_ref: "src/devices/bus/ti99/sidecar/speechsyn.cpp:242-246"
note: "TI-99 Speech Synthesizer sidecar. 2 TMS6100 vocabulary ROMs."
contents:
- name: cd2325a.u2a
description: "TMS6100 vocabulary ROM (bottom)"
size: 16384
crc32: 1f58b571
- name: cd2326a.u2b
description: "TMS6100 vocabulary ROM (top)"
size: 16384
crc32: 65d00401

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,14 @@ type: libretro
core_classification: community_fork
source: "https://github.com/libretro/quasi88-libretro"
upstream: "https://www.retropc.net/showzoh/quasi88/"
profiled_date: "2026-03-25"
profiled_date: "2026-03-29"
core_version: "0.6.4"
display_name: "NEC - PC-8000 / PC-8800 series (QUASI88)"
cores:
- quasi88
systems:
- pc-8801
- nec-pc-88
- nec-pc-80
# QUASI88 by Showzoh Fukunaga, libretro port by Celerizer.
# Includes pseudo-BIOS (pbios_n88, pbios_disk) by cisc compiled into the core.
@@ -35,6 +36,16 @@ systems:
#
# Upstream supports 2HD disk ROM (8 KB) via memory.c:356-368.
# The libretro port reads only 2 KB (2D type) at libretro.c:552.
#
# font.rom divergence: libretro.c:566-571 loads 0x1000 bytes then
# overwrites the buffer with kanji ROM + built-in graph. The font.rom
# data is not actually used (bug). Upstream memory.c:427 loads FONT_SZ
# (2048) and keeps the font data in the first half.
#
# OPNA rhythm samples (2608_BD.WAV etc.): loaded by upstream via
# fmgen/opna.cpp:1271 LoadRhythmSample(), but osd_dir_rom() returns
# NULL in the libretro port (dir_rom never initialized), so the samples
# cannot be loaded. OPNA rhythm playback is silent in the libretro core.
notes: |
Files go in <system_dir>/quasi88/ or directly in <system_dir>/.
@@ -43,7 +54,8 @@ notes: |
kanji display), real ROM files are needed.
Core option "quasi88_basic_mode" selects N88 V2, N88 V1H, N88 V1S,
or N mode. Each mode requires its corresponding ROM.
or N mode. Each mode requires its corresponding ROM. N mode emulates
PC-8001 series (N-BASIC) and requires n88n.rom.
files:
- name: n88.rom

66
emulators/ti99sim.yml Normal file
View File

@@ -0,0 +1,66 @@
emulator: ti99sim
display_name: "TI-99/Sim"
type: standalone
source: https://github.com/christianhaitian/ti99sim
upstream: https://www.mrousseau.org/programs/ti99sim/
profiled_date: "2026-03-29"
core_version: "0.16.0"
cores:
- ti99sim
systems:
- ti99
notes: |
Marc Rousseau's TI-99/4A simulator (since 1993). Standalone only, no libretro port.
Uses proprietary .ctg cartridge format for ROM packaging (CPU ROM + GROMs + RAM config).
Raw ROM dumps must be converted to .ctg via the convert-ctg utility.
File search order: CWD, CWD/console/ (v0.16.0) or CWD/roms/ (<=0.0.11),
~/.ti99sim/console/, /opt/ti99sim/console/.
v0.16.0 adds SHA1-based auto-discovery for device ROMs in the console/ directory.
Recalbox packages ti99sim as a standalone emulator for the ti994a system.
files:
- name: TI-994A.ctg
required: true
description: "Console ROM cartridge containing CPU ROMs, system GROMs, and RAM configuration"
source_ref: "src/core/ti994a.cpp:122-131 (v0.16.0), src/sdl/main.cpp:407-411 (v0.0.11)"
sha1: "0264512c7d9e7fa091a48e5c8734782ea031a52d"
note: "SHA1 is of .ctg format. v0.16.0 also recognizes console v2.2 (sha1 16e275faae427465ba4dd4c2bf8569f6546d32dd)."
- name: spchrom.bin
required: false
size: 32768
description: "TMS5220/TMS6100 Speech Synthesizer vocabulary ROM"
source_ref: "src/core/tms5220.cpp:190-213 (v0.16.0), src/core/tms5220.cpp:194-213 (v0.0.11)"
validation: [size]
hle_fallback: true
note: "Raw binary, not .ctg format. Must be exactly 32768 bytes. First byte must be 0xAA. Falls back to empty speech ROM if missing or invalid."
- name: ti-disk.ctg
required: false
description: "TI Disk Controller DSR ROM (CRU >1100)"
source_ref: "src/core/device-support.cpp:52 (v0.16.0), src/sdl/main.cpp:452-476 (v0.0.11)"
sha1: "ed91d48c1eaa8ca37d5055bcf67127ea51c4cad5"
note: "SHA1 is of .ctg format. Disk emulation disabled without this file."
- name: "Gram Kracker.ctg"
required: false
description: "Miller Graphics Gram Kracker ROM manipulation peripheral"
source_ref: "src/core/ti994a-gk.cpp:56 (v0.16.0), src/sdl/ti994a-sdl.cpp:78 (v0.0.11)"
sha1: "a3bd5257c63e190800921b52dbe3ffa91ad91113"
note: "SHA1 is of .ctg format. Gram Kracker features disabled without this file."
- name: ti-pcard.ctg
required: false
description: "TI P-Code Card (UCSD Pascal) DSR ROM"
source_ref: "src/core/device-support.cpp:53 (v0.16.0)"
sha1: "27aceb956262d3e3f97d938602dfaa91b53da59e"
note: "SHA1 is of .ctg format. v0.16.0+ only."
- name: cf7+.ctg
required: false
description: "CF7+/nanoPEB CompactFlash interface DSR ROM"
source_ref: "src/core/device-support.cpp:51 (v0.16.0)"
sha1: "4d26e5ef0997ed2f3a56eb8104778bfe719b38f2"
note: "SHA1 is of .ctg format. v0.16.0+ only."

View File

@@ -1,299 +1,58 @@
# Vita3K emulator firmware profile
# Generated from source analysis of https://github.com/Vita3K/Vita3K
# Commit analyzed: HEAD as of 2026-03-17
emulator: Vita3K
type: standalone
source: "https://github.com/Vita3K/Vita3K"
logo: "https://raw.githubusercontent.com/Vita3K/Vita3K/master/data/image/icon.png"
profiled_date: "2026-03-18"
upstream: "https://github.com/Vita3K/Vita3K"
profiled_date: "2026-03-29"
core_version: "0.2.1"
display_name: "Vita3K (PS Vita)"
systems: [sony-playstation-vita]
firmware_file: "PSVUPDAT.PUP"
firmware_source: "https://www.playstation.com/en-us/support/hardware/psvita/system-software/"
firmware_detection: "pup_decrypt"
firmware_install: "decrypts PUP, extracts FAT/exFAT images into os0/, vs0/, sa0/, pd0/"
# Standalone emulator, no libretro core exists.
# Firmware PUP files are one-time installations: install_pup() decrypts and
# extracts partition images (os0, vs0, sa0, pd0) into the emulator data directory.
# The PUP file itself is deleted after extraction.
# Three separate PUP packages exist on Sony servers:
# 1. PSVUPDAT.PUP (main firmware) from psv.update.playstation.net
# 2. PSP2UPDAT.PUP (font package) from psp2.update.playstation.net (sd_ prefix)
# 3. PSP2UPDAT.PUP (preinst firmware) from psp2.update.playstation.net (pre_ prefix)
# Vita3K uses hybrid LLE/HLE: some modules always loaded from firmware (libc,
# libSceFt2, libpvf, libcdlg), others toggleable per-module via settings.
validation:
method: "pup_decrypt_and_extract"
source_ref: "vita3k/packages/src/pup.cpp:260-314"
note: >
PUP is decrypted using SCE keys (register_keys), then four filesystem images are
extracted: os0.img (FAT), pd0.img (exFAT), sa0.img (FAT), vs0.img (FAT).
Each image is mounted to its respective partition path.
firmware_version:
path: "PUP_DEC/PUP/version.txt"
source_ref: "vita3k/packages/src/pup.cpp:303-309"
note: "Read from version.txt inside the decrypted PUP during installation"
# Firmware partitions extracted from PSVUPDAT.PUP
partitions:
os0:
image: "os0.img"
filesystem: "FAT"
source_ref: "vita3k/packages/src/pup.cpp:293"
note: "Core OS partition. Contains kernel modules and low-level system components."
vs0:
image: "vs0.img"
filesystem: "FAT"
source_ref: "vita3k/packages/src/pup.cpp:299"
files:
- name: PSVUPDAT.PUP
path: "psvita/PSVUPDAT.PUP"
required: true
hle_fallback: true
note: >
Main system partition. Contains firmware modules (sys/external/*.suprx),
system apps (app/), LiveArea resources, themes, and system configuration.
directories:
sys_external:
path: "vs0/sys/external/"
source_ref: "vita3k/modules/module_parent.cpp:332"
note: >
Primary firmware module directory. Modules are loaded as .suprx files.
RPCS3-style LLE/HLE toggle: Vita3K can load real firmware modules (LLE)
or use built-in reimplementations (HLE) per module.
Main PS Vita firmware. Decrypted via SCE keys, extracts os0 (kernel),
vs0 (system modules and apps), sa0 (fonts), pd0 (system data).
Games load LLE modules from vs0/sys/external/*.suprx. Preload modules
(libc, libSceFt2, libpvf) are always LLE. Without firmware most games fail.
Some simple titles may run on HLE alone.
source_ref: "vita3k/packages/src/pup.cpp:260-314, vita3k/module/src/load_module.cpp:142-161,187"
app:
path: "vs0/app/"
note: "System applications (settings, browser, store, etc)"
data_internal:
path: "vs0/data/internal/"
note: "Internal system data: themes, LiveArea defaults"
files:
- {path: "theme/", purpose: "Default system themes"}
- {path: "livearea/default/sce_sys/icon0.png", purpose: "Default app icon"}
sa0:
image: "sa0.img"
filesystem: "FAT"
source_ref: "vita3k/packages/src/pup.cpp:297"
note: "System assets partition. Contains firmware fonts (PVF files)."
directories:
fonts:
path: "sa0/data/font/pvf/"
source_ref: "vita3k/gui/src/gui.cpp:228-267"
note: >
PS Vita system fonts in PVF format (PlayStation Vita Font). Used for
system UI, LiveArea, and games that use the sceFt2/libpvf font API.
Without these, Vita3K falls back to bundled open-source fonts.
files:
- {name: "ltn0.pvf", type: "Latin Regular", required: true, note: "Primary UI font, checked at startup"}
- {name: "jpn0.pvf", type: "Japanese", required: false}
- {name: "kr0.pvf", type: "Korean", required: false}
- {name: "cn0.pvf", type: "Chinese Simplified", required: false}
pd0:
image: "pd0.img"
filesystem: "exFAT"
source_ref: "vita3k/packages/src/pup.cpp:295"
note: "System data partition. Contains system BGM and other data."
files:
- {path: "data/systembgm/initialsetup.at9", purpose: "Initial setup background music"}
# Firmware modules (vs0/sys/external/*.suprx)
firmware_modules:
# Auto-LLE modules (loaded from firmware by default)
auto_lle:
source_ref: "vita3k/module/src/load_module.cpp:142-161"
note: "These modules are automatically loaded via LLE when firmware is present"
modules:
- {id: "SCE_SYSMODULE_HTTP", libs: ["libhttp"]}
- {id: "SCE_SYSMODULE_SSL", libs: ["libssl"]}
- {id: "SCE_SYSMODULE_HTTPS", libs: ["libhttp", "libssl"]}
- {id: "SCE_SYSMODULE_ULT", libs: ["libult"]}
- {id: "SCE_SYSMODULE_SAS", libs: ["libsas"]}
- {id: "SCE_SYSMODULE_PGF", libs: ["libpgf"]}
- {id: "SCE_SYSMODULE_FIOS2", libs: ["libfios2"]}
- {id: "SCE_SYSMODULE_SYSTEM_GESTURE", libs: ["libsystemgesture"]}
- {id: "SCE_SYSMODULE_XML", libs: ["libSceXml"]}
- {id: "SCE_SYSMODULE_SQLITE", libs: ["libSceSqlite"]}
- {id: "SCE_SYSMODULE_RUDP", libs: ["librudp"]}
- {id: "SCE_SYSMODULE_NET_ADHOC_MATCHING", libs: ["adhoc_matching"]}
- {id: "SCE_SYSMODULE_MP4", libs: ["libscemp4"]}
- {id: "SCE_SYSMODULE_ATRAC", libs: ["libatrac"]}
- {id: "SCE_SYSMODULE_FACE", libs: ["libface"]}
- {id: "SCE_SYSMODULE_SMART", libs: ["libsmart"]}
- {id: "SCE_SYSMODULE_AVPLAYER", libs: ["libsceavplayer", "libscemp4"]}
- {id: "SCE_SYSMODULE_JSON", libs: ["libSceJson"]}
# Always-LLE preload modules
preload:
source_ref: "vita3k/interface.cpp:497-504, vita3k/module/src/load_module.cpp:187"
note: "These modules are always loaded from firmware (LLE), never HLE"
modules:
- {name: "libc", note: "C standard library"}
- {name: "libSceFt2", note: "FreeType2 font engine"}
- {name: "libpvf", note: "PlayStation Vita Font library"}
- {name: "libcdlg", note: "Common dialog library"}
# All registered sysmodule mappings
sysmodule_map:
source_ref: "vita3k/module/src/load_module.cpp:25-109"
note: "Complete mapping of SCE_SYSMODULE IDs to firmware library filenames"
modules:
# Networking
- {id: "SCE_SYSMODULE_NET", libs: ["libnet", "libnetctl"]}
- {id: "SCE_SYSMODULE_HTTP", libs: ["libhttp"]}
- {id: "SCE_SYSMODULE_SSL", libs: ["libssl"]}
- {id: "SCE_SYSMODULE_HTTPS", libs: ["libhttp", "libssl"]}
# Performance / Threading
- {id: "SCE_SYSMODULE_PERF", libs: ["libperf"]}
- {id: "SCE_SYSMODULE_FIBER", libs: ["libfiber"]}
- {id: "SCE_SYSMODULE_ULT", libs: ["libult"]}
# Debug
- {id: "SCE_SYSMODULE_DBG", libs: ["librazorcapture_es4", "librazorhud_es4"]}
- {id: "SCE_SYSMODULE_RAZOR_CAPTURE", libs: ["librazorcapture_es4"]}
- {id: "SCE_SYSMODULE_RAZOR_HUD", libs: ["librazorhud_es4"]}
# Audio
- {id: "SCE_SYSMODULE_NGS", libs: ["libngs"]}
- {id: "SCE_SYSMODULE_SULPHA", libs: ["libsulpha"]}
- {id: "SCE_SYSMODULE_SAS", libs: ["libsas"]}
- {id: "SCE_SYSMODULE_AUDIOCODEC", libs: ["libaudiocodec"]}
- {id: "SCE_SYSMODULE_AACENC", libs: ["libnaac"]}
- {id: "SCE_SYSMODULE_ATRAC", libs: ["libatrac"]}
- {id: "SCE_SYSMODULE_VOICE", libs: ["libvoice"]}
- {id: "SCE_SYSMODULE_VOICEQOS", libs: ["libvoiceqos"]}
# Font / Text
- {id: "SCE_SYSMODULE_PGF", libs: ["libpgf"]}
- {id: "SCE_SYSMODULE_IME", libs: ["libime"]}
- {id: "SCE_SYSMODULE_HANDWRITING", libs: ["libhandwriting"]}
# System
- {id: "SCE_SYSMODULE_APPUTIL", libs: ["apputil"]}
- {id: "SCE_SYSMODULE_FIOS2", libs: ["libfios2"]}
- {id: "SCE_SYSMODULE_SYSTEM_GESTURE", libs: ["libsystemgesture"]}
- {id: "SCE_SYSMODULE_LOCATION", libs: ["liblocation"]}
- {id: "SCE_SYSMODULE_CLIPBOARD", libs: ["libclipboard"]}
- {id: "SCE_SYSMODULE_TRIGGER_UTIL", libs: ["trigger_util"]}
- {id: "SCE_SYSMODULE_LIVEAREA", libs: ["livearea_util"]}
- {id: "SCE_SYSMODULE_BG_APP_UTIL", libs: ["bgapputil"]}
- {id: "SCE_SYSMODULE_INCOMING_DIALOG", libs: ["incoming_dialog"]}
- {id: "SCE_SYSMODULE_IPMI", libs: ["libipmi_nongame"]}
- {id: "SCE_SYSMODULE_NOTIFICATION_UTIL", libs: ["notification_util"]}
- {id: "SCE_SYSMODULE_SHUTTER_SOUND", libs: ["libSceShutterSound"]}
- {id: "SCE_SYSMODULE_SCREEN_SHOT", libs: ["libSceScreenShot"]}
# PlayStation Network
- {id: "SCE_SYSMODULE_NP_BASIC", libs: ["np_basic"]}
- {id: "SCE_SYSMODULE_NP", libs: ["np_common", "np_manager", "np_basic"]}
- {id: "SCE_SYSMODULE_NP_COMMERCE2", libs: ["np_commerce2"]}
- {id: "SCE_SYSMODULE_NP_UTILITY", libs: ["np_utility"]}
- {id: "SCE_SYSMODULE_NP_MATCHING2", libs: ["np_matching2"]}
- {id: "SCE_SYSMODULE_NP_SCORE_RANKING", libs: ["np_ranking"]}
- {id: "SCE_SYSMODULE_NP_ACTIVITY", libs: ["np_activity_sdk"]}
- {id: "SCE_SYSMODULE_NP_TROPHY", libs: ["np_trophy"]}
- {id: "SCE_SYSMODULE_NP_MESSAGE", libs: ["np_message_padding", "np_message"]}
- {id: "SCE_SYSMODULE_NP_PARTY", libs: ["np_party"]}
- {id: "SCE_SYSMODULE_NP_TUS", libs: ["np_tus"]}
- {id: "SCE_SYSMODULE_NP_SNS_FACEBOOK", libs: ["np_sns_facebook"]}
- {id: "SCE_SYSMODULE_NP_SIGNALING", libs: ["np_signaling"]}
- {id: "SCE_SYSMODULE_NP_WEBAPI", libs: ["np_webapi"]}
# Media
- {id: "SCE_SYSMODULE_MP4", libs: ["libscemp4"]}
- {id: "SCE_SYSMODULE_AVPLAYER", libs: ["libsceavplayer", "libscemp4"]}
- {id: "SCE_SYSMODULE_AVCDEC", libs: ["avcdec_for_player"]}
- {id: "SCE_SYSMODULE_MP4_RECORDER", libs: ["libSceMp4Rec"]}
- {id: "SCE_SYSMODULE_PHOTO_EXPORT", libs: ["libScePhotoExport"]}
- {id: "SCE_SYSMODULE_VIDEO_EXPORT", libs: ["libSceVideoExport"]}
- {id: "SCE_SYSMODULE_MUSIC_EXPORT", libs: ["libSceMusicExport"]}
# Other
- {id: "SCE_SYSMODULE_XML", libs: ["libSceXml"]}
- {id: "SCE_SYSMODULE_JSON", libs: ["libSceJson"]}
- {id: "SCE_SYSMODULE_SQLITE", libs: ["libSceSqlite"]}
- {id: "SCE_SYSMODULE_RUDP", libs: ["librudp"]}
- {id: "SCE_SYSMODULE_NET_ADHOC_MATCHING", libs: ["adhoc_matching"]}
- {id: "SCE_SYSMODULE_PSPNET_ADHOC", libs: ["pspnet_adhoc"]}
- {id: "SCE_SYSMODULE_FACE", libs: ["libface"]}
- {id: "SCE_SYSMODULE_SMART", libs: ["libsmart"]}
- {id: "SCE_SYSMODULE_MARLIN", libs: ["libmln"]}
- {id: "SCE_SYSMODULE_MARLIN_DOWNLOADER", libs: ["libmlndownloader"]}
- {id: "SCE_SYSMODULE_MARLIN_APP_LIB", libs: ["libmlnapplib"]}
- {id: "SCE_SYSMODULE_TELEPHONY_UTIL", libs: ["libSceTelephonyUtil"]}
- {id: "SCE_SYSMODULE_DTCP_IP", libs: ["libSceDtcpIp"]}
- {id: "SCE_SYSMODULE_VIDEO_SEARCH_EMPR", libs: ["libSceVideoSearchEmpr"]}
- {id: "SCE_SYSMODULE_BEISOBMF", libs: ["libSceBeisobmf"]}
- {id: "SCE_SYSMODULE_BEMP2SYS", libs: ["libSceBemp2sys"]}
- {id: "SCE_SYSMODULE_NEAR_UTIL", libs: ["libScenNearUtil"]}
- {id: "SCE_SYSMODULE_NEAR_DIALOG_UTIL", libs: ["libSceNearDialogUtil"]}
- {id: "SCE_SYSMODULE_LOCATION_EXTENSION", libs: ["liblocation_extension"]}
- {id: "SCE_SYSMODULE_MAIL_API", libs: ["mail_api_for_local_libc"]}
- {id: "SCE_SYSMODULE_TELEPORT_CLIENT", libs: ["libSceTeleportClient"]}
- {id: "SCE_SYSMODULE_TELEPORT_SERVER", libs: ["libSceTeleportServer"]}
- {id: "SCE_SYSMODULE_APPUTIL_EXT", libs: ["apputil_ext"]}
- {id: "SCE_SYSMODULE_CODECENGINE_PERF", libs: ["libcodecengine_perf"]}
# Internal sysmodule mappings
internal_modules:
source_ref: "vita3k/module/src/load_module.cpp:114-136"
modules:
- {id: "SCE_SYSMODULE_INTERNAL_JPEG_ENC_ARM", libs: ["libscejpegencarm"]}
- {id: "SCE_SYSMODULE_INTERNAL_AUDIOCODEC", libs: ["audiocodec"]}
- {id: "SCE_SYSMODULE_INTERNAL_BXCE", libs: ["bXCe"]}
- {id: "SCE_SYSMODULE_INTERNAL_INI_FILE_PROCESSOR", libs: ["ini_file_processor"]}
- {id: "SCE_SYSMODULE_INTERNAL_NP_ACTIVITY_NET", libs: ["np_activity"]}
- {id: "SCE_SYSMODULE_INTERNAL_PAF", libs: ["libpaf"]}
- {id: "SCE_SYSMODULE_INTERNAL_SQLITE_VSH", libs: ["sqlite"]}
- {id: "SCE_SYSMODULE_INTERNAL_DBUTIL", libs: ["dbutil"]}
- {id: "SCE_SYSMODULE_INTERNAL_ACTIVITY_DB", libs: ["activity_db"]}
- {id: "SCE_SYSMODULE_INTERNAL_COMMON_GUI_DIALOG", libs: ["common_gui_dialog"]}
- {id: "SCE_SYSMODULE_INTERNAL_MSG_DIALOG", libs: ["libcdlg_msg"]}
- {id: "SCE_SYSMODULE_INTERNAL_SAVEDATA_DIALOG", libs: ["libcdlg_savedata"]}
- {id: "SCE_SYSMODULE_INTERNAL_IME_DIALOG", libs: ["libcdlg_ime"]}
- {id: "SCE_SYSMODULE_INTERNAL_COMMON_DIALOG_MAIN", libs: ["libcdlg_main"]}
- {id: "SCE_SYSMODULE_INTERNAL_DB_RECOVERY_UTILITY", libs: ["dbrecovery_utility"]}
- {id: "SCE_SYSMODULE_INTERNAL_DRM_PSM_KDC", libs: ["psmkdc"]}
- {id: "SCE_SYSMODULE_INTERNAL_LOCATION_INTERNAL", libs: ["liblocation_internal"]}
# IO device layout (full Vita filesystem)
io_devices:
source_ref: "vita3k/io/include/io/VitaIoDevice.h:22-50"
firmware_partitions:
- {device: "os0", purpose: "Core OS kernel modules"}
- {device: "vs0", purpose: "System firmware modules and apps"}
- {device: "sa0", purpose: "System assets (fonts)"}
- {device: "pd0", purpose: "System data (BGM, resources)"}
user_partitions:
- {device: "ux0", purpose: "Main storage (memory card)"}
- {device: "ur0", purpose: "Internal user storage"}
- {device: "uma0", purpose: "USB mass storage (PSTV)"}
- {device: "imc0", purpose: "Internal memory card (Slim)"}
- {device: "grw0", purpose: "Game card (writable area)"}
- {device: "gro0", purpose: "Game card (read-only area)"}
app_partitions:
- {device: "app0", purpose: "Current running application"}
- {device: "addcont0", purpose: "Additional content (DLC)"}
- {device: "savedata0", purpose: "Save data (slot 0)"}
- {device: "savedata1", purpose: "Save data (slot 1)"}
# Fallback fonts when firmware is not installed
fallback_fonts:
source_ref: "vita3k/gui/src/gui.cpp:276-318"
path: "data/fonts/"
files:
- {name: "mplus-1mn-bold.ttf", purpose: "Primary fallback monospaced font"}
- {name: "SourceHanSansSC-Bold-Min.ttf", purpose: "Chinese fallback font"}
- {name: "neodgm.ttf", purpose: "Korean fallback font"}
note: "Open-source fonts bundled with Vita3K, used when firmware fonts (sa0) are not installed"
- name: PSP2UPDAT.PUP
path: "psvita/PSP2UPDAT.PUP"
required: false
hle_fallback: true
note: >
Supplementary firmware font package. Populates sa0 partition with system
fonts (PVF format) used by UI, LiveArea, and games via sceFt2/libpvf API.
Vita3K bundles open-source fallback fonts (mplus-1mn-bold.ttf,
SourceHanSansSC-Bold-Min.ttf, neodgm.ttf) when firmware fonts are missing.
source_ref: "vita3k/gui/src/gui.cpp:228-267,276-318, vita3k/gui/src/initial_setup.cpp:96-97"
notes: |
Vita3K requires the official PS Vita firmware (PSVUPDAT.PUP) from Sony for full
compatibility. The firmware is decrypted using SCE keys and extracted into four
partition images: os0 (kernel), vs0 (system), sa0 (assets), pd0 (data).
The most important firmware components are:
1. vs0/sys/external/*.suprx - Firmware modules loaded by games
2. sa0/data/font/pvf/*.pvf - System fonts (ltn0.pvf is checked at startup)
3. vs0/data/internal/ - LiveArea resources and default themes
Vita3K uses a hybrid LLE/HLE approach:
- Some modules (libc, libSceFt2, libpvf, libcdlg) are always loaded from firmware (LLE)
- Others can be toggled between LLE and HLE per module via settings
- Without firmware, many games will fail to load required modules
The module loading path is: first check app0:sce_module/{name}.suprx (game-bundled),
then fall back to vs0:sys/external/{name}.suprx (firmware). This allows games to
ship their own module versions.
CLI firmware install: vita3k --firmware /path/to/PSVUPDAT.PUP
GUI: File > Install Firmware
Vita3K does not have a libretro core. It is standalone only.
Standalone PS Vita emulator. No libretro core.
Firmware installed via File > Install Firmware or CLI --firmware flag.
PUP files validated by SCEUF magic header only (pup.cpp:119), no hash check.
After install, four partitions are extracted:
os0 (FAT, kernel modules), vs0 (FAT, system modules/apps),
sa0 (FAT, fonts), pd0 (exFAT, system data/BGM).
Module loading: app0:sce_module/{name}.suprx (game-bundled) then
vs0:sys/external/{name}.suprx (firmware). Auto-LLE modules include
libhttp, libssl, libult, libsas, libpgf, libfios2, libsystemgesture,
libSceXml, libSceSqlite, librudp, libatrac, libface, libsmart,
libsceavplayer, libSceJson, and more (load_module.cpp:142-161).
A third optional preinst firmware PUP (pd0 partition: system BGM, resources)
is available from Sony but not commonly required for game compatibility.

View File

@@ -194,7 +194,7 @@ nav:
- vitaQuakeII: emulators/vitaquake2.md
- yabasanshiro: emulators/yabasanshiro.md
- Yuzu: emulators/yuzu.md
- Community forks (108):
- Community forks (109):
- EightyOne: emulators/81.md
- a5200: emulators/a5200.md
- Anarch: emulators/anarch.md
@@ -236,6 +236,7 @@ nav:
- Gambatte: emulators/gambatte.md
- Genesis Plus GX: emulators/genesis_plus_gx.md
- gpSP: emulators/gpsp.md
- GSplus: emulators/gsplus.md
- Handy: emulators/handy.md
- higan (SFC Accuracy): emulators/higan_sfc.md
- LRPS2: emulators/lrps2.md
@@ -427,7 +428,7 @@ nav:
- PCSX-ReARMed: emulators/pcsx_rearmed.md
- Launchers (1):
- Dolphin Launcher: emulators/dolphin_launcher.md
- Other (25):
- Other (24):
- ares: emulators/ares.md
- Beetle GBA (Mednafen): emulators/beetle_gba.md
- BigPEmu: emulators/bigpemu.md
@@ -436,7 +437,6 @@ nav:
- Demul: emulators/demul.md
- eka2l1: emulators/eka2l1.md
- ep128emu-core: emulators/ep128emu.md
- GSplus: emulators/gsplus.md
- Lexaloffle: emulators/lexaloffle.md
- Model 2 Emulator: emulators/model2.md
- openMSX: emulators/openmsx.md

View File

@@ -409,7 +409,7 @@ systems:
required: true
sha1: e5b2922ca137051059e4269b236d07a22c07bc84
size: 524288
nintendo-bsx:
nintendo-satellaview:
files:
- name: Satellaview_BS-X.sfc
destination: Satellaview_BS-X.sfc

View File

@@ -6451,10 +6451,6 @@ systems:
destination: bios/ROM_XEGAME
required: false
md5: d7eb37aec6960cba36bc500e0e5d00bc
- name: ROM_400/800_CUSTOM
destination: bios/ROM_400/800_CUSTOM
required: false
md5: a3e8d617c95d08031fe1b20d541434b2,7e5e4ce9508edef684ebe2c5a0e6f0d3
- name: ROM_BASIC_A
destination: bios/ROM_BASIC_A
required: false

View File

@@ -10,6 +10,7 @@ import hashlib
import json
import os
import urllib.error
import urllib.parse
import urllib.request
import zipfile
import zlib
@@ -32,27 +33,46 @@ def require_yaml():
sys.exit(1)
def compute_hashes(filepath: str | Path) -> dict[str, str]:
"""Compute SHA1, MD5, SHA256, CRC32, Adler32 for a file."""
sha1 = hashlib.sha1()
md5 = hashlib.md5()
sha256 = hashlib.sha256()
_ALL_ALGORITHMS = frozenset({"sha1", "md5", "sha256", "crc32", "adler32"})
def compute_hashes(
filepath: str | Path,
algorithms: frozenset[str] | None = None,
) -> dict[str, str]:
"""Compute file hashes. Pass *algorithms* to limit which are computed."""
algos = algorithms or _ALL_ALGORITHMS
sha1 = hashlib.sha1() if "sha1" in algos else None
md5 = hashlib.md5() if "md5" in algos else None
sha256 = hashlib.sha256() if "sha256" in algos else None
do_crc = "crc32" in algos
do_adler = "adler32" in algos
crc = 0
adler = 1 # zlib.adler32 initial value
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
sha1.update(chunk)
md5.update(chunk)
sha256.update(chunk)
crc = zlib.crc32(chunk, crc)
adler = zlib.adler32(chunk, adler)
return {
"sha1": sha1.hexdigest(),
"md5": md5.hexdigest(),
"sha256": sha256.hexdigest(),
"crc32": format(crc & 0xFFFFFFFF, "08x"),
"adler32": format(adler & 0xFFFFFFFF, "08x"),
}
if sha1:
sha1.update(chunk)
if md5:
md5.update(chunk)
if sha256:
sha256.update(chunk)
if do_crc:
crc = zlib.crc32(chunk, crc)
if do_adler:
adler = zlib.adler32(chunk, adler)
result: dict[str, str] = {}
if sha1:
result["sha1"] = sha1.hexdigest()
if md5:
result["md5"] = md5.hexdigest()
if sha256:
result["sha256"] = sha256.hexdigest()
if do_crc:
result["crc32"] = format(crc & 0xFFFFFFFF, "08x")
if do_adler:
result["adler32"] = format(adler & 0xFFFFFFFF, "08x")
return result
def load_database(db_path: str) -> dict:
@@ -106,12 +126,20 @@ def parse_md5_list(raw: str) -> list[str]:
return [m.strip().lower() for m in raw.split(",") if m.strip()] if raw else []
_shared_yml_cache: dict[str, dict] = {}
_platform_config_cache: dict[tuple[str, str], dict] = {}
def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -> dict:
"""Load a platform config with inheritance and shared group resolution.
This is the SINGLE implementation used by generate_pack, generate_readme,
verify, and auto_fetch. No other copy should exist.
"""
cache_key = (platform_name, os.path.realpath(platforms_dir))
if cache_key in _platform_config_cache:
return _platform_config_cache[cache_key]
if yaml is None:
raise ImportError("PyYAML required: pip install pyyaml")
@@ -136,16 +164,14 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
merged["systems"][sys_id] = override
config = merged
# Resolve shared group includes (cached to avoid re-parsing per call)
# Resolve shared group includes
shared_path = os.path.join(platforms_dir, "_shared.yml")
if os.path.exists(shared_path):
if not hasattr(load_platform_config, "_shared_cache"):
load_platform_config._shared_cache = {}
cache_key = os.path.realpath(shared_path)
if cache_key not in load_platform_config._shared_cache:
shared_real = os.path.realpath(shared_path)
if shared_real not in _shared_yml_cache:
with open(shared_path) as f:
load_platform_config._shared_cache[cache_key] = yaml.safe_load(f) or {}
shared = load_platform_config._shared_cache[cache_key]
_shared_yml_cache[shared_real] = yaml.safe_load(f) or {}
shared = _shared_yml_cache[shared_real]
shared_groups = shared.get("shared_groups", {})
for system in config.get("systems", {}).values():
for group_name in system.get("includes", []):
@@ -165,6 +191,7 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
system.setdefault("files", []).append(gf)
existing.add(key)
_platform_config_cache[cache_key] = config
return config
@@ -444,7 +471,7 @@ def resolve_local_file(
if valid:
primary = [p for p, _ in valid if "/.variants/" not in p]
return (primary[0] if primary else valid[0][0]), "hash_mismatch"
# No candidate contains the zipped_file fall through to step 5
# No candidate contains the zipped_file -fall through to step 5
else:
primary = [p for p, _ in candidates if "/.variants/" not in p]
return (primary[0] if primary else candidates[0][0]), "hash_mismatch"
@@ -485,41 +512,45 @@ def resolve_local_file(
candidate = os.path.join(cache_dir, try_name)
if os.path.isfile(candidate):
return candidate, "data_dir"
# Basename walk: find file anywhere in cache tree
# Basename walk: find file anywhere in cache tree (case-insensitive)
basename_targets = {
(n.rsplit("/", 1)[-1] if "/" in n else n)
(n.rsplit("/", 1)[-1] if "/" in n else n).casefold()
for n in names_to_try
}
for root, _dirs, fnames in os.walk(cache_dir):
for fn in fnames:
if fn in basename_targets:
if fn.casefold() in basename_targets:
return os.path.join(root, fn), "data_dir"
return None, "not_found"
_mame_clone_map_cache: dict[str, str] | None = None
def _get_mame_clone_map() -> dict[str, str]:
"""Load and cache the MAME clone map (clone_name -> canonical_name)."""
if not hasattr(_get_mame_clone_map, "_cache"):
clone_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"_mame_clones.json",
)
if os.path.exists(clone_path):
import json as _json
with open(clone_path) as f:
data = _json.load(f)
_get_mame_clone_map._cache = {}
for canonical, info in data.items():
for clone in info.get("clones", []):
_get_mame_clone_map._cache[clone] = canonical
else:
_get_mame_clone_map._cache = {}
return _get_mame_clone_map._cache
global _mame_clone_map_cache
if _mame_clone_map_cache is not None:
return _mame_clone_map_cache
clone_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"_mame_clones.json",
)
if os.path.exists(clone_path):
with open(clone_path) as f:
data = json.load(f)
_mame_clone_map_cache = {}
for canonical, info in data.items():
for clone in info.get("clones", []):
_mame_clone_map_cache[clone] = canonical
else:
_mame_clone_map_cache = {}
return _mame_clone_map_cache
def check_inside_zip(container: str, file_name: str, expected_md5: str) -> str:
"""Check a ROM inside a ZIP replicates Batocera checkInsideZip().
"""Check a ROM inside a ZIP -replicates Batocera checkInsideZip().
Returns "ok", "untested", "not_in_zip", or "error".
"""
@@ -540,13 +571,32 @@ def check_inside_zip(container: str, file_name: str, expected_md5: str) -> str:
return "error"
_zip_contents_cache: tuple[frozenset[tuple[str, float]], dict] | None = None
def build_zip_contents_index(db: dict, max_entry_size: int = 512 * 1024 * 1024) -> dict:
"""Build {inner_rom_md5: zip_file_sha1} for ROMs inside ZIP files."""
index: dict[str, str] = {}
"""Build {inner_rom_md5: zip_file_sha1} for ROMs inside ZIP files.
Results are cached in-process; repeated calls with unchanged ZIPs return
the cached index.
"""
global _zip_contents_cache
# Build fingerprint from ZIP paths + mtimes for cache invalidation
zip_entries: list[tuple[str, str]] = []
for sha1, entry in db.get("files", {}).items():
path = entry["path"]
if not path.endswith(".zip") or not os.path.exists(path):
continue
if path.endswith(".zip") and os.path.exists(path):
zip_entries.append((path, sha1))
fingerprint = frozenset(
(path, os.path.getmtime(path)) for path, _ in zip_entries
)
if _zip_contents_cache is not None and _zip_contents_cache[0] == fingerprint:
return _zip_contents_cache[1]
index: dict[str, str] = {}
for path, sha1 in zip_entries:
try:
with zipfile.ZipFile(path, "r") as zf:
for info in zf.infolist():
@@ -559,6 +609,8 @@ def build_zip_contents_index(db: dict, max_entry_size: int = 512 * 1024 * 1024)
index[h.hexdigest()] = sha1
except (zipfile.BadZipFile, OSError):
continue
_zip_contents_cache = (fingerprint, index)
return index
@@ -713,7 +765,7 @@ MANUFACTURER_PREFIXES = (
"snk-", "panasonic-", "nec-", "epoch-", "mattel-", "fairchild-",
"hartung-", "tiger-", "magnavox-", "philips-", "bandai-", "casio-",
"coleco-", "commodore-", "sharp-", "sinclair-", "atari-", "sammy-",
"gce-", "texas-instruments-",
"gce-", "interton-", "texas-instruments-",
)
@@ -732,14 +784,31 @@ def derive_manufacturer(system_id: str, system_data: dict) -> str:
return "Other"
# Abbreviations that normalization alone cannot resolve.
# Maps platform-specific short names to canonical profile system IDs.
SYSTEM_ALIASES: dict[str, str] = {
"gmaster": "hartung-game-master",
"n64dd": "nintendo-64dd",
"neogeo64": "hyper-neogeo64",
"psvita": "sony-playstation-vita",
# Platform IDs missing the manufacturer-prefix hyphen
"atari5200": "atari-5200",
"atari7800": "atari-7800",
"atarist": "atari-st",
"sega32x": "sega-32x",
"segastv": "sega-stv",
}
def _norm_system_id(sid: str) -> str:
"""Normalize system ID for cross-platform matching.
Strips manufacturer prefixes and separators so that platform-specific
IDs (e.g., "xbox", "nintendo-wiiu") match profile IDs
(e.g., "microsoft-xbox", "nintendo-wii-u").
Resolves known aliases, then strips manufacturer prefixes and separators
so that platform-specific IDs (e.g., "xbox", "nintendo-wiiu") match
profile IDs (e.g., "microsoft-xbox", "nintendo-wii-u").
"""
s = sid.lower().replace("_", "-")
s = SYSTEM_ALIASES.get(s, s)
for prefix in MANUFACTURER_PREFIXES:
if s.startswith(prefix):
s = s[len(prefix):]
@@ -800,20 +869,20 @@ def filter_systems_by_target(
plat_cores_here = norm_plat_system_cores.get(norm_key, set())
if not all_cores and not plat_cores_here:
# No profile maps to this system keep it
# No profile maps to this system -keep it
filtered[sys_id] = sys_data
elif all_cores & expanded_target:
# At least one core is on the target
filtered[sys_id] = sys_data
elif not plat_cores_here:
# Platform resolution didn't find cores for this system keep it
# Platform resolution didn't find cores for this system -keep it
filtered[sys_id] = sys_data
# else: known cores exist but none are on the target exclude
# else: known cores exist but none are on the target -exclude
return filtered
# Validation and mode filtering extracted to validation.py for SoC.
# Validation and mode filtering -extracted to validation.py for SoC.
# Re-exported below for backward compatibility.
@@ -842,31 +911,35 @@ def fetch_large_file(name: str, dest_dir: str = LARGE_FILES_CACHE,
else:
return cached
encoded_name = urllib.request.quote(name)
encoded_name = urllib.parse.quote(name)
url = f"https://github.com/{LARGE_FILES_REPO}/releases/download/{LARGE_FILES_RELEASE}/{encoded_name}"
os.makedirs(dest_dir, exist_ok=True)
tmp_path = cached + ".tmp"
try:
req = urllib.request.Request(url, headers={"User-Agent": "retrobios/1.0"})
with urllib.request.urlopen(req, timeout=300) as resp:
os.makedirs(dest_dir, exist_ok=True)
with open(cached, "wb") as f:
with open(tmp_path, "wb") as f:
while True:
chunk = resp.read(65536)
if not chunk:
break
f.write(chunk)
except (urllib.error.URLError, urllib.error.HTTPError):
if os.path.exists(tmp_path):
os.unlink(tmp_path)
return None
if expected_sha1 or expected_md5:
hashes = compute_hashes(cached)
hashes = compute_hashes(tmp_path)
if expected_sha1 and hashes["sha1"].lower() != expected_sha1.lower():
os.unlink(cached)
os.unlink(tmp_path)
return None
if expected_md5:
md5_list = [m.strip().lower() for m in expected_md5.split(",") if m.strip()]
if hashes["md5"].lower() not in md5_list:
os.unlink(cached)
os.unlink(tmp_path)
return None
os.replace(tmp_path, cached)
return cached
@@ -921,12 +994,31 @@ def list_platform_system_ids(platform_name: str, platforms_dir: str) -> None:
# Re-exports: validation and truth modules extracted for SoC.
# Existing consumers import from common — these preserve that contract.
from validation import ( # noqa: F401, E402
_build_validation_index, _parse_validation, build_ground_truth,
check_file_validation, filter_files_by_mode, validate_cli_modes,
)
from truth import ( # noqa: F401, E402
diff_platform_truth, generate_platform_truth,
)
def build_target_cores_cache(
platforms: list[str],
target: str,
platforms_dir: str,
is_all: bool = False,
) -> tuple[dict[str, set[str] | None], list[str]]:
"""Build target cores cache for a list of platforms.
Returns (cache dict, list of platforms to keep after skipping failures).
"""
cache: dict[str, set[str] | None] = {}
skip: list[str] = []
for p in platforms:
try:
cache[p] = load_target_config(p, target, platforms_dir)
except FileNotFoundError:
if is_all:
cache[p] = None
else:
raise
except ValueError as e:
if is_all:
print(f"INFO: Skipping {p}: {e}")
skip.append(p)
else:
raise
kept = [p for p in platforms if p not in skip]
return cache, kept

View File

@@ -23,9 +23,7 @@ from collections.abc import Callable
from pathlib import Path
# ---------------------------------------------------------------------------
# Key file parsing (keys.txt / aes_keys.txt format)
# ---------------------------------------------------------------------------
def parse_keys_file(path: str | Path) -> dict[str, dict[str, bytes]]:
"""Parse a 3DS keys file with :AES, :RSA, :ECC sections.
@@ -67,9 +65,7 @@ def find_keys_file(bios_dir: str | Path) -> Path | None:
return None
# ---------------------------------------------------------------------------
# Pure Python RSA-2048 PKCS1v15 SHA256 verification (zero dependencies)
# ---------------------------------------------------------------------------
def _rsa_verify_pkcs1v15_sha256(
message: bytes,
@@ -79,7 +75,7 @@ def _rsa_verify_pkcs1v15_sha256(
) -> bool:
"""Verify RSA-2048 PKCS#1 v1.5 with SHA-256.
Pure Python uses Python's native int for modular exponentiation.
Pure Python -uses Python's native int for modular exponentiation.
Reproduces CryptoPP::RSASS<PKCS1v15, SHA256>::Verifier.
"""
n = int.from_bytes(modulus, "big")
@@ -124,9 +120,7 @@ def _rsa_verify_pkcs1v15_sha256(
return em == expected_em
# ---------------------------------------------------------------------------
# AES-128-CBC decryption (with fallback)
# ---------------------------------------------------------------------------
def _aes_128_cbc_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
"""Decrypt AES-128-CBC without padding."""
@@ -166,9 +160,7 @@ def _aes_128_cbc_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
)
# ---------------------------------------------------------------------------
# File verification functions
# ---------------------------------------------------------------------------
def verify_secure_info_a(
filepath: str | Path,
@@ -347,7 +339,7 @@ def verify_otp(
if computed_hash != stored_hash:
return False, "SHA-256 hash mismatch (OTP corrupted)"
# --- ECC certificate verification (sect233r1) ---
# ECC certificate verification (sect233r1)
ecc_keys = keys.get("ECC", {})
root_public_xy = ecc_keys.get("rootPublicXY")
if not root_public_xy or len(root_public_xy) != 60:
@@ -414,9 +406,7 @@ def verify_otp(
return False, "decrypted, magic+SHA256 valid, but ECC cert signature invalid"
# ---------------------------------------------------------------------------
# Unified verification interface for verify.py
# ---------------------------------------------------------------------------
# Map from (filename, validation_type) to verification function
_CRYPTO_VERIFIERS: dict[str, Callable] = {

View File

@@ -1,4 +1,4 @@
"""Deduplicate bios/ directory keep one canonical file per unique content.
"""Deduplicate bios/ directory -keep one canonical file per unique content.
Usage:
python scripts/dedup.py [--dry-run] [--bios-dir bios]
@@ -11,7 +11,7 @@ Two types of deduplication:
2. MAME DEVICE CLONES: Different filenames with identical content in the same
MAME directory (e.g., bbc_m87.zip and bbc_24bbc.zip are identical ZIPs).
These are NOT aliases MAME loads each by its unique name. Instead of
These are NOT aliases -MAME loads each by its unique name. Instead of
deleting, we create a _mame_clones.json mapping so generate_pack.py can
pack all names from a single canonical file.
@@ -94,7 +94,7 @@ def deduplicate(bios_dir: str, dry_run: bool = False) -> dict:
if len(paths) <= 1:
continue
# Separate by filename same name = true duplicate, different name = clone
# Separate by filename -same name = true duplicate, different name = clone
by_name: dict[str, list[str]] = defaultdict(list)
for p in paths:
by_name[os.path.basename(p)].append(p)
@@ -106,7 +106,7 @@ def deduplicate(bios_dir: str, dry_run: bool = False) -> dict:
name_paths.sort(key=path_priority)
true_dupes_to_remove.extend(name_paths[1:])
# Different filenames, same content need special handling
# Different filenames, same content -need special handling
unique_names = sorted(by_name.keys())
if len(unique_names) > 1:
# Check if these are all in MAME/Arcade dirs AND all ZIPs
@@ -133,7 +133,7 @@ def deduplicate(bios_dir: str, dry_run: bool = False) -> dict:
true_dupes_to_remove.append(p)
else:
# Non-MAME different names (e.g., 64DD_IPL_US.n64 vs IPL_USA.n64)
# Keep ALL each name may be needed by a different emulator
# Keep ALL -each name may be needed by a different emulator
# Only remove true duplicates (same name in multiple dirs)
pass
@@ -143,7 +143,7 @@ def deduplicate(bios_dir: str, dry_run: bool = False) -> dict:
# Find the best canonical across all paths
all_paths = [p for p in paths if p not in true_dupes_to_remove]
if not all_paths:
# All copies were marked for removal keep the best one
# All copies were marked for removal -keep the best one
all_paths_sorted = sorted(paths, key=path_priority)
all_paths = [all_paths_sorted[0]]
true_dupes_to_remove = [p for p in paths if p != all_paths[0]]

View File

@@ -1,7 +1,7 @@
"""Deterministic ZIP builder for MAME BIOS archives.
Creates byte-identical ZIP files from individual ROM atoms, enabling:
- Reproducible builds: same ROMs same ZIP hash, always
- Reproducible builds: same ROMs -> same ZIP hash, always
- Version-agnostic assembly: build neogeo.zip for any MAME version
- Deduplication: store ROM atoms once, assemble any ZIP on demand

View File

@@ -141,8 +141,8 @@ def scan_bios_dir(bios_dir: Path, cache: dict, force: bool) -> tuple[dict, dict,
def _path_suffix(rel_path: str) -> str:
"""Extract the path suffix after bios/Manufacturer/Console/.
bios/Nintendo/GameCube/GC/USA/IPL.bin GC/USA/IPL.bin
bios/Sony/PlayStation/scph5501.bin scph5501.bin
bios/Nintendo/GameCube/GC/USA/IPL.bin -> GC/USA/IPL.bin
bios/Sony/PlayStation/scph5501.bin -> scph5501.bin
"""
parts = rel_path.replace("\\", "/").split("/")
# Skip: bios / Manufacturer / Console (3 segments)

View File

@@ -27,9 +27,9 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import (
MANUFACTURER_PREFIXES,
build_zip_contents_index, check_inside_zip, compute_hashes,
fetch_large_file, group_identical_platforms, list_emulator_profiles,
list_platform_system_ids, list_registered_platforms,
build_target_cores_cache, build_zip_contents_index, check_inside_zip,
compute_hashes, fetch_large_file, group_identical_platforms,
list_emulator_profiles, list_platform_system_ids, list_registered_platforms,
filter_systems_by_target, list_system_ids, load_database,
load_data_dir_registry, load_emulator_profiles, load_platform_config,
md5_composite, require_yaml, resolve_local_file,
@@ -248,7 +248,7 @@ def resolve_file(file_entry: dict, db: dict, bios_dir: str,
if path and status != "hash_mismatch":
return path, status
# Large files from GitHub release assets tried when local file is
# Large files from GitHub release assets -tried when local file is
# missing OR has a hash mismatch (wrong variant on disk)
name = file_entry.get("name", "")
sha1 = file_entry.get("sha1")
@@ -362,7 +362,7 @@ def _collect_emulator_extras(
# Second pass: find alternative destinations for files already in the pack.
# A file declared by the platform or emitted above may also be needed at a
# different path by another core (e.g. neocd/ vs root, same_cdi/bios/ vs root).
# Only adds a copy when the file is ALREADY covered at a different path
# Only adds a copy when the file is ALREADY covered at a different path -
# never introduces a file that wasn't selected by the first pass.
profiles = emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir)
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
@@ -429,6 +429,202 @@ def _collect_emulator_extras(
return extras
def _build_readme(platform_name: str, platform_display: str,
base_dest: str, total_files: int, num_systems: int) -> str:
"""Build a personalized step-by-step README for each platform pack."""
sep = "=" * 50
header = (
f"{sep}\n"
f" RETROBIOS - {platform_display} BIOS Pack\n"
f" {total_files} files for {num_systems} systems\n"
f"{sep}\n\n"
)
guides: dict[str, str] = {
"retroarch": (
"INSTALLATION GUIDE\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Run this in a terminal:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" The script auto-detects your RetroArch install and copies\n"
" files to the correct location.\n\n"
" Option B: Manual (PC)\n"
" ---------------------\n"
" 1. Find your RetroArch system directory:\n"
" - RetroArch > Settings > Directory > System/BIOS\n"
" - Default: retroarch/system/\n"
" 2. Open the \"system\" folder from this archive\n"
" 3. Copy ALL contents into your system directory\n"
" 4. Overwrite if asked\n\n"
" Option C: Manual (handheld / SD card)\n"
" -------------------------------------\n"
" Anbernic, Retroid, Miyoo, Trimui, etc.:\n"
" 1. Connect your SD card to your PC\n"
" 2. Find the BIOS folder (usually BIOS/ or system/)\n"
" 3. Copy ALL contents of \"system\" from this archive\n"
" 4. Eject SD card and reboot your device\n\n"
" Common paths by device:\n"
" Anbernic (ArkOS/JELOS): BIOS/\n"
" Retroid (RetroArch): RetroArch/system/\n"
" Miyoo Mini (Onion OS): BIOS/\n"
" Steam Deck (RetroArch): ~/.config/retroarch/system/\n\n"
),
"batocera": (
"INSTALLATION GUIDE\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open a terminal (F1 from Batocera menu) and run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Manual (network share)\n"
" --------------------------------\n"
" 1. On your PC, open the Batocera network share:\n"
" - Windows: \\\\BATOCERA\\share\\bios\\\n"
" - Mac/Linux: smb://batocera/share/bios/\n"
" 2. Open the \"bios\" folder from this archive\n"
" 3. Copy ALL contents into the share\n"
" 4. Overwrite if asked\n\n"
" Option C: Manual (SD card)\n"
" --------------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /userdata/bios/ on the SHARE partition\n"
" 3. Copy ALL contents of \"bios\" from this archive\n\n"
" NOTE: Dreamcast flash memory is named dc_nvmem.bin\n"
" (if your setup asks for dc_flash.bin, same file).\n\n"
),
"recalbox": (
"INSTALLATION GUIDE\n\n"
" Option A: Automatic\n"
" -------------------\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Manual (network share)\n"
" --------------------------------\n"
" 1. On your PC, open the Recalbox network share:\n"
" - Windows: \\\\RECALBOX\\share\\bios\\\n"
" - Mac/Linux: smb://recalbox/share/bios/\n"
" 2. Open the \"bios\" folder from this archive\n"
" 3. Copy ALL contents into the share\n\n"
" Option C: Manual (SD card)\n"
" --------------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /recalbox/share/bios/\n"
" 3. Copy ALL contents of \"bios\" from this archive\n\n"
),
"emudeck": (
"INSTALLATION GUIDE (Steam Deck / Linux)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open Konsole (or any terminal) and run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" The script places BIOS files AND sets up standalone\n"
" emulator keys automatically.\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open Dolphin file manager\n"
" 2. Navigate to ~/Emulation/bios/\n"
" 3. Open the \"bios\" folder from this archive\n"
" 4. Copy ALL contents into ~/Emulation/bios/\n\n"
" STANDALONE EMULATORS (extra step)\n"
" Switch and 3DS emulators need keys in specific folders:\n"
" prod.keys -> ~/.local/share/yuzu/keys/\n"
" prod.keys -> ~/.local/share/eden/keys/\n"
" prod.keys -> ~/.config/Ryujinx/system/\n"
" aes_keys.txt -> ~/Emulation/bios/citra/keys/\n"
" The automatic installer handles this for you.\n\n"
),
"retrodeck": (
"INSTALLATION GUIDE (Steam Deck / Linux)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open Konsole (or any terminal) and run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open Dolphin file manager\n"
" 2. Show hidden files (Ctrl+H)\n"
" 3. Navigate to ~/retrodeck/bios/\n"
" 4. Open this archive and go into the top-level folder\n"
" 5. Copy ALL contents into ~/retrodeck/bios/\n\n"
" NOTE: RetroDECK uses its own BIOS checker. After\n"
" copying, open RetroDECK > Tools > BIOS Checker to\n"
" verify everything is detected.\n\n"
),
"retrobat": (
"INSTALLATION GUIDE (Windows)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Download and run install.bat from:\n"
" https://github.com/Abdess/retrobios/releases\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open your RetroBat installation folder\n"
" 2. Navigate to the bios\\ subfolder\n"
" (default: C:\\RetroBat\\bios\\)\n"
" 3. Open the \"bios\" folder from this archive\n"
" 4. Copy ALL contents into your bios\\ folder\n"
" 5. Overwrite if asked\n\n"
),
"bizhawk": (
"INSTALLATION GUIDE\n\n"
" 1. Open your BizHawk installation folder\n"
" 2. Navigate to the Firmware subfolder:\n"
" - Windows: BizHawk\\Firmware\\\n"
" - Linux: ~/.config/BizHawk/Firmware/\n"
" 3. Open the \"Firmware\" folder from this archive\n"
" 4. Copy ALL contents into your Firmware folder\n"
" 5. In BizHawk: Config > Paths > Firmware should\n"
" point to this folder\n\n"
),
"romm": (
"INSTALLATION GUIDE (RomM server)\n\n"
" 1. Locate your RomM library folder\n"
" 2. Navigate to the bios/ subdirectory\n"
" 3. Copy ALL contents of \"bios\" from this archive\n"
" 4. Restart the RomM service to detect new files\n\n"
),
"retropie": (
"INSTALLATION GUIDE (Raspberry Pi)\n\n"
" Option A: Via network share\n"
" --------------------------\n"
" 1. On your PC, open: \\\\RETROPIE\\bios\\\n"
" 2. Copy ALL contents of \"BIOS\" from this archive\n\n"
" Option B: Via SSH\n"
" -----------------\n"
" 1. SSH into your Pi: ssh pi@retropie\n"
" 2. Copy files to ~/RetroPie/BIOS/\n\n"
" Option C: Via SD card\n"
" ---------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /home/pi/RetroPie/BIOS/\n"
" 3. Copy ALL contents of \"BIOS\" from this archive\n\n"
),
}
# Lakka uses same guide as RetroArch
guides["lakka"] = guides["retroarch"]
guide = guides.get(platform_name, (
f"INSTALLATION\n\n"
f" 1. Open the \"{base_dest or 'files'}\" folder in this archive\n"
f" 2. Copy ALL contents to your BIOS directory\n"
f" 3. Overwrite if asked\n\n"
))
footer = (
"TROUBLESHOOTING\n\n"
" - Core says BIOS missing? Check the exact filename\n"
" and make sure it's in the right subfolder.\n"
" - Wrong region? Some systems have regional BIOS\n"
" variants (USA/EUR/JAP). All are included.\n"
" - Need help? https://github.com/Abdess/retrobios/issues\n\n"
f"{sep}\n"
f" https://github.com/Abdess/retrobios\n"
f"{sep}\n"
)
return header + guide + footer
def generate_pack(
platform_name: str,
platforms_dir: str,
@@ -635,7 +831,7 @@ def generate_pack(
# Emulator-level validation: informational only for platform packs.
# Platform verification (existence/md5) is the authority for pack status.
# Emulator checks are supplementary logged but don't downgrade.
# Emulator checks are supplementary -logged but don't downgrade.
# When a discrepancy is found, try to find a file satisfying both.
if (file_status.get(dedup_key) == "ok"
and local_path and validation_index):
@@ -696,7 +892,7 @@ def generate_pack(
if base_dest:
full_dest = f"{base_dest}/{dest}"
elif "/" not in dest:
# Bare filename with empty base_destination infer bios/ prefix
# Bare filename with empty base_destination -infer bios/ prefix
# to match platform conventions (RetroDECK: ~/retrodeck/bios/)
full_dest = f"bios/{dest}"
else:
@@ -740,7 +936,7 @@ def generate_pack(
continue
local_path = entry.get("local_cache", "")
if not local_path or not os.path.isdir(local_path):
print(f" WARNING: data directory '{ref_key}' not cached at {local_path} run refresh_data_dirs.py")
print(f" WARNING: data directory '{ref_key}' not cached at {local_path} -run refresh_data_dirs.py")
continue
dd_dest = dd.get("destination", "")
if base_dest and dd_dest:
@@ -754,7 +950,7 @@ 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 or full.lower() in seen_lower and case_insensitive:
if full in seen_destinations or (full.lower() in seen_lower and case_insensitive):
continue
if _has_path_conflict(full, seen_destinations, seen_parents):
continue
@@ -765,66 +961,10 @@ def generate_pack(
zf.write(src, full)
total_files += 1
# README.txt for users
extract_paths = {
"retroarch": "system/", "lakka": "system/",
"batocera": "/userdata/bios/", "recalbox": "/recalbox/share/bios/",
"emudeck": "Emulation/bios/", "retrobat": "bios/",
"retrodeck": "~/retrodeck/bios/", "romm": "bios/",
"bizhawk": "Firmware/", "retropie": "BIOS/",
}
extract_to = extract_paths.get(platform_name, f"{base_dest}/")
# README.txt for users -personalized step-by-step per platform
num_systems = len(pack_systems)
# Platform-specific notes
platform_notes = {
"emudeck": (
"\nSTANDALONE EMULATORS\n"
" Switch emulators (Yuzu, Eden, Ryujinx) and Citra need\n"
" keys in their own folders, not in Emulation/bios/.\n"
" Use the automatic installer to set this up, or copy\n"
" manually:\n"
" prod.keys -> ~/.local/share/yuzu/keys/\n"
" prod.keys -> ~/.local/share/eden/keys/\n"
" prod.keys -> ~/.config/Ryujinx/system/\n"
" aes_keys.txt -> Emulation/bios/citra/keys/\n\n"
),
"retroarch": (
"\nHANDHELDS (Anbernic, Retroid, Miyoo, etc.)\n"
" Copy the contents of \"system/\" to your SD card's\n"
" BIOS folder (usually BIOS/ or system/).\n\n"
),
"batocera": (
"\nDREAMCAST NOTE\n"
" The flash memory file is named dc_nvmem.bin\n"
" (Flycast's canonical name). If your setup asks for\n"
" dc_flash.bin, it serves the same purpose.\n\n"
),
}
extra_notes = platform_notes.get(platform_name, "")
# Lakka shares RetroArch notes
if platform_name == "lakka":
extra_notes = platform_notes.get("retroarch", "")
readme_text = (
f"{'=' * 43}\n"
f" RETROBIOS - {platform_display} BIOS Pack\n"
f" {total_files} files for {num_systems} systems\n"
f"{'=' * 43}\n\n"
f"HOW TO INSTALL\n\n"
f" 1. Open the \"{base_dest or 'files'}\" folder in this archive\n"
f" 2. Select everything inside (Ctrl+A)\n"
f" 3. Copy (Ctrl+C)\n"
f" 4. Go to: {extract_to}\n"
f" 5. Paste (Ctrl+V)\n\n"
f"IMPORTANT\n"
f" - Copy the FILES, not the folder itself\n"
f" - If asked to replace, click Yes\n"
f"{extra_notes}"
f"AUTOMATIC INSTALL (recommended)\n"
f" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
f"PROJECT: https://github.com/Abdess/retrobios\n"
f"{'=' * 43}\n"
)
readme_text = _build_readme(platform_name, platform_display,
base_dest, total_files, num_systems)
zf.writestr("README.txt", readme_text)
files_ok = sum(1 for s in file_status.values() if s == "ok")
@@ -843,7 +983,7 @@ def generate_pack(
for key, reason in sorted(file_reasons.items()):
status = file_status.get(key, "")
label = "UNTESTED" if status == "untested" else "DISCREPANCY"
print(f" {label}: {key} {reason}")
print(f" {label}: {key} -{reason}")
for name in missing_files:
print(f" MISSING: {name}")
return zip_path
@@ -871,7 +1011,7 @@ def _normalize_zip_for_pack(source_zip: str, dest_path: str, target_zf: zipfile.
the normalized version into the pack.
This ensures:
- Same ROMs same ZIP hash in every pack build
- Same ROMs -> same ZIP hash in every pack build
- No dependency on how the user built their MAME ROM set
- Bit-identical ZIPs across platforms and build times
"""
@@ -885,9 +1025,7 @@ def _normalize_zip_for_pack(source_zip: str, dest_path: str, target_zf: zipfile.
os.unlink(tmp_path)
# ---------------------------------------------------------------------------
# Emulator/system mode pack generation
# ---------------------------------------------------------------------------
def _resolve_destination(file_entry: dict, pack_structure: dict | None,
standalone: bool) -> str:
@@ -941,11 +1079,11 @@ def generate_emulator_pack(
p = all_profiles[name]
if p.get("type") == "alias":
alias_of = p.get("alias_of", "?")
print(f"Error: {name} is an alias of {alias_of} use --emulator {alias_of}",
print(f"Error: {name} is an alias of {alias_of} -use --emulator {alias_of}",
file=sys.stderr)
return None
if p.get("type") == "launcher":
print(f"Error: {name} is a launcher use the emulator it launches",
print(f"Error: {name} is a launcher -use the emulator it launches",
file=sys.stderr)
return None
ptype = p.get("type", "libretro")
@@ -1773,25 +1911,13 @@ def main():
target_cores_cache: dict[str, set[str] | None] = {}
if args.target:
from common import load_target_config
skip = []
for p in platforms:
try:
target_cores_cache[p] = load_target_config(p, args.target, args.platforms_dir)
except FileNotFoundError:
if args.all:
target_cores_cache[p] = None
else:
print(f"ERROR: No target config for platform '{p}'", file=sys.stderr)
sys.exit(1)
except ValueError as e:
if args.all:
print(f"INFO: Skipping {p}: {e}")
skip.append(p)
else:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
platforms = [p for p in platforms if p not in skip]
try:
target_cores_cache, platforms = build_target_cores_cache(
platforms, args.target, args.platforms_dir, is_all=args.all,
)
except (FileNotFoundError, ValueError) as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
groups = group_identical_platforms(platforms, args.platforms_dir,
target_cores_cache if args.target else None)
@@ -1803,9 +1929,7 @@ def main():
emu_profiles, target_cores_cache, system_filter)
# ---------------------------------------------------------------------------
# Manifest generation (JSON inventory for install.py)
# ---------------------------------------------------------------------------
_GITIGNORE_ENTRIES: set[str] | None = None
@@ -2011,7 +2135,7 @@ def generate_manifest(
if case_insensitive:
seen_lower.add(full_dest.lower())
# No phase 3 (data directories) skipped for manifest
# No phase 3 (data directories) -skipped for manifest
now = __import__("datetime").datetime.now(
__import__("datetime").timezone.utc
@@ -2033,9 +2157,7 @@ def generate_manifest(
return result
# ---------------------------------------------------------------------------
# Post-generation pack verification + manifest + SHA256SUMS
# ---------------------------------------------------------------------------
def verify_pack(zip_path: str, db: dict,
data_registry: dict | None = None) -> tuple[bool, dict]:
@@ -2334,7 +2456,7 @@ def verify_pack_against_platform(
if full in zip_set or full.lower() in zip_lower:
core_present += 1
# Not an error if missing some get deduped or filtered
# Not an error if missing -some get deduped or filtered
checked = baseline_checked + core_checked
present = baseline_present + core_present

View File

@@ -189,9 +189,7 @@ def _status_icon(pct: float) -> str:
return "partial"
# ---------------------------------------------------------------------------
# Home page
# ---------------------------------------------------------------------------
def generate_home(db: dict, coverages: dict, profiles: dict,
registry: dict | None = None) -> str:
@@ -303,9 +301,7 @@ def generate_home(db: dict, coverages: dict, profiles: dict,
return "\n".join(lines) + "\n"
# ---------------------------------------------------------------------------
# Platform pages
# ---------------------------------------------------------------------------
def generate_platform_index(coverages: dict) -> str:
lines = [
@@ -478,9 +474,7 @@ def generate_platform_page(name: str, cov: dict, registry: dict | None = None,
return "\n".join(lines) + "\n"
# ---------------------------------------------------------------------------
# System pages
# ---------------------------------------------------------------------------
def _group_by_manufacturer(db: dict) -> dict[str, dict[str, list]]:
"""Group files by manufacturer -> console -> files."""
@@ -572,9 +566,7 @@ def generate_system_page(
return "\n".join(lines) + "\n"
# ---------------------------------------------------------------------------
# Emulator pages
# ---------------------------------------------------------------------------
def generate_emulators_index(profiles: dict) -> str:
unique = {k: v for k, v in profiles.items() if v.get("type") not in ("alias", "test")}
@@ -1011,9 +1003,7 @@ def generate_emulator_page(name: str, profile: dict, db: dict,
return "\n".join(lines) + "\n"
# ---------------------------------------------------------------------------
# Contributing page
# ---------------------------------------------------------------------------
def generate_gap_analysis(
profiles: dict,
@@ -1367,9 +1357,7 @@ The CI automatically:
"""
# ---------------------------------------------------------------------------
# Wiki pages
# ---------------------------------------------------------------------------
def generate_wiki_index() -> str:
"""Generate wiki landing page."""
@@ -1924,9 +1912,7 @@ def generate_wiki_data_model(db: dict, profiles: dict) -> str:
return "\n".join(lines) + "\n"
# ---------------------------------------------------------------------------
# Build cross-reference indexes
# ---------------------------------------------------------------------------
def _build_platform_file_index(coverages: dict) -> dict[str, set]:
"""Map platform_name -> set of declared file names."""
@@ -1954,9 +1940,7 @@ def _build_emulator_file_index(profiles: dict) -> dict[str, dict]:
return index
# ---------------------------------------------------------------------------
# mkdocs.yml nav generator
# ---------------------------------------------------------------------------
def generate_mkdocs_nav(
coverages: dict,
@@ -2028,9 +2012,7 @@ def generate_mkdocs_nav(
]
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Generate MkDocs site from project data")
@@ -2053,14 +2035,12 @@ def main():
for d in GENERATED_DIRS:
(docs / d).mkdir(parents=True, exist_ok=True)
# Load registry for platform metadata (logos, etc.)
registry_path = Path(args.platforms_dir) / "_registry.yml"
registry = {}
if registry_path.exists():
with open(registry_path) as f:
registry = (yaml.safe_load(f) or {}).get("platforms", {})
# Load platform configs
platform_names = list_registered_platforms(args.platforms_dir, include_archived=True)
print("Computing platform coverage...")
@@ -2073,7 +2053,6 @@ def main():
except FileNotFoundError as e:
print(f" {name}: skipped ({e})", file=sys.stderr)
# Load emulator profiles
print("Loading emulator profiles...")
profiles = load_emulator_profiles(args.emulators_dir, skip_aliases=False)
unique_count = sum(1 for p in profiles.values() if p.get("type") != "alias")

View File

@@ -47,6 +47,24 @@ class ChangeSet:
return ", ".join(parts) if parts else "no changes"
MAX_RESPONSE_SIZE = 50 * 1024 * 1024 # 50 MB
def _read_limited(resp: object, max_bytes: int = MAX_RESPONSE_SIZE) -> bytes:
"""Read an HTTP response with a size limit to prevent OOM."""
chunks: list[bytes] = []
total = 0
while True:
chunk = resp.read(65536) # type: ignore[union-attr]
if not chunk:
break
total += len(chunk)
if total > max_bytes:
raise ValueError(f"Response exceeds {max_bytes} byte limit")
chunks.append(chunk)
return b"".join(chunks)
class BaseScraper(ABC):
"""Abstract base class for platform BIOS requirement scrapers."""
@@ -63,7 +81,7 @@ class BaseScraper(ABC):
try:
req = urllib.request.Request(self.url, headers={"User-Agent": "retrobios-scraper/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
self._raw_data = resp.read().decode("utf-8")
self._raw_data = _read_limited(resp).decode("utf-8")
return self._raw_data
except urllib.error.URLError as e:
raise ConnectionError(f"Failed to fetch {self.url}: {e}") from e

View File

@@ -65,7 +65,7 @@ SYSTEM_ID_MAP: dict[str, str] = {
"Amiga": "commodore-amiga",
"AmstradCPC": "amstrad-cpc",
"AppleII": "apple-ii",
"BSX": "nintendo-bsx",
"BSX": "nintendo-satellaview",
"C64": "commodore-c64",
"ChannelF": "fairchild-channel-f",
"Coleco": "coleco-colecovision",

View File

@@ -24,12 +24,12 @@ SOURCE_URL = (
# Libretro cores that expect BIOS files in a subdirectory of system/.
# System.dat lists filenames flat; the scraper prepends the prefix.
# ref: each core's libretro.c or equivalent see platforms/README.md
# ref: each core's libretro.c or equivalent -see platforms/README.md
CORE_SUBDIR_MAP = {
"nec-pc-98": "np2kai", # libretro-np2kai/sdl/libretro.c
"sharp-x68000": "keropi", # px68k/libretro/libretro.c
"sega-dreamcast": "dc", # flycast/shell/libretro/libretro.cpp
"sega-dreamcast-arcade": "dc", # flycast same subfolder
"sega-dreamcast-arcade": "dc", # flycast -same subfolder
}
SYSTEM_SLUG_MAP = {
@@ -254,7 +254,7 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(entry)
# Systems not in System.dat but needed for RetroArch added via
# Systems not in System.dat but needed for RetroArch -added via
# shared groups in _shared.yml. The includes directive is resolved
# at load time by load_platform_config().
EXTRA_SYSTEMS = {
@@ -264,7 +264,7 @@ class Scraper(BaseScraper):
"manufacturer": "NEC",
"docs": "https://docs.libretro.com/library/quasi88/",
},
# ref: Vircon32/libretro.c virtual console, single BIOS
# ref: Vircon32/libretro.c -virtual console, single BIOS
"vircon32": {
"files": [
{"name": "Vircon32Bios.v32", "destination": "Vircon32Bios.v32", "required": True},
@@ -273,7 +273,7 @@ class Scraper(BaseScraper):
"manufacturer": "Vircon",
"docs": "https://docs.libretro.com/library/vircon32/",
},
# ref: xrick/src/sysvid.c, xrick/src/data.c game data archive
# ref: xrick/src/sysvid.c, xrick/src/data.c -game data archive
"xrick": {
"files": [
{"name": "data.zip", "destination": "xrick/data.zip", "required": True},
@@ -290,7 +290,7 @@ class Scraper(BaseScraper):
# Arcade BIOS present in the repo but absent from System.dat.
# FBNeo expects them in system/ or system/fbneo/.
# ref: fbneo/src/burner/libretro/libretro.cpp
# ref: fbneo/src/burner/libretro/libretro.cpp search order:
# ref: fbneo/src/burner/libretro/libretro.cpp -search order:
# 1) romset dir 2) system/fbneo/ 3) system/
EXTRA_ARCADE_FILES = [
{"name": "namcoc69.zip", "destination": "namcoc69.zip", "required": True},
@@ -329,33 +329,33 @@ class Scraper(BaseScraper):
# Extra files missing from System.dat for specific systems.
# Each traced to the core's source code.
EXTRA_SYSTEM_FILES = {
# melonDS DS DSi mode ref: JesseTG/melonds-ds/src/libretro.cpp
# melonDS DS DSi mode -ref: JesseTG/melonds-ds/src/libretro.cpp
"nintendo-ds": [
{"name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", "required": True},
{"name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", "required": True},
{"name": "dsi_firmware.bin", "destination": "dsi_firmware.bin", "required": True},
{"name": "dsi_nand.bin", "destination": "dsi_nand.bin", "required": True},
],
# bsnes SGB naming ref: bsnes/target-libretro/libretro.cpp
# bsnes SGB naming -ref: bsnes/target-libretro/libretro.cpp
"nintendo-sgb": [
{"name": "sgb.boot.rom", "destination": "sgb.boot.rom", "required": False},
],
# JollyCV ref: jollycv/libretro.c
# JollyCV -ref: jollycv/libretro.c
"coleco-colecovision": [
{"name": "BIOS.col", "destination": "BIOS.col", "required": True},
{"name": "coleco.rom", "destination": "coleco.rom", "required": True},
{"name": "bioscv.rom", "destination": "bioscv.rom", "required": True},
],
# Kronos ST-V ref: libretro-kronos/libretro/libretro.c
# Kronos ST-V -ref: libretro-kronos/libretro/libretro.c
"sega-saturn": [
{"name": "stvbios.zip", "destination": "kronos/stvbios.zip", "required": True},
],
# PCSX ReARMed / Beetle PSX alt BIOS ref: pcsx_rearmed/libpcsxcore/misc.c
# PCSX ReARMed / Beetle PSX alt BIOS -ref: pcsx_rearmed/libpcsxcore/misc.c
# docs say PSXONPSP660.bin (uppercase) but core accepts any case
"sony-playstation": [
{"name": "psxonpsp660.bin", "destination": "psxonpsp660.bin", "required": False},
],
# Dolphin GC ref: DolphinLibretro/Boot.cpp:72-73,
# Dolphin GC -ref: DolphinLibretro/Boot.cpp:72-73,
# BootManager.cpp:200-217, CommonPaths.h:139 GC_IPL="IPL.bin"
# Core searches system/dolphin-emu/Sys/ for data and BIOS.
# System.dat gc-ntsc-*.bin names are NOT what Dolphin loads.
@@ -364,15 +364,15 @@ class Scraper(BaseScraper):
{"name": "gc-ntsc-12.bin", "destination": "dolphin-emu/Sys/GC/USA/IPL.bin", "required": False},
{"name": "gc-pal-12.bin", "destination": "dolphin-emu/Sys/GC/EUR/IPL.bin", "required": False},
{"name": "gc-ntsc-12.bin", "destination": "dolphin-emu/Sys/GC/JAP/IPL.bin", "required": False},
# DSP firmware ref: Source/Core/Core/HW/DSPLLE/DSPHost.cpp
# DSP firmware -ref: Source/Core/Core/HW/DSPLLE/DSPHost.cpp
{"name": "dsp_coef.bin", "destination": "dolphin-emu/Sys/GC/dsp_coef.bin", "required": True},
{"name": "dsp_rom.bin", "destination": "dolphin-emu/Sys/GC/dsp_rom.bin", "required": True},
# Fonts ref: Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp
# Fonts -ref: Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp
{"name": "font_western.bin", "destination": "dolphin-emu/Sys/GC/font_western.bin", "required": False},
{"name": "font_japanese.bin", "destination": "dolphin-emu/Sys/GC/font_japanese.bin", "required": False},
],
# minivmac casing ref: minivmac/src/MYOSGLUE.c
# doc says MacII.rom, repo has MacII.ROM both work on case-insensitive FS
# minivmac casing -ref: minivmac/src/MYOSGLUE.c
# doc says MacII.rom, repo has MacII.ROM -both work on case-insensitive FS
"apple-macintosh-ii": [
{"name": "MacII.ROM", "destination": "MacII.ROM", "required": True},
],
@@ -398,7 +398,7 @@ class Scraper(BaseScraper):
# Inject shared group references for systems that have core-specific
# subdirectory requirements already defined in _shared.yml.
# Note: fuse/ prefix NOT injected for sinclair-zx-spectrum.
# Verified in fuse-libretro/src/compat/paths.c core searches
# Verified in fuse-libretro/src/compat/paths.c -core searches
# system/ flat, not fuse/ subfolder. Docs are wrong on this.
SYSTEM_SHARED_GROUPS = {
"nec-pc-98": ["np2kai"],
@@ -421,12 +421,12 @@ class Scraper(BaseScraper):
{"ref": "ppsspp-assets", "destination": "PPSSPP"},
],
# single buildbot ZIP contains both Databases/ and Machines/
# ref: libretro.c:1118-1119 system_dir/Machines + system_dir/Databases
# ref: libretro.c:1118-1119 -system_dir/Machines + system_dir/Databases
"microsoft-msx": [
{"ref": "bluemsx", "destination": ""},
],
# FreeIntv overlays system/freeintv_overlays/<rom>.png
# ref: FreeIntv/src/libretro.c:273 stbi_load from system dir
# FreeIntv overlays -system/freeintv_overlays/<rom>.png
# ref: FreeIntv/src/libretro.c:273 -stbi_load from system dir
# ZIP contains FreeIntvTS_Overlays/ subfolder, cache preserves it
# pack destination maps cache root to system/freeintv_overlays
# so final path is system/freeintv_overlays/FreeIntvTS_Overlays/<rom>.png

View File

@@ -171,7 +171,7 @@ def _resolve_path(p: str) -> str:
def _extract_bios_entries(component_val: dict) -> list[dict]:
"""Extract BIOS entries from all three possible locations in a component.
No dedup here dedup is done in fetch_requirements() with full
No dedup here -dedup is done in fetch_requirements() with full
(system, filename) key to avoid dropping valid same-filename entries
across different systems.
"""
@@ -338,13 +338,13 @@ class Scraper(BaseScraper):
if resolved.startswith("saves"):
continue
# Build destination default to bios/ if no path specified
# Build destination -default to bios/ if no path specified
if resolved:
destination = f"{resolved}/{filename}"
else:
destination = f"bios/{filename}"
# MD5 handling sanitize upstream errors
# MD5 handling -sanitize upstream errors
md5_raw = entry.get("md5", "")
if isinstance(md5_raw, list):
parts = [str(m).strip().lower() for m in md5_raw if m]

View File

@@ -6,18 +6,20 @@ from __future__ import annotations
import importlib
import pkgutil
from abc import ABC, abstractmethod
from pathlib import Path
class BaseTargetScraper:
class BaseTargetScraper(ABC):
"""Base class for target scrapers."""
def __init__(self, url: str = ""):
self.url = url
@abstractmethod
def fetch_targets(self) -> dict:
"""Fetch targets and their core lists. Returns dict matching target YAML format."""
raise NotImplementedError
...
def write_output(self, data: dict, output_path: str) -> None:
"""Write target data to YAML file."""

View File

@@ -136,7 +136,7 @@ def _check_atom(tokens: list[str], pos: int, active: frozenset[str]) -> tuple[bo
if tok.startswith('"'):
pos += 1
return True, pos
# Unknown token treat as true to avoid false negatives
# Unknown token -treat as true to avoid false negatives
pos += 1
return True, pos
@@ -152,7 +152,7 @@ def _condition_holds(condition: str, active: frozenset[str]) -> bool:
try:
result, _ = _check_condition(tokens, 0, active)
return result
except Exception:
except (IndexError, ValueError, TypeError):
return True # conservative: include on parse failure

View File

@@ -1,8 +1,8 @@
"""Scraper for EmuDeck emulator targets.
Sources:
SteamOS: dragoonDorise/EmuDeck functions/EmuScripts/*.sh
Windows: EmuDeck/emudeck-we functions/EmuScripts/*.ps1
SteamOS: dragoonDorise/EmuDeck -functions/EmuScripts/*.sh
Windows: EmuDeck/emudeck-we -functions/EmuScripts/*.ps1
"""
from __future__ import annotations

View File

@@ -56,7 +56,7 @@ TARGETS: list[tuple[str, str, str]] = [
("nintendo/wiiu/latest", "nintendo-wiiu", "ppc"),
("playstation/ps2/latest", "playstation-ps2", "mips"),
("playstation/psp/latest", "playstation-psp", "mips"),
# vita: only VPK bundles on buildbot cores listed via libretro-super recipes
# vita: only VPK bundles on buildbot -cores listed via libretro-super recipes
]
# Recipe-based targets: (recipe_path_under_RECIPE_BASE_URL, target_name, architecture)

View File

@@ -3,7 +3,7 @@
Implements GF(2^233) field arithmetic, elliptic curve point operations,
and ECDSA-SHA256 verification for Nintendo 3DS OTP certificate checking.
Zero external dependencies uses only Python stdlib.
Zero external dependencies -uses only Python stdlib.
Curve: sect233r1 (NIST B-233, SEC 2 v2)
Field: GF(2^233) with irreducible polynomial t^233 + t^74 + 1
@@ -13,9 +13,7 @@ from __future__ import annotations
import hashlib
# ---------------------------------------------------------------------------
# sect233r1 curve parameters (SEC 2 v2)
# ---------------------------------------------------------------------------
_M = 233
_F = (1 << 233) | (1 << 74) | 1 # irreducible polynomial
@@ -34,9 +32,7 @@ _N_BITLEN = _N.bit_length() # 233
_H = 2
# ---------------------------------------------------------------------------
# GF(2^233) field arithmetic
# ---------------------------------------------------------------------------
def _gf_reduce(a: int) -> int:
"""Reduce polynomial a modulo t^233 + t^74 + 1."""
@@ -85,7 +81,7 @@ def _gf_inv(a: int) -> int:
q ^= 1 << shift
temp ^= r << shift
remainder = temp
# Multiply q * s in GF(2)[x] (no reduction working in polynomial ring)
# Multiply q * s in GF(2)[x] (no reduction -working in polynomial ring)
qs = 0
qt = q
st = s
@@ -102,10 +98,8 @@ def _gf_inv(a: int) -> int:
return _gf_reduce(old_s)
# ---------------------------------------------------------------------------
# Elliptic curve point operations on sect233r1
# y^2 + xy = x^3 + ax^2 + b (a=1)
# ---------------------------------------------------------------------------
# Point at infinity
_INF = None
@@ -175,9 +169,7 @@ def _ec_mul(k: int, p: tuple[int, int] | None) -> tuple[int, int] | None:
return result
# ---------------------------------------------------------------------------
# ECDSA-SHA256 verification
# ---------------------------------------------------------------------------
def _modinv(a: int, m: int) -> int:
"""Modular inverse of a modulo m (integers, not GF(2^m))."""

View File

@@ -217,7 +217,7 @@ def generate_platform_truth(
sys_cov["profiled"].add(emu_name)
# Ensure all systems of resolved cores have entries (even with 0 files).
# This documents that the system is covered the core was analyzed and
# This documents that the system is covered -the core was analyzed and
# needs no external files for this system.
for emu_name in cores_profiled:
profile = profiles[emu_name]
@@ -261,9 +261,7 @@ def generate_platform_truth(
}
# -------------------------------------------------------------------
# Platform truth diffing
# -------------------------------------------------------------------
def _diff_system(truth_sys: dict, scraped_sys: dict) -> dict:
"""Compare files between truth and scraped for a single system."""
@@ -430,7 +428,7 @@ def diff_platform_truth(truth: dict, scraped: dict) -> dict:
else:
summary["systems_fully_covered"] += 1
# Truth systems not matched by any scraped system all files missing
# Truth systems not matched by any scraped system -all files missing
for t_sid in sorted(truth_systems):
if t_sid in matched_truth:
continue

View File

@@ -12,7 +12,7 @@ import os
from common import compute_hashes
# Validation types that require console-specific cryptographic keys.
# verify.py cannot reproduce these size checks still apply if combined.
# verify.py cannot reproduce these -size checks still apply if combined.
_CRYPTO_CHECKS = frozenset({"signature", "crypto"})
# All reproducible validation types.
@@ -85,7 +85,7 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
if f.get("max_size") is not None:
cur = index[fname]["max_size"]
index[fname]["max_size"] = max(cur, f["max_size"]) if cur is not None else f["max_size"]
# Hash checks collect all accepted hashes as sets (multiple valid
# Hash checks -collect all accepted hashes as sets (multiple valid
# versions of the same file, e.g. MT-32 ROM versions)
if "crc32" in checks and f.get("crc32"):
crc_val = f["crc32"]
@@ -103,7 +103,7 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
index[fname][hash_type].add(str(h).lower())
else:
index[fname][hash_type].add(str(val).lower())
# Adler32 stored as known_hash_adler32 field (not in validation: list
# Adler32 -stored as known_hash_adler32 field (not in validation: list
# for Dolphin, but support it in both forms for future profiles)
adler_val = f.get("known_hash_adler32") or f.get("adler32")
if adler_val:
@@ -186,7 +186,7 @@ def check_file_validation(
return None
checks = entry["checks"]
# Size checks sizes is a set of accepted values
# Size checks -sizes is a set of accepted values
if "size" in checks:
actual_size = os.path.getsize(local_path)
if entry["sizes"] and actual_size not in entry["sizes"]:
@@ -197,7 +197,7 @@ def check_file_validation(
if entry["max_size"] is not None and actual_size > entry["max_size"]:
return f"size too large: max {entry['max_size']}, got {actual_size}"
# Hash checks compute once, reuse for all hash types.
# Hash checks -compute once, reuse for all hash types.
# Each hash field is a set of accepted values (multiple valid ROM versions).
need_hashes = (
any(h in checks and entry.get(h) for h in ("crc32", "md5", "sha1", "sha256"))

View File

@@ -30,11 +30,11 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import (
build_zip_contents_index, check_inside_zip, compute_hashes,
filter_systems_by_target, group_identical_platforms, list_emulator_profiles,
list_system_ids, load_data_dir_registry, load_emulator_profiles,
load_platform_config, md5sum, md5_composite, require_yaml, resolve_local_file,
resolve_platform_cores,
build_target_cores_cache, build_zip_contents_index, check_inside_zip,
compute_hashes, filter_systems_by_target, group_identical_platforms,
list_emulator_profiles, list_system_ids, load_data_dir_registry,
load_emulator_profiles, load_platform_config, md5sum, md5_composite,
require_yaml, resolve_local_file, resolve_platform_cores,
)
yaml = require_yaml()
@@ -47,9 +47,7 @@ DEFAULT_PLATFORMS_DIR = "platforms"
DEFAULT_EMULATORS_DIR = "emulators"
# ---------------------------------------------------------------------------
# Status model — aligned with Batocera BiosStatus (batocera-systems:967-969)
# ---------------------------------------------------------------------------
# Status model -aligned with Batocera BiosStatus (batocera-systems:967-969)
class Status:
OK = "ok"
@@ -68,15 +66,13 @@ _STATUS_ORDER = {Status.OK: 0, Status.UNTESTED: 1, Status.MISSING: 2}
_SEVERITY_ORDER = {Severity.OK: 0, Severity.INFO: 1, Severity.WARNING: 2, Severity.CRITICAL: 3}
# ---------------------------------------------------------------------------
# Verification functions
# ---------------------------------------------------------------------------
def verify_entry_existence(
file_entry: dict, local_path: str | None,
validation_index: dict[str, dict] | None = None,
) -> dict:
"""RetroArch verification: path_is_valid() file exists = OK."""
"""RetroArch verification: path_is_valid() -file exists = OK."""
name = file_entry.get("name", "")
required = file_entry.get("required", True)
if not local_path:
@@ -96,7 +92,7 @@ def verify_entry_md5(
local_path: str | None,
resolve_status: str = "",
) -> dict:
"""MD5 verification Batocera md5sum + Recalbox multi-hash + Md5Composite."""
"""MD5 verification -Batocera md5sum + Recalbox multi-hash + Md5Composite."""
name = file_entry.get("name", "")
expected_md5 = file_entry.get("md5", "")
zipped_file = file_entry.get("zipped_file")
@@ -162,7 +158,7 @@ def verify_entry_sha1(
file_entry: dict,
local_path: str | None,
) -> dict:
"""SHA1 verification BizHawk firmware hash check."""
"""SHA1 verification -BizHawk firmware hash check."""
name = file_entry.get("name", "")
expected_sha1 = file_entry.get("sha1", "")
required = file_entry.get("required", True)
@@ -183,20 +179,18 @@ def verify_entry_sha1(
"reason": f"expected {expected_sha1[:12]}… got {actual_sha1[:12]}"}
# ---------------------------------------------------------------------------
# Severity mapping per platform
# ---------------------------------------------------------------------------
def compute_severity(
status: str, required: bool, mode: str, hle_fallback: bool = False,
) -> str:
"""Map (status, required, verification_mode, hle_fallback) severity.
"""Map (status, required, verification_mode, hle_fallback) -> severity.
Based on native platform behavior + emulator HLE capability:
- RetroArch (existence): required+missing = warning, optional+missing = info
- Batocera/Recalbox/RetroBat/EmuDeck (md5): hash-based verification
- BizHawk (sha1): same severity rules as md5
- hle_fallback: core works without this file via HLE always INFO when missing
- hle_fallback: core works without this file via HLE -> always INFO when missing
"""
if status == Status.OK:
return Severity.OK
@@ -218,13 +212,9 @@ def compute_severity(
return Severity.OK
# ---------------------------------------------------------------------------
# ZIP content index
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Cross-reference: undeclared files used by cores
# ---------------------------------------------------------------------------
def _build_expected(file_entry: dict, checks: list[str]) -> dict:
@@ -447,7 +437,7 @@ def find_exclusion_notes(
})
continue
# Count standalone-only files but only report as excluded if the
# Count standalone-only files -but only report as excluded if the
# platform does NOT use this emulator in standalone mode
standalone_set = set(str(c) for c in config.get("standalone_cores", []))
is_standalone = emu_name in standalone_set or bool(
@@ -466,9 +456,7 @@ def find_exclusion_notes(
return notes
# ---------------------------------------------------------------------------
# Platform verification
# ---------------------------------------------------------------------------
def _find_best_variant(
file_entry: dict, db: dict, current_path: str,
@@ -504,6 +492,7 @@ def verify_platform(
emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
data_dir_registry: dict | None = None,
supplemental_names: set[str] | None = None,
) -> dict:
"""Verify all BIOS files for a platform, including cross-reference gaps."""
mode = config.get("verification_mode", "existence")
@@ -601,10 +590,11 @@ def verify_platform(
status_counts[s] = status_counts.get(s, 0) + 1
# Cross-reference undeclared files
from cross_reference import _build_supplemental_index
data_names = _build_supplemental_index()
if supplemental_names is None:
from cross_reference import _build_supplemental_index
supplemental_names = _build_supplemental_index()
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles,
target_cores=target_cores, data_names=data_names)
target_cores=target_cores, data_names=supplemental_names)
exclusions = find_exclusion_notes(config, emulators_dir, emu_profiles, target_cores=target_cores)
# Ground truth coverage
@@ -638,9 +628,7 @@ def verify_platform(
}
# ---------------------------------------------------------------------------
# Output
# ---------------------------------------------------------------------------
def _format_ground_truth_aggregate(ground_truth: list[dict]) -> str:
"""Format ground truth as a single aggregated line.
@@ -696,7 +684,7 @@ def _print_detail_entries(details: list[dict], seen: set[str], verbose: bool) ->
req = "required" if d.get("required", True) else "optional"
hle = ", HLE available" if d.get("hle_fallback") else ""
reason = d.get("reason", "")
print(f" UNTESTED ({req}{hle}): {key} {reason}")
print(f" UNTESTED ({req}{hle}): {key} -{reason}")
_print_ground_truth(d.get("ground_truth", []), verbose)
for d in details:
if d["status"] == Status.MISSING:
@@ -715,7 +703,7 @@ def _print_detail_entries(details: list[dict], seen: set[str], verbose: bool) ->
if key in seen:
continue
seen.add(key)
print(f" DISCREPANCY: {key} {disc}")
print(f" DISCREPANCY: {key} -{disc}")
_print_ground_truth(d.get("ground_truth", []), verbose)
if verbose:
@@ -829,7 +817,7 @@ def print_platform_result(result: dict, group: list[str], verbose: bool = False)
if exclusions:
print(f" No external files ({len(exclusions)}):")
for ex in exclusions:
print(f" {ex['emulator']} {ex['detail']} [{ex['reason']}]")
print(f" {ex['emulator']} -{ex['detail']} [{ex['reason']}]")
gt_cov = result.get("ground_truth_coverage")
if gt_cov and gt_cov["total"] > 0:
@@ -839,9 +827,7 @@ def print_platform_result(result: dict, group: list[str], verbose: bool = False)
print(f" {gt_cov['platform_only']} platform-only (no emulator profile)")
# ---------------------------------------------------------------------------
# Emulator/system mode verification
# ---------------------------------------------------------------------------
def _effective_validation_label(details: list[dict], validation_index: dict) -> str:
"""Determine the bracket label for the report.
@@ -890,11 +876,11 @@ def verify_emulator(
p = all_profiles[name]
if p.get("type") == "alias":
alias_of = p.get("alias_of", "?")
print(f"Error: {name} is an alias of {alias_of} use --emulator {alias_of}",
print(f"Error: {name} is an alias of {alias_of} -use --emulator {alias_of}",
file=sys.stderr)
sys.exit(1)
if p.get("type") == "launcher":
print(f"Error: {name} is a launcher use the emulator it launches",
print(f"Error: {name} is a launcher -use the emulator it launches",
file=sys.stderr)
sys.exit(1)
# Check standalone capability
@@ -1115,7 +1101,7 @@ def print_emulator_result(result: dict, verbose: bool = False) -> None:
req = "required" if d.get("required", True) else "optional"
hle = ", HLE available" if d.get("hle_fallback") else ""
reason = d.get("reason", "")
print(f" UNTESTED ({req}{hle}): {d['name']} {reason}")
print(f" UNTESTED ({req}{hle}): {d['name']} -{reason}")
gt = d.get("ground_truth", [])
if gt:
if verbose:
@@ -1260,29 +1246,20 @@ def main():
target_cores_cache: dict[str, set[str] | None] = {}
if args.target:
from common import load_target_config
skip = []
for p in platforms:
try:
target_cores_cache[p] = load_target_config(p, args.target, args.platforms_dir)
except FileNotFoundError:
if args.all:
target_cores_cache[p] = None
else:
print(f"ERROR: No target config for platform '{p}'", file=sys.stderr)
sys.exit(1)
except ValueError as e:
if args.all:
print(f"INFO: Skipping {p}: {e}")
skip.append(p)
else:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
platforms = [p for p in platforms if p not in skip]
try:
target_cores_cache, platforms = build_target_cores_cache(
platforms, args.target, args.platforms_dir, is_all=args.all,
)
except (FileNotFoundError, ValueError) as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
# Group identical platforms (same function as generate_pack)
groups = group_identical_platforms(platforms, args.platforms_dir,
target_cores_cache if args.target else None)
from cross_reference import _build_supplemental_index
suppl_names = _build_supplemental_index()
all_results = {}
group_results: list[tuple[dict, list[str]]] = []
for group_platforms, representative in groups:
@@ -1291,6 +1268,7 @@ def main():
result = verify_platform(
config, db, args.emulators_dir, emu_profiles,
target_cores=tc, data_dir_registry=data_registry,
supplemental_names=suppl_names,
)
names = [load_platform_config(p, args.platforms_dir).get("platform", p) for p in group_platforms]
group_results.append((result, names))

View File

@@ -230,10 +230,10 @@ class TestE2E(unittest.TestCase):
# Correct hash
{"name": "correct_hash.bin", "destination": "correct_hash.bin",
"md5": f["correct_hash.bin"]["md5"], "required": True},
# Wrong hash on disk untested
# Wrong hash on disk ->untested
{"name": "wrong_hash.bin", "destination": "wrong_hash.bin",
"md5": "ffffffffffffffffffffffffffffffff", "required": True},
# No MD5 OK (existence within md5 platform)
# No MD5 ->OK (existence within md5 platform)
{"name": "no_md5.bin", "destination": "no_md5.bin", "required": False},
# Missing required
{"name": "gone_req.bin", "destination": "gone_req.bin",
@@ -259,7 +259,7 @@ class TestE2E(unittest.TestCase):
# Truncated MD5 (Batocera 29 chars)
{"name": "truncated.bin", "destination": "truncated.bin",
"md5": truncated_md5, "required": True},
# Same destination from different entry worst status wins
# Same destination from different entry ->worst status wins
{"name": "correct_hash.bin", "destination": "dedup_target.bin",
"md5": f["correct_hash.bin"]["md5"], "required": True},
{"name": "correct_hash.bin", "destination": "dedup_target.bin",
@@ -367,7 +367,7 @@ class TestE2E(unittest.TestCase):
with open(os.path.join(self.emulators_dir, "test_alias.yml"), "w") as fh:
yaml.dump(alias, fh)
# Emulator with data_dir that matches platform gaps suppressed
# Emulator with data_dir that matches platform ->gaps suppressed
emu_dd = {
"emulator": "TestEmuDD",
"type": "libretro",
@@ -415,39 +415,39 @@ class TestE2E(unittest.TestCase):
"type": "libretro",
"systems": ["console-a", "sys-md5"],
"files": [
# Size validation correct size (16 bytes = len(b"PRESENT_REQUIRED"))
# Size validation -correct size (16 bytes = len(b"PRESENT_REQUIRED"))
{"name": "present_req.bin", "required": True,
"validation": ["size"], "size": 16,
"source_ref": "test.c:10-20"},
# Size validation wrong expected size
# Size validation -wrong expected size
{"name": "present_opt.bin", "required": False,
"validation": ["size"], "size": 9999},
# CRC32 validation correct crc32
# CRC32 validation -correct crc32
{"name": "correct_hash.bin", "required": True,
"validation": ["crc32"], "crc32": "91d0b1d3",
"source_ref": "hash.c:42"},
# CRC32 validation wrong crc32
# CRC32 validation -wrong crc32
{"name": "no_md5.bin", "required": False,
"validation": ["crc32"], "crc32": "deadbeef"},
# CRC32 starting with '0' (regression: lstrip("0x") bug)
{"name": "leading_zero_crc.bin", "required": True,
"validation": ["crc32"], "crc32": "0179e92e"},
# MD5 validation correct md5
# MD5 validation -correct md5
{"name": "correct_hash.bin", "required": True,
"validation": ["md5"], "md5": "4a8db431e3b1a1acacec60e3424c4ce8"},
# SHA1 validation correct sha1
# SHA1 validation -correct sha1
{"name": "correct_hash.bin", "required": True,
"validation": ["sha1"], "sha1": "a2ab6c95c5bbd191b9e87e8f4e85205a47be5764"},
# MD5 validation wrong md5
# MD5 validation -wrong md5
{"name": "alias_target.bin", "required": False,
"validation": ["md5"], "md5": "0000000000000000000000000000dead"},
# Adler32 known_hash_adler32 field
# Adler32 -known_hash_adler32 field
{"name": "present_req.bin", "required": True,
"known_hash_adler32": None}, # placeholder, set below
# Min/max size range validation
{"name": "present_req.bin", "required": True,
"validation": ["size"], "min_size": 10, "max_size": 100},
# Signature crypto check we can't reproduce, but size applies
# Signature -crypto check we can't reproduce, but size applies
{"name": "correct_hash.bin", "required": True,
"validation": ["size", "signature"], "size": 17},
],
@@ -491,7 +491,7 @@ class TestE2E(unittest.TestCase):
yaml.dump(emu_subdir, fh)
# ---------------------------------------------------------------
# THE TEST one method per feature area, all using same fixtures
# THE TEST -one method per feature area, all using same fixtures
# ---------------------------------------------------------------
def test_01_resolve_sha1(self):
@@ -676,7 +676,7 @@ class TestE2E(unittest.TestCase):
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 is a file entry, not data_dir content still undeclared
# 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):
@@ -688,7 +688,7 @@ class TestE2E(unittest.TestCase):
self.assertNotIn("launcher_bios.bin", names)
def test_45_hle_fallback_downgrades_severity(self):
"""Missing file with hle_fallback=true INFO severity, not CRITICAL."""
"""Missing file with hle_fallback=true ->INFO severity, not CRITICAL."""
from verify import compute_severity, Severity
# required + missing + NO HLE = CRITICAL
sev = compute_severity("missing", True, "md5", hle_fallback=False)
@@ -723,7 +723,7 @@ class TestE2E(unittest.TestCase):
groups = group_identical_platforms(
["test_existence", "test_inherited"], self.platforms_dir
)
# Different base_destination separate groups
# Different base_destination ->separate groups
self.assertEqual(len(groups), 2)
def test_51_platform_grouping_same(self):
@@ -1064,7 +1064,7 @@ class TestE2E(unittest.TestCase):
def test_95_verify_emulator_validation_applied(self):
"""Emulator mode applies validation checks as primary verification."""
result = verify_emulator(["test_validation"], self.emulators_dir, self.db)
# present_opt.bin has wrong size UNTESTED
# present_opt.bin has wrong size ->UNTESTED
for d in result["details"]:
if d["name"] == "present_opt.bin":
self.assertEqual(d["status"], Status.UNTESTED)
@@ -1092,13 +1092,13 @@ class TestE2E(unittest.TestCase):
def test_98_verify_emulator_validation_label(self):
"""Validation label reflects the checks used."""
result = verify_emulator(["test_validation"], self.emulators_dir, self.db)
# test_validation has crc32, md5, sha1, size all listed
# test_validation has crc32, md5, sha1, size ->all listed
self.assertEqual(result["verification_mode"], "crc32+md5+sha1+signature+size")
def test_99filter_files_by_mode(self):
"""filter_files_by_mode correctly filters standalone/libretro."""
files = [
{"name": "a.bin"}, # no mode both
{"name": "a.bin"}, # no mode ->both
{"name": "b.bin", "mode": "libretro"}, # libretro only
{"name": "c.bin", "mode": "standalone"}, # standalone only
{"name": "d.bin", "mode": "both"}, # explicit both
@@ -1126,7 +1126,7 @@ class TestE2E(unittest.TestCase):
self.assertTrue(len(notes) > 0)
def test_101_verify_emulator_severity_missing_required(self):
"""Missing required file in emulator mode WARNING severity."""
"""Missing required file in emulator mode ->WARNING severity."""
result = verify_emulator(["test_emu"], self.emulators_dir, self.db)
# undeclared_req.bin is required and missing
for d in result["details"]:
@@ -1494,7 +1494,7 @@ class TestE2E(unittest.TestCase):
def test_112_build_ground_truth(self):
"""build_ground_truth returns per-emulator detail for a filename."""
from common import build_ground_truth
from validation import build_ground_truth
profiles = load_emulator_profiles(self.emulators_dir)
index = _build_validation_index(profiles)
gt = build_ground_truth("present_req.bin", index)
@@ -1510,7 +1510,7 @@ class TestE2E(unittest.TestCase):
def test_113_build_ground_truth_empty(self):
"""build_ground_truth returns [] for unknown filename."""
from common import build_ground_truth
from validation import build_ground_truth
profiles = load_emulator_profiles(self.emulators_dir)
index = _build_validation_index(profiles)
gt = build_ground_truth("nonexistent.bin", index)
@@ -1642,7 +1642,7 @@ class TestE2E(unittest.TestCase):
config = load_platform_config("test_existence", self.platforms_dir)
profiles = load_emulator_profiles(self.emulators_dir)
result = verify_platform(config, self.db, self.emulators_dir, profiles)
# Simulate --json filtering (non-OK only) ground_truth must survive
# Simulate --json filtering (non-OK only) -ground_truth must survive
filtered = [d for d in result["details"] if d["status"] != Status.OK]
for d in filtered:
self.assertIn("ground_truth", d)
@@ -2426,7 +2426,7 @@ class TestE2E(unittest.TestCase):
config = load_platform_config("test_archive_platform", self.platforms_dir)
profiles = load_emulator_profiles(self.emulators_dir)
undeclared = find_undeclared_files(config, self.emulators_dir, self.db, profiles)
# test_archive.zip is declared its archived ROMs should be skipped
# test_archive.zip is declared ->its archived ROMs should be skipped
archive_entries = [u for u in undeclared if u.get("archive") == "test_archive.zip"]
self.assertEqual(len(archive_entries), 0)
@@ -3010,7 +3010,7 @@ class TestE2E(unittest.TestCase):
def test_104_diff_truth_normalized_system_ids(self):
"""Diff matches systems with different ID formats via normalization."""
from common import diff_platform_truth
from truth import diff_platform_truth
truth = {
"systems": {