mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-20 07:42:35 -05:00
Compare commits
18 Commits
8f1c7e47de
...
5cbd461a97
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cbd461a97 | |||
| 6cbdd4c40c | |||
| 37acc8d0fc | |||
| 2cf1398786 | |||
| 6b14b5e2b1 | |||
| 6d959ff2b0 | |||
| 3672912de7 | |||
| 569781c104 | |||
| 7f265b3cb2 | |||
| 3ea1e09cb0 | |||
| 89d6dd2eee | |||
| acd2daf7c1 | |||
| 0ad8324d46 | |||
| 29749898f8 | |||
| 59d7582c0e | |||
| e94ce6b194 | |||
| 181248b6db | |||
| a117b13b49 |
@@ -2,7 +2,7 @@
|
||||
|
||||
Complete BIOS and firmware packs for Batocera, EmuDeck, Lakka, Recalbox, RetroArch, RetroBat, RetroDECK, RetroPie, and RomM.
|
||||
|
||||
**6,748** verified files across **352** systems, ready to extract into your emulator's BIOS directory.
|
||||
**6,756** verified files across **352** systems, ready to extract into your emulator's BIOS directory.
|
||||
|
||||
## Download BIOS packs
|
||||
|
||||
@@ -28,8 +28,8 @@ Each file is checked against the emulator's source code to match what the code a
|
||||
- **9 platforms** supported with platform-specific verification
|
||||
- **328 emulators** profiled from source (RetroArch cores + standalone)
|
||||
- **352 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
|
||||
- **6,748 files** verified with MD5, SHA1, CRC32 checksums
|
||||
- **5251 MB** total collection size
|
||||
- **6,756 files** verified with MD5, SHA1, CRC32 checksums
|
||||
- **5331 MB** total collection size
|
||||
|
||||
## Supported systems
|
||||
|
||||
@@ -41,7 +41,7 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
|
||||
|
||||
| Platform | Coverage | Verified | Untested | Missing |
|
||||
|----------|----------|----------|----------|---------|
|
||||
| Batocera | 359/359 (100.0%) | 358 | 1 | 0 |
|
||||
| Batocera | 359/359 (100.0%) | 359 | 0 | 0 |
|
||||
| EmuDeck | 161/161 (100.0%) | 161 | 0 | 0 |
|
||||
| Lakka | 448/448 (100.0%) | 448 | 0 | 0 |
|
||||
| Recalbox | 346/346 (100.0%) | 346 | 0 | 0 |
|
||||
@@ -49,7 +49,7 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
|
||||
| RetroBat | 331/331 (100.0%) | 331 | 0 | 0 |
|
||||
| RetroDECK | 2007/2007 (100.0%) | 2007 | 0 | 0 |
|
||||
| RetroPie | 448/448 (100.0%) | 448 | 0 | 0 |
|
||||
| RomM | 374/374 (100.0%) | 359 | 15 | 0 |
|
||||
| RomM | 374/374 (100.0%) | 374 | 0 | 0 |
|
||||
|
||||
## Build your own pack
|
||||
|
||||
@@ -72,6 +72,7 @@ python scripts/generate_pack.py --list-systems
|
||||
python scripts/verify.py --all
|
||||
python scripts/verify.py --platform batocera
|
||||
python scripts/verify.py --emulator flycast
|
||||
python scripts/verify.py --platform retroarch --verbose # emulator ground truth
|
||||
```
|
||||
|
||||
Only dependency: Python 3 + `pyyaml`.
|
||||
@@ -110,4 +111,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
||||
|
||||
This repository provides BIOS files for personal backup and archival purposes.
|
||||
|
||||
*Auto-generated on 2026-03-26T12:17:35Z*
|
||||
*Auto-generated on 2026-03-27T22:52:26Z*
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+186
-54
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"generated_at": "2026-03-26T12:17:17Z",
|
||||
"total_files": 6748,
|
||||
"total_size": 5505760050,
|
||||
"generated_at": "2026-03-27T22:52:09Z",
|
||||
"total_files": 6756,
|
||||
"total_size": 5589782999,
|
||||
"files": {
|
||||
"520d3d1b5897800af47f92efd2444a26b7a7dead": {
|
||||
"path": "bios/3DO Company/3DO/3do_arcade_saot.bin",
|
||||
@@ -573,19 +573,9 @@
|
||||
"crc32": "665cd50f",
|
||||
"adler32": "39cd4d98"
|
||||
},
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80709": {
|
||||
"afd060e6f35faf3bb0146fa889fc787adf56330a": {
|
||||
"path": "bios/Apple/Apple II/disk2-13boot.rom",
|
||||
"name": "disk2-13boot.rom",
|
||||
"size": 0,
|
||||
"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
"md5": "d41d8cd98f00b204e9800998ecf8427e",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"crc32": "00000000",
|
||||
"adler32": "00000001"
|
||||
},
|
||||
"afd060e6f35faf3bb0146fa889fc787adf56330a": {
|
||||
"path": "bios/Apple/Apple II/disk2-13seq.rom",
|
||||
"name": "disk2-13seq.rom",
|
||||
"size": 256,
|
||||
"sha1": "afd060e6f35faf3bb0146fa889fc787adf56330a",
|
||||
"md5": "4f80448507cf43ab40c17ac08d89e278",
|
||||
@@ -1043,6 +1033,16 @@
|
||||
"crc32": "7eba26a4",
|
||||
"adler32": "dbd38d64"
|
||||
},
|
||||
"533ef12a6d22726da81a50f08871c6e9a377a328": {
|
||||
"path": "bios/Arcade/Arcade/.variants/naomi2.zip.533ef12a",
|
||||
"name": "naomi2.zip",
|
||||
"size": 2189528,
|
||||
"sha1": "533ef12a6d22726da81a50f08871c6e9a377a328",
|
||||
"md5": "0ea5bf0345e27b1cf51bbde1bd398eca",
|
||||
"sha256": "75910bca402ce5b3db9aaab485150bc40aee5a0028e3e50c8bd63132520ffe4f",
|
||||
"crc32": "f25cf3a8",
|
||||
"adler32": "744000e5"
|
||||
},
|
||||
"2962e338ccc9f66f29b409f73ca27aeee79633ac": {
|
||||
"path": "bios/Arcade/Arcade/.variants/naomi2.zip.da79eca4",
|
||||
"name": "naomi2.zip",
|
||||
@@ -1113,6 +1113,16 @@
|
||||
"crc32": "578e8fde",
|
||||
"adler32": "d530a21a"
|
||||
},
|
||||
"c0c001ec80fa860857000f4cfc9844a28498a355": {
|
||||
"path": "bios/Arcade/Arcade/.variants/pgm.zip.c0c001ec",
|
||||
"name": "pgm.zip",
|
||||
"size": 2094636,
|
||||
"sha1": "c0c001ec80fa860857000f4cfc9844a28498a355",
|
||||
"md5": "87cc944eef4c671aa2629a8ba48a08e0",
|
||||
"sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5",
|
||||
"crc32": "bf3dd2ef",
|
||||
"adler32": "d0cdbd6d"
|
||||
},
|
||||
"44bc8180dca3dfdf1b461268919da8efb2e3fb07": {
|
||||
"path": "bios/Arcade/Arcade/.variants/skns.zip.44bc8180",
|
||||
"name": "skns.zip",
|
||||
@@ -27713,6 +27723,56 @@
|
||||
"crc32": "0636e0be",
|
||||
"adler32": "0ca77de6"
|
||||
},
|
||||
"997bae5e5a190c5bb3b1fb9e7e3e75b2da88cb27": {
|
||||
"path": "bios/Id Software/Doom/.variants/DOOM.WAD.997bae5e",
|
||||
"name": "DOOM.WAD",
|
||||
"size": 12733492,
|
||||
"sha1": "997bae5e5a190c5bb3b1fb9e7e3e75b2da88cb27",
|
||||
"md5": "4461d4511386518e784c647e3128e7bc",
|
||||
"sha256": "d91f81caeafc863f811c2945259589d617eb95dac5ca18b2d1f4e1bbebb41703",
|
||||
"crc32": "cff03d9f",
|
||||
"adler32": "a94ca820"
|
||||
},
|
||||
"c745f04a6abc2e6d2a2d52382f45500dd2a260be": {
|
||||
"path": "bios/Id Software/Doom/.variants/DOOM2.WAD.c745f04a",
|
||||
"name": "DOOM2.WAD",
|
||||
"size": 14802506,
|
||||
"sha1": "c745f04a6abc2e6d2a2d52382f45500dd2a260be",
|
||||
"md5": "9aa3cbf65b961d0bdac98ec403b832e1",
|
||||
"sha256": "5142a922518a7cfe09ed21097cf6a57316ec77c095682ebe2e90b56f4a568089",
|
||||
"crc32": "09b8a6ae",
|
||||
"adler32": "9bcb9107"
|
||||
},
|
||||
"d041456bea851c173f65ac6ab3f2ee61bb0b8b53": {
|
||||
"path": "bios/Id Software/Doom/.variants/DOOM64.WAD.d041456b",
|
||||
"name": "DOOM64.WAD",
|
||||
"size": 15103212,
|
||||
"sha1": "d041456bea851c173f65ac6ab3f2ee61bb0b8b53",
|
||||
"md5": "0aaba212339c72250f8a53a0a2b6189e",
|
||||
"sha256": "05ec0118cc130036d04bf6e6f7fe4792dfafc2d4bd98de349dd63e2022925365",
|
||||
"crc32": "65816192",
|
||||
"adler32": "58321899"
|
||||
},
|
||||
"816c7c6b0098f66c299c9253f62bd908456efb63": {
|
||||
"path": "bios/Id Software/Doom/.variants/PLUTONIA.WAD.816c7c6b",
|
||||
"name": "PLUTONIA.WAD",
|
||||
"size": 17531493,
|
||||
"sha1": "816c7c6b0098f66c299c9253f62bd908456efb63",
|
||||
"md5": "24037397056e919961005e08611623f4",
|
||||
"sha256": "2376491fdf49e6fda80f9b489571252e195d24423c4f1be3f3c38e81d2022f52",
|
||||
"crc32": "650b998d",
|
||||
"adler32": "57a05e03"
|
||||
},
|
||||
"9820e2a3035f0cdd87f69a7d57c59a7a267c9409": {
|
||||
"path": "bios/Id Software/Doom/.variants/TNT.WAD.9820e2a3",
|
||||
"name": "TNT.WAD",
|
||||
"size": 18304630,
|
||||
"sha1": "9820e2a3035f0cdd87f69a7d57c59a7a267c9409",
|
||||
"md5": "8974e3117ed4a1839c752d5e11ab1b7b",
|
||||
"sha256": "b7b60364d8dd6763b82d5b4f93da547ff15990135c9df8a3abce4328c3dba5f0",
|
||||
"crc32": "15f18ddb",
|
||||
"adler32": "479fb930"
|
||||
},
|
||||
"eca9cff1014ce5081804e193588d96c6ddb35432": {
|
||||
"path": "bios/Id Software/Doom/CHEX.WAD",
|
||||
"name": "CHEX.WAD",
|
||||
@@ -31423,6 +31483,16 @@
|
||||
"crc32": "8205795e",
|
||||
"adler32": "87ca090b"
|
||||
},
|
||||
"6639b6693784574d204c42703a74fd8b088a3a5e": {
|
||||
"path": "bios/Microsoft/Xbox/.variants/Complex_4627.bin.6639b669",
|
||||
"name": "Complex_4627.bin",
|
||||
"size": 1048576,
|
||||
"sha1": "6639b6693784574d204c42703a74fd8b088a3a5e",
|
||||
"md5": "ec00e31e746de2473acfe7903c5a4cb7",
|
||||
"sha256": "34f1c8ded59116436065783f8ad2ef0939df3cbfc76277ec9e5c41bf9ccb93cd",
|
||||
"crc32": "ccb97a84",
|
||||
"adler32": "3721a382"
|
||||
},
|
||||
"358a20e61ec1a2387127b1fa92113034fb279f9b": {
|
||||
"path": "bios/Microsoft/Xbox/Complex.bin",
|
||||
"name": "Complex.bin",
|
||||
@@ -59903,6 +59973,16 @@
|
||||
"crc32": "05cfde8c",
|
||||
"adler32": "ff3c17cb"
|
||||
},
|
||||
"bf6b379c204da77dece1aedf83ff35227a623e5d": {
|
||||
"path": "bios/SNK/Neo Geo CD/.variants/neocdz.zip.bf6b379c",
|
||||
"name": "neocdz.zip",
|
||||
"size": 214876,
|
||||
"sha1": "bf6b379c204da77dece1aedf83ff35227a623e5d",
|
||||
"md5": "c38cb8e50321783e413dc5ff292a3ff8",
|
||||
"sha256": "71994d72072f0c1b554209a9055bbdbf6045df73999720ed3432ac072f394aba",
|
||||
"crc32": "19681e91",
|
||||
"adler32": "3d8bbd14"
|
||||
},
|
||||
"5158b728e62b391fb69493743dcf7abbc62abc82": {
|
||||
"path": "bios/SNK/Neo Geo CD/.variants/uni-bioscd.rom.5158b728",
|
||||
"name": "uni-bioscd.rom",
|
||||
@@ -63383,18 +63463,8 @@
|
||||
"crc32": "4dcfd55c",
|
||||
"adler32": "75a93df4"
|
||||
},
|
||||
"c983bfa2f4c6d077e70e6ff9c7ed59b72368e355": {
|
||||
"path": "bios/Sega/SC-3000/.variants/sc3000.zip.a43aef36",
|
||||
"name": "sc3000.zip",
|
||||
"size": 21232,
|
||||
"sha1": "c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"md5": "52adbcbef759756a5b97cafda75b922c",
|
||||
"sha256": "258b08eafabed889970445adadcc483f91c31205e8c9a75dfa2efceab2f1c43f",
|
||||
"crc32": "48decddc",
|
||||
"adler32": "1dd14fb4"
|
||||
},
|
||||
"12de390be2595ad17015310085eaec57ad2b953f": {
|
||||
"path": "bios/Sega/SC-3000/sc3000.zip",
|
||||
"path": "bios/Sega/SC-3000/.variants/sc3000.zip.12de390b",
|
||||
"name": "sc3000.zip",
|
||||
"size": 21348,
|
||||
"sha1": "12de390be2595ad17015310085eaec57ad2b953f",
|
||||
@@ -63403,6 +63473,16 @@
|
||||
"crc32": "62fb7d82",
|
||||
"adler32": "9fb3a0ff"
|
||||
},
|
||||
"c983bfa2f4c6d077e70e6ff9c7ed59b72368e355": {
|
||||
"path": "bios/Sega/SC-3000/sc3000.zip",
|
||||
"name": "sc3000.zip",
|
||||
"size": 21232,
|
||||
"sha1": "c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"md5": "52adbcbef759756a5b97cafda75b922c",
|
||||
"sha256": "258b08eafabed889970445adadcc483f91c31205e8c9a75dfa2efceab2f1c43f",
|
||||
"crc32": "48decddc",
|
||||
"adler32": "1dd14fb4"
|
||||
},
|
||||
"8c031bf9908fd0142fdd10a9cdd79389f8a3f2fc": {
|
||||
"path": "bios/Sega/Saturn/.variants/hisaturn_v103.bin",
|
||||
"name": "hisaturn_v103.bin",
|
||||
@@ -67543,7 +67623,6 @@
|
||||
"ba89edf2729a28a17cd9e0f7a0ac9a39": "bc32bc0e8902946663998f56aea52be597d9e361",
|
||||
"8f7ee14ccca8ae3dd8c759497af3f09b": "799e2fc90d6bfd8cb74e331e04d5afd36f2f21a1",
|
||||
"d3bef2755267a941f264fb5b288e3076": "e8c40d3a44a41a9b6b5dd3a993d7057b3bfb4086",
|
||||
"d41d8cd98f00b204e9800998ecf8427e": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
"4f80448507cf43ab40c17ac08d89e278": "afd060e6f35faf3bb0146fa889fc787adf56330a",
|
||||
"5f1be0c1cdff26f5956eef9643911886": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4",
|
||||
"c835eab06842e3c1d6e2e7dc19289828": "a57f14469867ce0d0865995a645b69f41a0ea718",
|
||||
@@ -67590,6 +67669,7 @@
|
||||
"526eda1e2a7920c92c88178789d71d84": "c96711c01c0158f161791d6fbe75d88329e8ac0a",
|
||||
"4e1ca1ade518f53efcce30bdefb855a4": "11ad55ee6b11092e810365b8389c1f8b4081e5d0",
|
||||
"58033e4ba5793c09dffb87f96f3e9301": "2533cc33201da28b2086a0a2fd2b5e04271b6eeb",
|
||||
"0ea5bf0345e27b1cf51bbde1bd398eca": "533ef12a6d22726da81a50f08871c6e9a377a328",
|
||||
"c50072cbab75673e1b1a6b94355e6fa8": "2962e338ccc9f66f29b409f73ca27aeee79633ac",
|
||||
"e20b430bd7def78b45f61f238abab624": "f9ad4a4c6b0bbbe39ba358690a48f763ecbd98f0",
|
||||
"82f3a8bea688b4863947722d2fcb07f7": "a0f07de6070d98f86d55a4ecd61b4a5b05a4a0d5",
|
||||
@@ -67597,6 +67677,7 @@
|
||||
"68ef99a1f2847d08ff9242a90561d31b": "0783012b4eabca599e460988257ec37500501df6",
|
||||
"653e991a39e867354d090c3394157d1c": "6bbbce094422062bd178d6007bed06dcdd0d8b78",
|
||||
"581cc172db39bb5007642405adf25b6e": "bc0acaefb5fac0cdc05476a9f452391d34a5150d",
|
||||
"87cc944eef4c671aa2629a8ba48a08e0": "c0c001ec80fa860857000f4cfc9844a28498a355",
|
||||
"49e192febe2f011d9be44ebc69129080": "44bc8180dca3dfdf1b461268919da8efb2e3fb07",
|
||||
"937ab1072d9ce3ecdf3d16d7fc72137a": "aabedfebe2cd8cfede8c2a3cb9ff33166f0ef953",
|
||||
"fcb298d97792b9e9bdd3296cc6be10b6": "eb2a867578a05bbf8741e9fe7204301062df0cb8",
|
||||
@@ -70257,6 +70338,11 @@
|
||||
"8ecd11275ad418d302cd358b408b01ec": "10f1a7204f69b82a18bc94a3010c9660aec0c802",
|
||||
"ec2a977f0c0645dd284010160caec636": "c0ee6f9443fa82c0fef5d497b3e76aa3077f1865",
|
||||
"19d55c537767a7424a8e376d62fc2ac0": "72c60172fb1ba77c9b24b06b7755f0a16f0b3a13",
|
||||
"4461d4511386518e784c647e3128e7bc": "997bae5e5a190c5bb3b1fb9e7e3e75b2da88cb27",
|
||||
"9aa3cbf65b961d0bdac98ec403b832e1": "c745f04a6abc2e6d2a2d52382f45500dd2a260be",
|
||||
"0aaba212339c72250f8a53a0a2b6189e": "d041456bea851c173f65ac6ab3f2ee61bb0b8b53",
|
||||
"24037397056e919961005e08611623f4": "816c7c6b0098f66c299c9253f62bd908456efb63",
|
||||
"8974e3117ed4a1839c752d5e11ab1b7b": "9820e2a3035f0cdd87f69a7d57c59a7a267c9409",
|
||||
"25485721882b050afa96a56e5758dd52": "eca9cff1014ce5081804e193588d96c6ddb35432",
|
||||
"1cd63c5ddff1bf8ce844237f580e9cf3": "7742089b4468a736cadb659a7deca3320fe6dcbd",
|
||||
"f0cefca49926d00903cf57551d901abe": "5b2e249b9c5133ec987b3ea77596381dc0d6bc1d",
|
||||
@@ -70628,6 +70714,7 @@
|
||||
"57509815f93e2817d3eb57e20286c7fb": "b484c8fa4549b79cff976001ac4beb19abf7cfb5",
|
||||
"248514aba82a0ec7fe2a9106862b05cd": "e7905d16d2ccd57a013c122dc432106cd59ef52c",
|
||||
"a0452dbf5ace7d2e49d0a8029efed09a": "829c00c3114f25b3dae5157c0a238b52a3ac37db",
|
||||
"ec00e31e746de2473acfe7903c5a4cb7": "6639b6693784574d204c42703a74fd8b088a3a5e",
|
||||
"21445c6f28fca7285b0f167ea770d1e5": "358a20e61ec1a2387127b1fa92113034fb279f9b",
|
||||
"39cee882148a87f93cb440b99dde3ceb": "3944392c954cfb176d4210544e88353b3c5d36b1",
|
||||
"04e9565c5eb34c71c8f5f8b9f6524406": "4e1c2c2ee308ca4591542b3ca48653f65fae6e0f",
|
||||
@@ -73476,6 +73563,7 @@
|
||||
"266c7454616da286c1a7de54181834c2": "df89ef91bbaf84f2a109377fd4b17b0050a163e8",
|
||||
"041fe48b90b1aed6d0b72da192fb76ef": "aed2cb663b6370efb10969a3373fd84c558edb4f",
|
||||
"a7cca75f3d5af6acc85efcce589ab04f": "891407e147b2c022ebedf8c51fe3a2f497304906",
|
||||
"c38cb8e50321783e413dc5ff292a3ff8": "bf6b379c204da77dece1aedf83ff35227a623e5d",
|
||||
"a147aeab5edeb1a9b652e7fb640f5bb3": "5158b728e62b391fb69493743dcf7abbc62abc82",
|
||||
"fc7599f3f871578fe9a0453662d1c966": "5992277debadeb64d1c1c64b0a92d9293eaf7e4a",
|
||||
"5c2366f25ff92d71788468ca492ebeca": "53bc1f283cdf00fa2efbb79f2e36d4c8038d743a",
|
||||
@@ -73824,8 +73912,8 @@
|
||||
"ff4a3572475236e859e3e9ac5c87d1f1": "02c287d10da6de579af7a4ce73b134bbdf23c970",
|
||||
"4ea493ea4e9f6c9ebfccbdb15110367e": "88d6499d874dcb5721ff58d76fe1b9af811192e3",
|
||||
"b4e76e416b887f4e7413ba76fa735f16": "70429f1d80503a0632f603bf762fe0bbaa881d22",
|
||||
"52adbcbef759756a5b97cafda75b922c": "c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"48e8821fb9087ab60a2a3b1465ee5124": "12de390be2595ad17015310085eaec57ad2b953f",
|
||||
"52adbcbef759756a5b97cafda75b922c": "c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"0306c0e408d6682dd2d86324bd4ac661": "8c031bf9908fd0142fdd10a9cdd79389f8a3f2fc",
|
||||
"9992f2761b0f6e83b3e923451ab8057b": "999ed28cfbf18103a4963b0d3797af3dcf67db05",
|
||||
"37e9746f4491aa2df9a83729d1a93620": "fefb403a7e91bdaf75ffd8a94c2a1f0ef4ece740",
|
||||
@@ -74406,9 +74494,6 @@
|
||||
"5ba8555f716bd48834858d8a7f42810ab7293b12"
|
||||
],
|
||||
"disk2-13boot.rom": [
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
||||
],
|
||||
"disk2-13seq.rom": [
|
||||
"afd060e6f35faf3bb0146fa889fc787adf56330a"
|
||||
],
|
||||
"disk2-16seq.rom": [
|
||||
@@ -74554,6 +74639,7 @@
|
||||
"d7ef86bd03de7c1d0e2b0762e04b6f8f8d26dbdb"
|
||||
],
|
||||
"naomi2.zip": [
|
||||
"533ef12a6d22726da81a50f08871c6e9a377a328",
|
||||
"2962e338ccc9f66f29b409f73ca27aeee79633ac",
|
||||
"46278151f85b74951592d57155441485fe3e0274",
|
||||
"b42c05722fa25416a382f87113132131153363f7"
|
||||
@@ -74568,12 +74654,14 @@
|
||||
"4f28af31ca0defdd73d80edec2fa296908e624dc",
|
||||
"eac782be9f6ebe5dfb09bb4a5342d8aefb82d527",
|
||||
"891407e147b2c022ebedf8c51fe3a2f497304906",
|
||||
"bf6b379c204da77dece1aedf83ff35227a623e5d",
|
||||
"80c9a25ccf39c6b85d1a7c9c0d6fe527ab45ec1c"
|
||||
],
|
||||
"pgm.zip": [
|
||||
"0783012b4eabca599e460988257ec37500501df6",
|
||||
"6bbbce094422062bd178d6007bed06dcdd0d8b78",
|
||||
"bc0acaefb5fac0cdc05476a9f452391d34a5150d",
|
||||
"c0c001ec80fa860857000f4cfc9844a28498a355",
|
||||
"2b61f36b22933f3bec237818ecc959efb016d652",
|
||||
"75de7964a38038c45791960d5f2ec9d26dbdc5e2"
|
||||
],
|
||||
@@ -82236,24 +82324,35 @@
|
||||
"ALI1429G.AMW": [
|
||||
"72c60172fb1ba77c9b24b06b7755f0a16f0b3a13"
|
||||
],
|
||||
"DOOM.WAD": [
|
||||
"997bae5e5a190c5bb3b1fb9e7e3e75b2da88cb27",
|
||||
"7742089b4468a736cadb659a7deca3320fe6dcbd"
|
||||
],
|
||||
"DOOM2.WAD": [
|
||||
"c745f04a6abc2e6d2a2d52382f45500dd2a260be",
|
||||
"7ec7652fcfce8ddc6e801839291f0e28ef1d5ae7"
|
||||
],
|
||||
"DOOM64.WAD": [
|
||||
"d041456bea851c173f65ac6ab3f2ee61bb0b8b53",
|
||||
"ae363db8cd5645e1753d9bacc82cc0724e8e7f21"
|
||||
],
|
||||
"PLUTONIA.WAD": [
|
||||
"816c7c6b0098f66c299c9253f62bd908456efb63",
|
||||
"90361e2a538d2388506657252ae41aceeb1ba360"
|
||||
],
|
||||
"TNT.WAD": [
|
||||
"9820e2a3035f0cdd87f69a7d57c59a7a267c9409",
|
||||
"9fbc66aedef7fe3bae0986cdb9323d2b8db4c9d3"
|
||||
],
|
||||
"CHEX.WAD": [
|
||||
"eca9cff1014ce5081804e193588d96c6ddb35432"
|
||||
],
|
||||
"DOOM.WAD": [
|
||||
"7742089b4468a736cadb659a7deca3320fe6dcbd"
|
||||
],
|
||||
"DOOM1.WAD": [
|
||||
"5b2e249b9c5133ec987b3ea77596381dc0d6bc1d"
|
||||
],
|
||||
"DOOM2.WAD": [
|
||||
"7ec7652fcfce8ddc6e801839291f0e28ef1d5ae7"
|
||||
],
|
||||
"DOOM2F.WAD": [
|
||||
"d510c877031bbd5f3d198581a2c8651e09b9861f"
|
||||
],
|
||||
"DOOM64.WAD": [
|
||||
"ae363db8cd5645e1753d9bacc82cc0724e8e7f21"
|
||||
],
|
||||
"HERETIC.WAD": [
|
||||
"f489d479371df32f6d280a0cb23b59a35ba2b833"
|
||||
],
|
||||
@@ -82266,18 +82365,12 @@
|
||||
"HEXEN.WAD": [
|
||||
"4b53832f0733c1e29e5f1de2428e5475e891af29"
|
||||
],
|
||||
"PLUTONIA.WAD": [
|
||||
"90361e2a538d2388506657252ae41aceeb1ba360"
|
||||
],
|
||||
"STRIFE0.WAD": [
|
||||
"bc0a110bf27aee89a0b2fc8111e2391ede891b8d"
|
||||
],
|
||||
"STRIFE1.WAD": [
|
||||
"64c13b951a845ca7f8081f68138a6181557458d1"
|
||||
],
|
||||
"TNT.WAD": [
|
||||
"9fbc66aedef7fe3bae0986cdb9323d2b8db4c9d3"
|
||||
],
|
||||
"VOICES.WAD": [
|
||||
"ec6883100d807b894a98f426d024d22c77b63e7f"
|
||||
],
|
||||
@@ -82941,12 +83034,13 @@
|
||||
"vg8020_basic-bios1.rom": [
|
||||
"829c00c3114f25b3dae5157c0a238b52a3ac37db"
|
||||
],
|
||||
"Complex_4627.bin": [
|
||||
"6639b6693784574d204c42703a74fd8b088a3a5e",
|
||||
"3944392c954cfb176d4210544e88353b3c5d36b1"
|
||||
],
|
||||
"Complex.bin": [
|
||||
"358a20e61ec1a2387127b1fa92113034fb279f9b"
|
||||
],
|
||||
"Complex_4627.bin": [
|
||||
"3944392c954cfb176d4210544e88353b3c5d36b1"
|
||||
],
|
||||
"cromwell_1024.bin": [
|
||||
"4e1c2c2ee308ca4591542b3ca48653f65fae6e0f"
|
||||
],
|
||||
@@ -91667,8 +91761,8 @@
|
||||
"70429f1d80503a0632f603bf762fe0bbaa881d22"
|
||||
],
|
||||
"sc3000.zip": [
|
||||
"c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"12de390be2595ad17015310085eaec57ad2b953f"
|
||||
"12de390be2595ad17015310085eaec57ad2b953f",
|
||||
"c983bfa2f4c6d077e70e6ff9c7ed59b72368e355"
|
||||
],
|
||||
"hisaturn_v103.bin": [
|
||||
"8c031bf9908fd0142fdd10a9cdd79389f8a3f2fc"
|
||||
@@ -92833,6 +92927,9 @@
|
||||
"PSVUPDAT.PUP": [
|
||||
"cc72dfcc964577cc29112ef368c28f55277c237c"
|
||||
],
|
||||
"disk2-13seq.rom": [
|
||||
"afd060e6f35faf3bb0146fa889fc787adf56330a"
|
||||
],
|
||||
"disk2-16boot.rom": [
|
||||
"d4181c9f046aafc3fb326b381baac809d9e38d16"
|
||||
],
|
||||
@@ -95646,7 +95743,6 @@
|
||||
"de7ddf29": "bc32bc0e8902946663998f56aea52be597d9e361",
|
||||
"4f923a35": "799e2fc90d6bfd8cb74e331e04d5afd36f2f21a1",
|
||||
"665cd50f": "e8c40d3a44a41a9b6b5dd3a993d7057b3bfb4086",
|
||||
"00000000": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
"d34eb2ff": "afd060e6f35faf3bb0146fa889fc787adf56330a",
|
||||
"b72a2c70": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4",
|
||||
"6105d2ee": "a57f14469867ce0d0865995a645b69f41a0ea718",
|
||||
@@ -95693,6 +95789,7 @@
|
||||
"6ee50181": "c96711c01c0158f161791d6fbe75d88329e8ac0a",
|
||||
"fb0bca9c": "11ad55ee6b11092e810365b8389c1f8b4081e5d0",
|
||||
"7eba26a4": "2533cc33201da28b2086a0a2fd2b5e04271b6eeb",
|
||||
"f25cf3a8": "533ef12a6d22726da81a50f08871c6e9a377a328",
|
||||
"2143196c": "2962e338ccc9f66f29b409f73ca27aeee79633ac",
|
||||
"ca501374": "f9ad4a4c6b0bbbe39ba358690a48f763ecbd98f0",
|
||||
"31828d82": "a0f07de6070d98f86d55a4ecd61b4a5b05a4a0d5",
|
||||
@@ -95700,6 +95797,7 @@
|
||||
"713d6657": "0783012b4eabca599e460988257ec37500501df6",
|
||||
"fefb84f1": "6bbbce094422062bd178d6007bed06dcdd0d8b78",
|
||||
"578e8fde": "bc0acaefb5fac0cdc05476a9f452391d34a5150d",
|
||||
"bf3dd2ef": "c0c001ec80fa860857000f4cfc9844a28498a355",
|
||||
"33ca0801": "44bc8180dca3dfdf1b461268919da8efb2e3fb07",
|
||||
"651b0f2b": "aabedfebe2cd8cfede8c2a3cb9ff33166f0ef953",
|
||||
"ad37f2de": "eb2a867578a05bbf8741e9fe7204301062df0cb8",
|
||||
@@ -98360,6 +98458,11 @@
|
||||
"0f3e6586": "10f1a7204f69b82a18bc94a3010c9660aec0c802",
|
||||
"e75945f3": "c0ee6f9443fa82c0fef5d497b3e76aa3077f1865",
|
||||
"0636e0be": "72c60172fb1ba77c9b24b06b7755f0a16f0b3a13",
|
||||
"cff03d9f": "997bae5e5a190c5bb3b1fb9e7e3e75b2da88cb27",
|
||||
"09b8a6ae": "c745f04a6abc2e6d2a2d52382f45500dd2a260be",
|
||||
"65816192": "d041456bea851c173f65ac6ab3f2ee61bb0b8b53",
|
||||
"650b998d": "816c7c6b0098f66c299c9253f62bd908456efb63",
|
||||
"15f18ddb": "9820e2a3035f0cdd87f69a7d57c59a7a267c9409",
|
||||
"298dd5b5": "eca9cff1014ce5081804e193588d96c6ddb35432",
|
||||
"723e60f9": "7742089b4468a736cadb659a7deca3320fe6dcbd",
|
||||
"162b696a": "5b2e249b9c5133ec987b3ea77596381dc0d6bc1d",
|
||||
@@ -98731,6 +98834,7 @@
|
||||
"c443c426": "b484c8fa4549b79cff976001ac4beb19abf7cfb5",
|
||||
"95db2959": "e7905d16d2ccd57a013c122dc432106cd59ef52c",
|
||||
"8205795e": "829c00c3114f25b3dae5157c0a238b52a3ac37db",
|
||||
"ccb97a84": "6639b6693784574d204c42703a74fd8b088a3a5e",
|
||||
"2c7a2191": "358a20e61ec1a2387127b1fa92113034fb279f9b",
|
||||
"1dbb7b59": "3944392c954cfb176d4210544e88353b3c5d36b1",
|
||||
"5ae5278a": "4e1c2c2ee308ca4591542b3ca48653f65fae6e0f",
|
||||
@@ -101579,6 +101683,7 @@
|
||||
"bd34bfad": "df89ef91bbaf84f2a109377fd4b17b0050a163e8",
|
||||
"e5554b80": "aed2cb663b6370efb10969a3373fd84c558edb4f",
|
||||
"05cfde8c": "891407e147b2c022ebedf8c51fe3a2f497304906",
|
||||
"19681e91": "bf6b379c204da77dece1aedf83ff35227a623e5d",
|
||||
"0ffb3127": "5158b728e62b391fb69493743dcf7abbc62abc82",
|
||||
"5a86cff2": "5992277debadeb64d1c1c64b0a92d9293eaf7e4a",
|
||||
"cac62307": "53bc1f283cdf00fa2efbb79f2e36d4c8038d743a",
|
||||
@@ -101927,8 +102032,8 @@
|
||||
"c94e8c8b": "02c287d10da6de579af7a4ce73b134bbdf23c970",
|
||||
"0658f691": "88d6499d874dcb5721ff58d76fe1b9af811192e3",
|
||||
"4dcfd55c": "70429f1d80503a0632f603bf762fe0bbaa881d22",
|
||||
"48decddc": "c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"62fb7d82": "12de390be2595ad17015310085eaec57ad2b953f",
|
||||
"48decddc": "c983bfa2f4c6d077e70e6ff9c7ed59b72368e355",
|
||||
"6abfefea": "8c031bf9908fd0142fdd10a9cdd79389f8a3f2fc",
|
||||
"0ab1c9ec": "999ed28cfbf18103a4963b0d3797af3dcf67db05",
|
||||
"94c90a92": "fefb403a7e91bdaf75ffd8a94c2a1f0ef4ece740",
|
||||
@@ -102389,6 +102494,9 @@
|
||||
".variants/naomi.zip.2533cc33": [
|
||||
"2533cc33201da28b2086a0a2fd2b5e04271b6eeb"
|
||||
],
|
||||
".variants/naomi2.zip.533ef12a": [
|
||||
"533ef12a6d22726da81a50f08871c6e9a377a328"
|
||||
],
|
||||
".variants/naomi2.zip.da79eca4": [
|
||||
"2962e338ccc9f66f29b409f73ca27aeee79633ac"
|
||||
],
|
||||
@@ -102408,6 +102516,9 @@
|
||||
".variants/pgm.zip.bc0acaef": [
|
||||
"bc0acaefb5fac0cdc05476a9f452391d34a5150d"
|
||||
],
|
||||
".variants/pgm.zip.c0c001ec": [
|
||||
"c0c001ec80fa860857000f4cfc9844a28498a355"
|
||||
],
|
||||
".variants/skns.zip.44bc8180": [
|
||||
"44bc8180dca3dfdf1b461268919da8efb2e3fb07"
|
||||
],
|
||||
@@ -105807,6 +105918,21 @@
|
||||
"win486/ALI1429G.AMW": [
|
||||
"72c60172fb1ba77c9b24b06b7755f0a16f0b3a13"
|
||||
],
|
||||
".variants/DOOM.WAD.997bae5e": [
|
||||
"997bae5e5a190c5bb3b1fb9e7e3e75b2da88cb27"
|
||||
],
|
||||
".variants/DOOM2.WAD.c745f04a": [
|
||||
"c745f04a6abc2e6d2a2d52382f45500dd2a260be"
|
||||
],
|
||||
".variants/DOOM64.WAD.d041456b": [
|
||||
"d041456bea851c173f65ac6ab3f2ee61bb0b8b53"
|
||||
],
|
||||
".variants/PLUTONIA.WAD.816c7c6b": [
|
||||
"816c7c6b0098f66c299c9253f62bd908456efb63"
|
||||
],
|
||||
".variants/TNT.WAD.9820e2a3": [
|
||||
"9820e2a3035f0cdd87f69a7d57c59a7a267c9409"
|
||||
],
|
||||
".variants/ecwolf.pk3": [
|
||||
"f3f2a11f3ecd91cd62d74c3acfad68a4cc6ddbd9"
|
||||
],
|
||||
@@ -106572,6 +106698,9 @@
|
||||
"share/systemroms/vg8020_basic-bios1.rom": [
|
||||
"829c00c3114f25b3dae5157c0a238b52a3ac37db"
|
||||
],
|
||||
".variants/Complex_4627.bin.6639b669": [
|
||||
"6639b6693784574d204c42703a74fd8b088a3a5e"
|
||||
],
|
||||
".variants/syscard1.pce.008cf0f5": [
|
||||
"008cf0f5cd5e2000b9f2ebf5e4ee84097e6aef74"
|
||||
],
|
||||
@@ -114549,6 +114678,9 @@
|
||||
".variants/neocdz.zip.891407e1": [
|
||||
"891407e147b2c022ebedf8c51fe3a2f497304906"
|
||||
],
|
||||
".variants/neocdz.zip.bf6b379c": [
|
||||
"bf6b379c204da77dece1aedf83ff35227a623e5d"
|
||||
],
|
||||
".variants/uni-bioscd.rom.5158b728": [
|
||||
"5158b728e62b391fb69493743dcf7abbc62abc82"
|
||||
],
|
||||
@@ -115197,8 +115329,8 @@
|
||||
".variants/bios_MD.bin.453fca4e": [
|
||||
"453fca4e1db6fae4a10657c4451bccbb71955628"
|
||||
],
|
||||
".variants/sc3000.zip.a43aef36": [
|
||||
"c983bfa2f4c6d077e70e6ff9c7ed59b72368e355"
|
||||
".variants/sc3000.zip.12de390b": [
|
||||
"12de390be2595ad17015310085eaec57ad2b953f"
|
||||
],
|
||||
".variants/hisaturn_v103.bin": [
|
||||
"8c031bf9908fd0142fdd10a9cdd79389f8a3f2fc"
|
||||
|
||||
@@ -1614,7 +1614,7 @@ systems:
|
||||
- name: sc3000.zip
|
||||
destination: sc3000.zip
|
||||
required: true
|
||||
md5: a6a47eae38600e41cc67e887e36e70b7
|
||||
md5: fda6619ba96bf00b849192f5e7460622
|
||||
zipped_file: sc3000.rom
|
||||
segaai:
|
||||
files:
|
||||
|
||||
@@ -7,6 +7,7 @@ base_destination: system
|
||||
cores: all_libretro
|
||||
hash_type: sha1
|
||||
verification_mode: existence
|
||||
case_insensitive_fs: true
|
||||
systems:
|
||||
3do:
|
||||
files:
|
||||
|
||||
@@ -5,6 +5,7 @@ source: "https://raw.githubusercontent.com/RetroBat-Official/emulatorlauncher/ma
|
||||
base_destination: bios
|
||||
hash_type: md5
|
||||
verification_mode: md5
|
||||
case_insensitive_fs: true
|
||||
cores:
|
||||
- 81
|
||||
- a5200
|
||||
|
||||
+18
-18
@@ -1613,23 +1613,23 @@ systems:
|
||||
- name: plus3-0.rom
|
||||
destination: zxs/plus3-0.rom
|
||||
required: true
|
||||
sha1: a837f66977040f7b51ed053a2483c10f3d070ab7
|
||||
md5: 3abdc20e72890a750dd3c745d286dfba
|
||||
crc32: a10230c0
|
||||
sha1: e319ed08b4d53a5e421a75ea00ea02039ba6555b
|
||||
md5: 9833b8b73384dd5fa3678377ff00a2bb
|
||||
crc32: 17373da2
|
||||
size: 16384
|
||||
- name: plus3-1.rom
|
||||
destination: zxs/plus3-1.rom
|
||||
required: true
|
||||
sha1: 6a4364f25513e4079f048f2de131a896d30edc64
|
||||
md5: 8361a1d9c8bcef89c0c39293776564ad
|
||||
crc32: 09b9c3ca
|
||||
sha1: c9969fc36095a59787554026a9adc3b87678c794
|
||||
md5: 0f711ceb5ab801b4701989982e0f334c
|
||||
crc32: f1d1d99e
|
||||
size: 16384
|
||||
- name: plus3-2.rom
|
||||
destination: zxs/plus3-2.rom
|
||||
required: true
|
||||
sha1: 0a747cc0b827a94b4fd74cfd818ca792437a38f7
|
||||
md5: f36c5c2d1f2a682caadeaa6f947db0da
|
||||
crc32: a60285a0
|
||||
sha1: 22e50c6ba4157a3f6a821bd9937cd26e292775c6
|
||||
md5: 3b6dd659d5e4ec97f0e2f7878152c987
|
||||
crc32: 3dbf351d
|
||||
size: 16384
|
||||
- name: plus3-3.rom
|
||||
destination: zxs/plus3-3.rom
|
||||
@@ -1641,23 +1641,23 @@ systems:
|
||||
- name: plus3e-0.rom
|
||||
destination: zxs/plus3e-0.rom
|
||||
required: true
|
||||
sha1: a837f66977040f7b51ed053a2483c10f3d070ab7
|
||||
md5: 3abdc20e72890a750dd3c745d286dfba
|
||||
crc32: a10230c0
|
||||
sha1: 649fbd233490bf58b35350b0123d36caaaa011eb
|
||||
md5: bc123f625e245c225f92ef05933ed134
|
||||
crc32: 7f4a5482
|
||||
size: 16384
|
||||
- name: plus3e-1.rom
|
||||
destination: zxs/plus3e-1.rom
|
||||
required: true
|
||||
sha1: 6a4364f25513e4079f048f2de131a896d30edc64
|
||||
md5: 8361a1d9c8bcef89c0c39293776564ad
|
||||
crc32: 09b9c3ca
|
||||
sha1: f12198108cbb14de4f03c6695bc16d08c85ee214
|
||||
md5: 617364264c587d20c9fc4746c29679f2
|
||||
crc32: 5aeb4675
|
||||
size: 16384
|
||||
- name: plus3e-2.rom
|
||||
destination: zxs/plus3e-2.rom
|
||||
required: true
|
||||
sha1: 0a747cc0b827a94b4fd74cfd818ca792437a38f7
|
||||
md5: f36c5c2d1f2a682caadeaa6f947db0da
|
||||
crc32: a60285a0
|
||||
sha1: 773633dce5ba323a9e00d9d0f9e4d8c295df7c87
|
||||
md5: c363e95dcd0a90e6e7f847e6e47e0179
|
||||
crc32: 504755cb
|
||||
size: 16384
|
||||
- name: plus3e-3.rom
|
||||
destination: zxs/plus3e-3.rom
|
||||
|
||||
+54
-4
@@ -759,16 +759,18 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
|
||||
Returns {filename: {"checks": [str], "size": int|None, "min_size": int|None,
|
||||
"max_size": int|None, "crc32": str|None, "md5": str|None, "sha1": str|None,
|
||||
"adler32": str|None, "crypto_only": [str]}}.
|
||||
"adler32": str|None, "crypto_only": [str], "per_emulator": {emu: detail}}}.
|
||||
|
||||
``crypto_only`` lists validation types we cannot reproduce (signature, crypto)
|
||||
so callers can report them as non-verifiable rather than silently skipping.
|
||||
|
||||
``per_emulator`` preserves each core's individual checks, source_ref, and
|
||||
expected values before merging, for ground truth reporting.
|
||||
|
||||
When multiple emulators reference the same file, merges checks (union).
|
||||
Raises ValueError if two profiles declare conflicting values.
|
||||
"""
|
||||
index: dict[str, dict] = {}
|
||||
sources: dict[str, dict[str, str]] = {}
|
||||
for emu_name, profile in profiles.items():
|
||||
if profile.get("type") in ("launcher", "alias"):
|
||||
continue
|
||||
@@ -785,9 +787,8 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
"min_size": None, "max_size": None,
|
||||
"crc32": set(), "md5": set(), "sha1": set(), "sha256": set(),
|
||||
"adler32": set(), "crypto_only": set(),
|
||||
"emulators": set(),
|
||||
"emulators": set(), "per_emulator": {},
|
||||
}
|
||||
sources[fname] = {}
|
||||
index[fname]["emulators"].add(emu_name)
|
||||
index[fname]["checks"].update(checks)
|
||||
# Track non-reproducible crypto checks
|
||||
@@ -830,6 +831,34 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
if norm.startswith("0x"):
|
||||
norm = norm[2:]
|
||||
index[fname]["adler32"].add(norm)
|
||||
# Per-emulator ground truth detail
|
||||
expected: dict = {}
|
||||
if "size" in checks:
|
||||
for key in ("size", "min_size", "max_size"):
|
||||
if f.get(key) is not None:
|
||||
expected[key] = f[key]
|
||||
for hash_type in ("crc32", "md5", "sha1", "sha256"):
|
||||
if hash_type in checks and f.get(hash_type):
|
||||
expected[hash_type] = f[hash_type]
|
||||
adler_val_pe = f.get("known_hash_adler32") or f.get("adler32")
|
||||
if adler_val_pe:
|
||||
expected["adler32"] = adler_val_pe
|
||||
pe_entry = {
|
||||
"checks": sorted(checks),
|
||||
"source_ref": f.get("source_ref"),
|
||||
"expected": expected,
|
||||
}
|
||||
pe = index[fname]["per_emulator"]
|
||||
if emu_name in pe:
|
||||
# Merge checks from multiple file entries for same emulator
|
||||
existing = pe[emu_name]
|
||||
merged_checks = sorted(set(existing["checks"]) | set(pe_entry["checks"]))
|
||||
existing["checks"] = merged_checks
|
||||
existing["expected"].update(pe_entry["expected"])
|
||||
if pe_entry["source_ref"] and not existing["source_ref"]:
|
||||
existing["source_ref"] = pe_entry["source_ref"]
|
||||
else:
|
||||
pe[emu_name] = pe_entry
|
||||
# Convert sets to sorted tuples/lists for determinism
|
||||
for v in index.values():
|
||||
v["checks"] = sorted(v["checks"])
|
||||
@@ -839,6 +868,27 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||
return index
|
||||
|
||||
|
||||
def build_ground_truth(filename: str, validation_index: dict[str, dict]) -> list[dict]:
|
||||
"""Format per-emulator ground truth for a file from the validation index.
|
||||
|
||||
Returns a sorted list of {emulator, checks, source_ref, expected} dicts.
|
||||
Returns [] if the file has no emulator validation data.
|
||||
"""
|
||||
entry = validation_index.get(filename)
|
||||
if not entry or not entry.get("per_emulator"):
|
||||
return []
|
||||
result = []
|
||||
for emu_name in sorted(entry["per_emulator"]):
|
||||
detail = entry["per_emulator"][emu_name]
|
||||
result.append({
|
||||
"emulator": emu_name,
|
||||
"checks": detail["checks"],
|
||||
"source_ref": detail.get("source_ref"),
|
||||
"expected": detail.get("expected", {}),
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def check_file_validation(
|
||||
local_path: str, filename: str, validation_index: dict[str, dict],
|
||||
bios_dir: str = "bios",
|
||||
|
||||
+152
-6
@@ -250,11 +250,16 @@ def generate_pack(
|
||||
zip_path = os.path.join(output_dir, zip_name)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Case-insensitive dedup only for platforms targeting Windows/macOS.
|
||||
# Linux-only platforms (Batocera, Recalbox, RetroDECK, Lakka, RomM)
|
||||
# are case-sensitive and may have distinct files like DISK.ROM vs disk.rom.
|
||||
case_insensitive = config.get("case_insensitive_fs", False)
|
||||
|
||||
total_files = 0
|
||||
missing_files = []
|
||||
user_provided = []
|
||||
seen_destinations: set[str] = set()
|
||||
seen_lower: set[str] = set() # case-insensitive dedup for Windows/macOS
|
||||
seen_lower: set[str] = set() # only used when case_insensitive=True
|
||||
# Per-file status: worst status wins (missing > untested > ok)
|
||||
file_status: dict[str, str] = {}
|
||||
file_reasons: dict[str, str] = {}
|
||||
@@ -293,7 +298,7 @@ def generate_pack(
|
||||
full_dest = dest
|
||||
|
||||
dedup_key = full_dest
|
||||
already_packed = dedup_key in seen_destinations or dedup_key.lower() in seen_lower
|
||||
already_packed = dedup_key in seen_destinations or (case_insensitive and dedup_key.lower() in seen_lower)
|
||||
|
||||
storage = file_entry.get("storage", "embedded")
|
||||
|
||||
@@ -301,6 +306,7 @@ def generate_pack(
|
||||
if already_packed:
|
||||
continue
|
||||
seen_destinations.add(dedup_key)
|
||||
if case_insensitive:
|
||||
seen_lower.add(dedup_key.lower())
|
||||
file_status.setdefault(dedup_key, "ok")
|
||||
instructions = file_entry.get("instructions", "Please provide this file manually.")
|
||||
@@ -326,6 +332,7 @@ def generate_pack(
|
||||
else:
|
||||
zf.write(tmp_path, full_dest)
|
||||
seen_destinations.add(dedup_key)
|
||||
if case_insensitive:
|
||||
seen_lower.add(dedup_key.lower())
|
||||
file_status.setdefault(dedup_key, "ok")
|
||||
total_files += 1
|
||||
@@ -401,6 +408,7 @@ def generate_pack(
|
||||
if already_packed:
|
||||
continue
|
||||
seen_destinations.add(dedup_key)
|
||||
if case_insensitive:
|
||||
seen_lower.add(dedup_key.lower())
|
||||
|
||||
extract = file_entry.get("extract", False)
|
||||
@@ -437,7 +445,7 @@ def generate_pack(
|
||||
if full_dest in seen_destinations:
|
||||
continue
|
||||
# Skip case-insensitive duplicates (Windows/macOS FS safety)
|
||||
if full_dest.lower() in seen_lower:
|
||||
if full_dest.lower() in seen_lower and case_insensitive:
|
||||
continue
|
||||
|
||||
local_path, status = resolve_file(fe, db, bios_dir, zip_contents)
|
||||
@@ -449,6 +457,7 @@ def generate_pack(
|
||||
else:
|
||||
zf.write(local_path, full_dest)
|
||||
seen_destinations.add(full_dest)
|
||||
if case_insensitive:
|
||||
seen_lower.add(full_dest.lower())
|
||||
core_count += 1
|
||||
total_files += 1
|
||||
@@ -468,15 +477,21 @@ def generate_pack(
|
||||
print(f" WARNING: data directory '{ref_key}' not cached at {local_path} — run refresh_data_dirs.py")
|
||||
continue
|
||||
dd_dest = dd.get("destination", "")
|
||||
dd_prefix = f"{base_dest}/{dd_dest}" if base_dest else dd_dest
|
||||
if base_dest and dd_dest:
|
||||
dd_prefix = f"{base_dest}/{dd_dest}"
|
||||
elif base_dest:
|
||||
dd_prefix = base_dest
|
||||
else:
|
||||
dd_prefix = dd_dest
|
||||
for root, _dirs, filenames in os.walk(local_path):
|
||||
for fname in filenames:
|
||||
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:
|
||||
if full in seen_destinations or full.lower() in seen_lower and case_insensitive:
|
||||
continue
|
||||
seen_destinations.add(full)
|
||||
if case_insensitive:
|
||||
seen_lower.add(full.lower())
|
||||
zf.write(src, full)
|
||||
total_files += 1
|
||||
@@ -1156,16 +1171,130 @@ def generate_sha256sums(output_dir: str) -> str | None:
|
||||
return sums_path
|
||||
|
||||
|
||||
def verify_and_finalize_packs(output_dir: str, db: dict) -> bool:
|
||||
def verify_pack_against_platform(
|
||||
zip_path: str, platform_name: str, platforms_dir: str,
|
||||
db: dict | None = None, emulators_dir: str = "emulators",
|
||||
emu_profiles: dict | None = None,
|
||||
) -> tuple[bool, int, int, list[str]]:
|
||||
"""Verify a pack ZIP against its platform config and core requirements.
|
||||
|
||||
Checks:
|
||||
1. Every baseline file declared by the platform exists in the ZIP
|
||||
at the correct destination path
|
||||
2. Every in-repo core extra file (from emulator profiles) is present
|
||||
3. No duplicate entries
|
||||
4. No path anomalies (double slash, absolute, traversal)
|
||||
5. No unexpected zero-byte BIOS files
|
||||
|
||||
Returns (all_ok, checked, present, errors).
|
||||
"""
|
||||
from collections import Counter
|
||||
from verify import find_undeclared_files
|
||||
|
||||
config = load_platform_config(platform_name, platforms_dir)
|
||||
base_dest = config.get("base_destination", "")
|
||||
errors: list[str] = []
|
||||
checked = 0
|
||||
present = 0
|
||||
|
||||
if emu_profiles is None:
|
||||
emu_profiles = load_emulator_profiles(emulators_dir)
|
||||
|
||||
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||
zip_set = set(zf.namelist())
|
||||
zip_lower = {n.lower(): n for n in zip_set}
|
||||
|
||||
# Structural checks
|
||||
dupes = sum(1 for c in Counter(zf.namelist()).values() if c > 1)
|
||||
if dupes:
|
||||
errors.append(f"{dupes} duplicate entries")
|
||||
for n in zip_set:
|
||||
if "//" in n:
|
||||
errors.append(f"double slash: {n}")
|
||||
if n.startswith("/"):
|
||||
errors.append(f"absolute path: {n}")
|
||||
if ".." in n:
|
||||
errors.append(f"path traversal: {n}")
|
||||
|
||||
# Zero-byte check (exclude Dolphin GraphicMods markers)
|
||||
for info in zf.infolist():
|
||||
if info.file_size == 0 and not info.is_dir():
|
||||
if "GraphicMods" not in info.filename and info.filename != "manifest.json":
|
||||
errors.append(f"zero-byte: {info.filename}")
|
||||
|
||||
# 1. Baseline file presence
|
||||
baseline_checked = 0
|
||||
baseline_present = 0
|
||||
for sys_id, system in config.get("systems", {}).items():
|
||||
for fe in system.get("files", []):
|
||||
dest = fe.get("destination", fe.get("name", ""))
|
||||
if not dest:
|
||||
continue
|
||||
expected = f"{base_dest}/{dest}" if base_dest else dest
|
||||
baseline_checked += 1
|
||||
|
||||
if expected in zip_set or expected.lower() in zip_lower:
|
||||
baseline_present += 1
|
||||
else:
|
||||
errors.append(f"baseline missing: {expected}")
|
||||
|
||||
# 2. Core extras presence (files from emulator profiles, in repo)
|
||||
core_checked = 0
|
||||
core_present = 0
|
||||
if db is not None:
|
||||
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles)
|
||||
for u in undeclared:
|
||||
if not u["in_repo"]:
|
||||
continue
|
||||
dest = u.get("path") or u["name"]
|
||||
if base_dest:
|
||||
full = f"{base_dest}/{dest}"
|
||||
elif "/" not in dest:
|
||||
full = f"bios/{dest}"
|
||||
else:
|
||||
full = dest
|
||||
core_checked += 1
|
||||
|
||||
if full in zip_set or full.lower() in zip_lower:
|
||||
core_present += 1
|
||||
# Not an error if missing — some get deduped or filtered
|
||||
|
||||
checked = baseline_checked + core_checked
|
||||
present = baseline_present + core_present
|
||||
|
||||
return (len(errors) == 0, checked, present, errors,
|
||||
baseline_checked, baseline_present, core_checked, core_present)
|
||||
|
||||
|
||||
def verify_and_finalize_packs(output_dir: str, db: dict,
|
||||
platforms_dir: str = "platforms") -> bool:
|
||||
"""Verify all packs, inject manifests, generate SHA256SUMS.
|
||||
|
||||
Two-stage verification:
|
||||
1. Hash check against database.json (integrity)
|
||||
2. Extract + verify against platform config (conformance)
|
||||
|
||||
Returns True if all packs pass verification.
|
||||
"""
|
||||
all_ok = True
|
||||
|
||||
# Map ZIP names to platform names
|
||||
pack_to_platform: dict[str, list[str]] = {}
|
||||
for name in sorted(os.listdir(output_dir)):
|
||||
if not name.endswith(".zip"):
|
||||
continue
|
||||
for pname in list_registered_platforms(platforms_dir):
|
||||
cfg = load_platform_config(pname, platforms_dir)
|
||||
display = cfg.get("platform", pname).replace(" ", "_")
|
||||
if display in name or display.replace("_", "") in name.replace("_", ""):
|
||||
pack_to_platform.setdefault(name, []).append(pname)
|
||||
|
||||
for name in sorted(os.listdir(output_dir)):
|
||||
if not name.endswith(".zip"):
|
||||
continue
|
||||
zip_path = os.path.join(output_dir, name)
|
||||
|
||||
# Stage 1: database integrity
|
||||
ok, manifest = verify_pack(zip_path, db)
|
||||
summary = manifest["summary"]
|
||||
status = "OK" if ok else "ERRORS"
|
||||
@@ -1176,6 +1305,23 @@ def verify_and_finalize_packs(output_dir: str, db: dict) -> bool:
|
||||
print(f" ERROR: {err}")
|
||||
all_ok = False
|
||||
inject_manifest(zip_path, manifest)
|
||||
|
||||
# Stage 2: platform conformance (extract + verify)
|
||||
platforms = pack_to_platform.get(name, [])
|
||||
for pname in platforms:
|
||||
(p_ok, total, matched, p_errors,
|
||||
bl_checked, bl_present, core_checked, core_present) = \
|
||||
verify_pack_against_platform(
|
||||
zip_path, pname, platforms_dir, db=db,
|
||||
)
|
||||
status = "OK" if p_ok else "FAILED"
|
||||
print(f" platform {pname}: {bl_present}/{bl_checked} baseline, "
|
||||
f"{core_present}/{core_checked} cores, {status}")
|
||||
if not p_ok:
|
||||
for err in p_errors:
|
||||
print(f" {err}")
|
||||
all_ok = False
|
||||
|
||||
generate_sha256sums(output_dir)
|
||||
return all_ok
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
"python scripts/verify.py --all",
|
||||
"python scripts/verify.py --platform batocera",
|
||||
"python scripts/verify.py --emulator flycast",
|
||||
"python scripts/verify.py --platform retroarch --verbose # emulator ground truth",
|
||||
"```",
|
||||
"",
|
||||
f"Only dependency: Python 3 + `pyyaml`.",
|
||||
|
||||
+239
-37
@@ -35,7 +35,8 @@ except ImportError:
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
_build_validation_index, build_zip_contents_index, check_file_validation,
|
||||
_build_validation_index, _parse_validation, build_ground_truth,
|
||||
build_zip_contents_index, check_file_validation,
|
||||
check_inside_zip, compute_hashes, filter_files_by_mode,
|
||||
filter_systems_by_target, group_identical_platforms, list_emulator_profiles,
|
||||
list_system_ids, load_data_dir_registry, load_emulator_profiles,
|
||||
@@ -201,6 +202,24 @@ def compute_severity(
|
||||
# Cross-reference: undeclared files used by cores
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _build_expected(file_entry: dict, checks: list[str]) -> dict:
|
||||
"""Extract expected validation values from an emulator profile file entry."""
|
||||
expected: dict = {}
|
||||
if not checks:
|
||||
return expected
|
||||
if "size" in checks:
|
||||
for key in ("size", "min_size", "max_size"):
|
||||
if file_entry.get(key) is not None:
|
||||
expected[key] = file_entry[key]
|
||||
for hash_type in ("crc32", "md5", "sha1", "sha256"):
|
||||
if hash_type in checks and file_entry.get(hash_type):
|
||||
expected[hash_type] = file_entry[hash_type]
|
||||
adler_val = file_entry.get("known_hash_adler32") or file_entry.get("adler32")
|
||||
if adler_val:
|
||||
expected["adler32"] = adler_val
|
||||
return expected
|
||||
|
||||
def find_undeclared_files(
|
||||
config: dict,
|
||||
emulators_dir: str,
|
||||
@@ -247,6 +266,9 @@ def find_undeclared_files(
|
||||
fname = f.get("name", "")
|
||||
if not fname or fname in seen:
|
||||
continue
|
||||
# Skip pattern placeholders (e.g., <user-selected>.bin)
|
||||
if "<" in fname or ">" in fname or "*" in fname:
|
||||
continue
|
||||
# Mode filtering: skip files incompatible with platform's usage
|
||||
file_mode = f.get("mode")
|
||||
if file_mode == "standalone" and not is_standalone:
|
||||
@@ -264,6 +286,7 @@ def find_undeclared_files(
|
||||
|
||||
in_repo = fname in by_name or fname.rsplit("/", 1)[-1] in by_name
|
||||
seen.add(fname)
|
||||
checks = _parse_validation(f.get("validation"))
|
||||
undeclared.append({
|
||||
"emulator": profile.get("emulator", emu_name),
|
||||
"name": fname,
|
||||
@@ -273,6 +296,9 @@ def find_undeclared_files(
|
||||
"category": f.get("category", "bios"),
|
||||
"in_repo": in_repo,
|
||||
"note": f.get("note", ""),
|
||||
"checks": sorted(checks) if checks else [],
|
||||
"source_ref": f.get("source_ref"),
|
||||
"expected": _build_expected(f, checks),
|
||||
})
|
||||
|
||||
return undeclared
|
||||
@@ -322,7 +348,13 @@ def find_exclusion_notes(
|
||||
})
|
||||
continue
|
||||
|
||||
# Count standalone-only files
|
||||
# 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(
|
||||
standalone_set & {str(c) for c in profile.get("cores", [])}
|
||||
)
|
||||
if not is_standalone:
|
||||
standalone_files = [f for f in profile.get("files", []) if f.get("mode") == "standalone"]
|
||||
if standalone_files:
|
||||
names = [f["name"] for f in standalone_files[:3]]
|
||||
@@ -434,6 +466,9 @@ def verify_platform(
|
||||
result["discrepancy"] = f"{platform} says OK but {emus} says {reason}"
|
||||
result["system"] = sys_id
|
||||
result["hle_fallback"] = hle_index.get(file_entry.get("name", ""), False)
|
||||
result["ground_truth"] = build_ground_truth(
|
||||
file_entry.get("name", ""), validation_index,
|
||||
)
|
||||
details.append(result)
|
||||
|
||||
# Aggregate by destination
|
||||
@@ -466,15 +501,34 @@ def verify_platform(
|
||||
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles, target_cores=target_cores)
|
||||
exclusions = find_exclusion_notes(config, emulators_dir, emu_profiles, target_cores=target_cores)
|
||||
|
||||
# Ground truth coverage
|
||||
gt_filenames = set(validation_index)
|
||||
dest_to_name: dict[str, str] = {}
|
||||
for sys_id, system in verify_systems.items():
|
||||
for fe in system.get("files", []):
|
||||
dest = fe.get("destination", fe.get("name", ""))
|
||||
if not dest:
|
||||
dest = f"{sys_id}/{fe.get('name', '')}"
|
||||
dest_to_name.setdefault(dest, fe.get("name", ""))
|
||||
with_validation = sum(
|
||||
1 for dest in file_status if dest_to_name.get(dest, "") in gt_filenames
|
||||
)
|
||||
total = len(file_status)
|
||||
|
||||
return {
|
||||
"platform": platform,
|
||||
"verification_mode": mode,
|
||||
"total_files": len(file_status),
|
||||
"total_files": total,
|
||||
"severity_counts": counts,
|
||||
"status_counts": status_counts,
|
||||
"undeclared_files": undeclared,
|
||||
"exclusion_notes": exclusions,
|
||||
"details": details,
|
||||
"ground_truth_coverage": {
|
||||
"with_validation": with_validation,
|
||||
"platform_only": total - with_validation,
|
||||
"total": total,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -482,7 +536,39 @@ def verify_platform(
|
||||
# Output
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
def _format_ground_truth_aggregate(ground_truth: list[dict]) -> str:
|
||||
"""Format ground truth as a single aggregated line.
|
||||
|
||||
Example: beetle_psx [md5], pcsx_rearmed [existence]
|
||||
"""
|
||||
parts = []
|
||||
for gt in ground_truth:
|
||||
checks_label = "+".join(gt["checks"]) if gt["checks"] else "existence"
|
||||
parts.append(f"{gt['emulator']} [{checks_label}]")
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
def _format_ground_truth_verbose(ground_truth: list[dict]) -> list[str]:
|
||||
"""Format ground truth as one line per core with expected values and source ref.
|
||||
|
||||
Example: handy validates size=512,crc32=0d973c9d [rom.h:48-49]
|
||||
"""
|
||||
lines = []
|
||||
for gt in ground_truth:
|
||||
checks_label = "+".join(gt["checks"]) if gt["checks"] else "existence"
|
||||
expected = gt.get("expected", {})
|
||||
if expected:
|
||||
vals = ",".join(f"{k}={v}" for k, v in sorted(expected.items()))
|
||||
part = f"{gt['emulator']} validates {vals}"
|
||||
else:
|
||||
part = f"{gt['emulator']} validates {checks_label}"
|
||||
if gt.get("source_ref"):
|
||||
part += f" [{gt['source_ref']}]"
|
||||
lines.append(part)
|
||||
return lines
|
||||
|
||||
|
||||
def print_platform_result(result: dict, group: list[str], verbose: bool = False) -> None:
|
||||
mode = result["verification_mode"]
|
||||
total = result["total_files"]
|
||||
c = result["severity_counts"]
|
||||
@@ -525,6 +611,13 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
reason = d.get("reason", "")
|
||||
print(f" UNTESTED ({req}{hle}): {key} — {reason}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.MISSING:
|
||||
key = f"{d['system']}/{d['name']}"
|
||||
@@ -534,6 +627,13 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
print(f" MISSING ({req}{hle}): {key}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
disc = d.get("discrepancy")
|
||||
if disc:
|
||||
@@ -542,6 +642,27 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
continue
|
||||
seen_details.add(key)
|
||||
print(f" DISCREPANCY: {key} — {disc}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
|
||||
if verbose:
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.OK:
|
||||
key = f"{d['system']}/{d['name']}"
|
||||
if key in seen_details:
|
||||
continue
|
||||
seen_details.add(key)
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
print(f" OK ({req}): {key}")
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
|
||||
# Cross-reference: undeclared files used by cores
|
||||
undeclared = result.get("undeclared_files", [])
|
||||
@@ -555,45 +676,73 @@ def print_platform_result(result: dict, group: list[str]) -> None:
|
||||
opt_in_repo = [u for u in bios_files if not u["required"] and u["in_repo"]]
|
||||
opt_not_in_repo = [u for u in bios_files if not u["required"] and not u["in_repo"]]
|
||||
|
||||
summary_parts = []
|
||||
# Core coverage: files from emulator profiles not declared by the platform
|
||||
core_in_pack = len(req_in_repo) + len(opt_in_repo)
|
||||
core_missing_req = len(req_not_in_repo) + len(req_hle_not_in_repo)
|
||||
core_missing_opt = len(opt_not_in_repo)
|
||||
core_total = len(bios_files)
|
||||
|
||||
print(f" Core files: {core_in_pack} in pack, {core_missing_req} required missing, {core_missing_opt} optional missing")
|
||||
|
||||
# Required NOT in repo = critical
|
||||
if req_not_in_repo:
|
||||
summary_parts.append(f"{len(req_not_in_repo)} required NOT in repo")
|
||||
for u in req_not_in_repo:
|
||||
print(f" MISSING (required): {u['emulator']} needs {u['name']}")
|
||||
checks = u.get("checks", [])
|
||||
if checks:
|
||||
if verbose:
|
||||
expected = u.get("expected", {})
|
||||
if expected:
|
||||
vals = ",".join(f"{k}={v}" for k, v in sorted(expected.items()))
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {vals}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {checks_label}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
print(f" [{checks_label}]")
|
||||
if req_hle_not_in_repo:
|
||||
summary_parts.append(f"{len(req_hle_not_in_repo)} required with HLE NOT in repo")
|
||||
if req_in_repo:
|
||||
summary_parts.append(f"{len(req_in_repo)} required in repo")
|
||||
if opt_in_repo:
|
||||
summary_parts.append(f"{len(opt_in_repo)} optional in repo")
|
||||
if opt_not_in_repo:
|
||||
summary_parts.append(f"{len(opt_not_in_repo)} optional NOT in repo")
|
||||
for u in req_hle_not_in_repo:
|
||||
print(f" MISSING (required, HLE fallback): {u['emulator']} needs {u['name']}")
|
||||
checks = u.get("checks", [])
|
||||
if checks:
|
||||
if verbose:
|
||||
expected = u.get("expected", {})
|
||||
if expected:
|
||||
vals = ",".join(f"{k}={v}" for k, v in sorted(expected.items()))
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {vals}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
ref_part = f" [{u['source_ref']}]" if u.get("source_ref") else ""
|
||||
print(f" validates {checks_label}{ref_part}")
|
||||
else:
|
||||
checks_label = "+".join(checks)
|
||||
print(f" [{checks_label}]")
|
||||
|
||||
if game_data:
|
||||
gd_missing = [u for u in game_data if not u["in_repo"]]
|
||||
gd_present = [u for u in game_data if u["in_repo"]]
|
||||
if gd_missing:
|
||||
summary_parts.append(f"{len(gd_missing)} game_data NOT in repo")
|
||||
if gd_present:
|
||||
summary_parts.append(f"{len(gd_present)} game_data in repo")
|
||||
print(f" Core gaps: {len(undeclared)} undeclared ({', '.join(summary_parts)})")
|
||||
if gd_missing or gd_present:
|
||||
print(f" Game data: {len(gd_present)} in pack, {len(gd_missing)} missing")
|
||||
|
||||
# Show critical gaps (required bios + no HLE + not in repo)
|
||||
for u in req_not_in_repo:
|
||||
print(f" {u['emulator']} → {u['name']} (required, NOT in repo)")
|
||||
# Show required with HLE (core works but not ideal)
|
||||
for u in req_hle_not_in_repo:
|
||||
print(f" {u['emulator']} → {u['name']} (required, HLE available, NOT in repo)")
|
||||
# Show required in repo (actionable)
|
||||
for u in req_in_repo[:10]:
|
||||
print(f" {u['emulator']} → {u['name']} (required, in repo)")
|
||||
if len(req_in_repo) > 10:
|
||||
print(f" ... and {len(req_in_repo) - 10} more required in repo")
|
||||
|
||||
# Intentional exclusions (explain why certain emulator files are NOT included)
|
||||
# No external files (explain why certain emulator files are NOT included)
|
||||
exclusions = result.get("exclusion_notes", [])
|
||||
if exclusions:
|
||||
print(f" Intentional exclusions ({len(exclusions)}):")
|
||||
print(f" No external files ({len(exclusions)}):")
|
||||
for ex in exclusions:
|
||||
print(f" {ex['emulator']} — {ex['detail']} [{ex['reason']}]")
|
||||
|
||||
# Ground truth coverage footer
|
||||
gt_cov = result.get("ground_truth_coverage")
|
||||
if gt_cov and gt_cov["total"] > 0:
|
||||
pct = gt_cov["with_validation"] * 100 // gt_cov["total"]
|
||||
print(f" Ground truth: {gt_cov['with_validation']}/{gt_cov['total']} files have emulator validation ({pct}%)")
|
||||
if gt_cov["platform_only"]:
|
||||
print(f" {gt_cov['platform_only']} platform-only (no emulator profile)")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Emulator/system mode verification
|
||||
@@ -671,6 +820,7 @@ def verify_emulator(
|
||||
details = []
|
||||
file_status: dict[str, str] = {}
|
||||
file_severity: dict[str, str] = {}
|
||||
dest_to_name: dict[str, str] = {}
|
||||
data_dir_notices: list[str] = []
|
||||
|
||||
for emu_name, profile in selected:
|
||||
@@ -692,6 +842,7 @@ def verify_emulator(
|
||||
"name": f"({emu_name})", "status": Status.OK,
|
||||
"required": False, "system": "",
|
||||
"note": f"No files needed for {profile.get('emulator', emu_name)}",
|
||||
"ground_truth": [],
|
||||
})
|
||||
continue
|
||||
|
||||
@@ -715,8 +866,10 @@ def verify_emulator(
|
||||
"required": required}
|
||||
result["system"] = file_entry.get("system", "")
|
||||
result["hle_fallback"] = False
|
||||
result["ground_truth"] = build_ground_truth(archive, validation_index)
|
||||
details.append(result)
|
||||
dest = archive
|
||||
dest_to_name[dest] = archive
|
||||
cur = result["status"]
|
||||
prev = file_status.get(dest)
|
||||
if prev is None or _STATUS_ORDER.get(cur, 0) > _STATUS_ORDER.get(prev, 0):
|
||||
@@ -754,10 +907,12 @@ def verify_emulator(
|
||||
|
||||
result["system"] = file_entry.get("system", "")
|
||||
result["hle_fallback"] = hle
|
||||
result["ground_truth"] = build_ground_truth(name, validation_index)
|
||||
details.append(result)
|
||||
|
||||
# Aggregate by destination (path if available, else name)
|
||||
dest = file_entry.get("path", "") or name
|
||||
dest_to_name[dest] = name
|
||||
cur = result["status"]
|
||||
prev = file_status.get(dest)
|
||||
if prev is None or _STATUS_ORDER.get(cur, 0) > _STATUS_ORDER.get(prev, 0):
|
||||
@@ -776,14 +931,25 @@ def verify_emulator(
|
||||
|
||||
label = _effective_validation_label(details, validation_index)
|
||||
|
||||
gt_filenames = set(validation_index)
|
||||
total = len(file_status)
|
||||
with_validation = sum(
|
||||
1 for dest in file_status if dest_to_name.get(dest, "") in gt_filenames
|
||||
)
|
||||
|
||||
return {
|
||||
"emulators": [n for n, _ in selected],
|
||||
"verification_mode": label,
|
||||
"total_files": len(file_status),
|
||||
"total_files": total,
|
||||
"severity_counts": counts,
|
||||
"status_counts": status_counts,
|
||||
"details": details,
|
||||
"data_dir_notices": sorted(set(data_dir_notices)),
|
||||
"ground_truth_coverage": {
|
||||
"with_validation": with_validation,
|
||||
"platform_only": total - with_validation,
|
||||
"total": total,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -823,7 +989,7 @@ def verify_system(
|
||||
return verify_emulator(matching, emulators_dir, db, standalone)
|
||||
|
||||
|
||||
def print_emulator_result(result: dict) -> None:
|
||||
def print_emulator_result(result: dict, verbose: bool = False) -> None:
|
||||
"""Print verification result for emulator/system mode."""
|
||||
label = " + ".join(result["emulators"])
|
||||
mode = result["verification_mode"]
|
||||
@@ -851,6 +1017,13 @@ def print_emulator_result(result: dict) -> None:
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
reason = d.get("reason", "")
|
||||
print(f" UNTESTED ({req}{hle}): {d['name']} — {reason}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.MISSING:
|
||||
if d["name"] in seen:
|
||||
@@ -859,13 +1032,41 @@ def print_emulator_result(result: dict) -> None:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
hle = ", HLE available" if d.get("hle_fallback") else ""
|
||||
print(f" MISSING ({req}{hle}): {d['name']}")
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
if verbose:
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(f" {_format_ground_truth_aggregate(gt)}")
|
||||
for d in result["details"]:
|
||||
if d.get("note"):
|
||||
print(f" {d['note']}")
|
||||
|
||||
if verbose:
|
||||
for d in result["details"]:
|
||||
if d["status"] == Status.OK:
|
||||
if d["name"] in seen:
|
||||
continue
|
||||
seen.add(d["name"])
|
||||
gt = d.get("ground_truth", [])
|
||||
if gt:
|
||||
req = "required" if d.get("required", True) else "optional"
|
||||
print(f" OK ({req}): {d['name']}")
|
||||
for line in _format_ground_truth_verbose(gt):
|
||||
print(f" {line}")
|
||||
|
||||
for ref in result.get("data_dir_notices", []):
|
||||
print(f" Note: data directory '{ref}' required but not included (use refresh_data_dirs.py)")
|
||||
|
||||
# Ground truth coverage footer
|
||||
gt_cov = result.get("ground_truth_coverage")
|
||||
if gt_cov and gt_cov["total"] > 0:
|
||||
pct = gt_cov["with_validation"] * 100 // gt_cov["total"]
|
||||
print(f" Ground truth: {gt_cov['with_validation']}/{gt_cov['total']} files have emulator validation ({pct}%)")
|
||||
if gt_cov["platform_only"]:
|
||||
print(f" {gt_cov['platform_only']} platform-only (no emulator profile)")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Platform-native BIOS verification")
|
||||
@@ -882,6 +1083,7 @@ def main():
|
||||
parser.add_argument("--db", default=DEFAULT_DB)
|
||||
parser.add_argument("--platforms-dir", default=DEFAULT_PLATFORMS_DIR)
|
||||
parser.add_argument("--emulators-dir", default=DEFAULT_EMULATORS_DIR)
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Show emulator ground truth details")
|
||||
parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -929,7 +1131,7 @@ def main():
|
||||
result["details"] = [d for d in result["details"] if d["status"] != Status.OK]
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print_emulator_result(result)
|
||||
print_emulator_result(result, verbose=args.verbose)
|
||||
return
|
||||
|
||||
# System mode
|
||||
@@ -940,7 +1142,7 @@ def main():
|
||||
result["details"] = [d for d in result["details"] if d["status"] != Status.OK]
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print_emulator_result(result)
|
||||
print_emulator_result(result, verbose=args.verbose)
|
||||
return
|
||||
|
||||
# Platform mode (existing)
|
||||
@@ -994,7 +1196,7 @@ def main():
|
||||
|
||||
if not args.json:
|
||||
for result, group in group_results:
|
||||
print_platform_result(result, group)
|
||||
print_platform_result(result, group, verbose=args.verbose)
|
||||
print()
|
||||
|
||||
if args.json:
|
||||
|
||||
+189
-2
@@ -353,13 +353,15 @@ class TestE2E(unittest.TestCase):
|
||||
"files": [
|
||||
# Size validation — correct size (16 bytes = len(b"PRESENT_REQUIRED"))
|
||||
{"name": "present_req.bin", "required": True,
|
||||
"validation": ["size"], "size": 16},
|
||||
"validation": ["size"], "size": 16,
|
||||
"source_ref": "test.c:10-20"},
|
||||
# Size validation — wrong expected size
|
||||
{"name": "present_opt.bin", "required": False,
|
||||
"validation": ["size"], "size": 9999},
|
||||
# CRC32 validation — correct crc32
|
||||
{"name": "correct_hash.bin", "required": True,
|
||||
"validation": ["crc32"], "crc32": "91d0b1d3"},
|
||||
"validation": ["crc32"], "crc32": "91d0b1d3",
|
||||
"source_ref": "hash.c:42"},
|
||||
# CRC32 validation — wrong crc32
|
||||
{"name": "no_md5.bin", "required": False,
|
||||
"validation": ["crc32"], "crc32": "deadbeef"},
|
||||
@@ -1353,5 +1355,190 @@ class TestE2E(unittest.TestCase):
|
||||
self.assertNotIn("bios_b.bin", names)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Validation index per-emulator ground truth (Task: ground truth)
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
def test_111_validation_index_per_emulator(self):
|
||||
"""Validation index includes per-emulator detail for ground truth."""
|
||||
profiles = load_emulator_profiles(self.emulators_dir)
|
||||
index = _build_validation_index(profiles)
|
||||
entry = index["present_req.bin"]
|
||||
self.assertIn("per_emulator", entry)
|
||||
pe = entry["per_emulator"]
|
||||
self.assertIn("test_validation", pe)
|
||||
detail = pe["test_validation"]
|
||||
self.assertIn("size", detail["checks"])
|
||||
self.assertEqual(detail["expected"]["size"], 16)
|
||||
|
||||
def test_112_build_ground_truth(self):
|
||||
"""build_ground_truth returns per-emulator detail for a filename."""
|
||||
from common import build_ground_truth
|
||||
profiles = load_emulator_profiles(self.emulators_dir)
|
||||
index = _build_validation_index(profiles)
|
||||
gt = build_ground_truth("present_req.bin", index)
|
||||
self.assertIsInstance(gt, list)
|
||||
self.assertTrue(len(gt) >= 1)
|
||||
emu_names = {g["emulator"] for g in gt}
|
||||
self.assertIn("test_validation", emu_names)
|
||||
for g in gt:
|
||||
if g["emulator"] == "test_validation":
|
||||
self.assertIn("size", g["checks"])
|
||||
self.assertIn("source_ref", g)
|
||||
self.assertIn("expected", g)
|
||||
|
||||
def test_113_build_ground_truth_empty(self):
|
||||
"""build_ground_truth returns [] for unknown filename."""
|
||||
from common import build_ground_truth
|
||||
profiles = load_emulator_profiles(self.emulators_dir)
|
||||
index = _build_validation_index(profiles)
|
||||
gt = build_ground_truth("nonexistent.bin", index)
|
||||
self.assertEqual(gt, [])
|
||||
|
||||
def test_114_platform_result_has_ground_truth(self):
|
||||
"""verify_platform attaches ground_truth to each detail entry."""
|
||||
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)
|
||||
for d in result["details"]:
|
||||
self.assertIn("ground_truth", d)
|
||||
# present_req.bin has validation in test_validation profile
|
||||
for d in result["details"]:
|
||||
if d["name"] == "present_req.bin":
|
||||
self.assertTrue(len(d["ground_truth"]) >= 1)
|
||||
emu_names = {g["emulator"] for g in d["ground_truth"]}
|
||||
self.assertIn("test_validation", emu_names)
|
||||
break
|
||||
else:
|
||||
self.fail("present_req.bin not found in details")
|
||||
|
||||
def test_116_undeclared_files_have_ground_truth(self):
|
||||
"""find_undeclared_files attaches ground truth fields."""
|
||||
config = load_platform_config("test_existence", self.platforms_dir)
|
||||
profiles = load_emulator_profiles(self.emulators_dir)
|
||||
undeclared = find_undeclared_files(config, self.emulators_dir, self.db, profiles)
|
||||
for u in undeclared:
|
||||
self.assertIn("checks", u)
|
||||
self.assertIn("source_ref", u)
|
||||
self.assertIn("expected", u)
|
||||
|
||||
def test_117_platform_result_ground_truth_coverage(self):
|
||||
"""verify_platform includes ground truth coverage counts."""
|
||||
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)
|
||||
gt = result["ground_truth_coverage"]
|
||||
self.assertIn("with_validation", gt)
|
||||
self.assertIn("total", gt)
|
||||
self.assertIn("platform_only", gt)
|
||||
self.assertEqual(gt["total"], result["total_files"])
|
||||
self.assertEqual(gt["platform_only"], gt["total"] - gt["with_validation"])
|
||||
self.assertGreaterEqual(gt["with_validation"], 1)
|
||||
|
||||
def test_118_emulator_result_has_ground_truth(self):
|
||||
"""verify_emulator attaches ground_truth to each detail entry."""
|
||||
result = verify_emulator(["test_validation"], self.emulators_dir, self.db)
|
||||
for d in result["details"]:
|
||||
self.assertIn("ground_truth", d)
|
||||
# present_req.bin should have ground truth from test_validation
|
||||
for d in result["details"]:
|
||||
if d["name"] == "present_req.bin":
|
||||
self.assertTrue(len(d["ground_truth"]) >= 1)
|
||||
break
|
||||
|
||||
def test_119_emulator_result_ground_truth_coverage(self):
|
||||
"""verify_emulator includes ground truth coverage counts."""
|
||||
result = verify_emulator(["test_validation"], self.emulators_dir, self.db)
|
||||
gt = result["ground_truth_coverage"]
|
||||
self.assertEqual(gt["total"], result["total_files"])
|
||||
|
||||
def test_115_platform_result_ground_truth_empty_for_unknown(self):
|
||||
"""Files with no emulator validation get ground_truth=[]."""
|
||||
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)
|
||||
for d in result["details"]:
|
||||
if d["name"] == "missing_opt.bin":
|
||||
self.assertEqual(d["ground_truth"], [])
|
||||
break
|
||||
|
||||
|
||||
def test_120_format_ground_truth_aggregate(self):
|
||||
"""Aggregate format: one line with all cores."""
|
||||
from verify import _format_ground_truth_aggregate
|
||||
gt = [
|
||||
{"emulator": "beetle_psx", "checks": ["md5"], "source_ref": "libretro.cpp:252", "expected": {"md5": "abc"}},
|
||||
{"emulator": "pcsx_rearmed", "checks": ["existence"], "source_ref": None, "expected": {}},
|
||||
]
|
||||
line = _format_ground_truth_aggregate(gt)
|
||||
self.assertIn("beetle_psx", line)
|
||||
self.assertIn("[md5]", line)
|
||||
self.assertIn("pcsx_rearmed", line)
|
||||
self.assertIn("[existence]", line)
|
||||
|
||||
def test_121_format_ground_truth_verbose(self):
|
||||
"""Verbose format: one line per core with expected values and source ref."""
|
||||
from verify import _format_ground_truth_verbose
|
||||
gt = [
|
||||
{"emulator": "handy", "checks": ["size", "crc32"],
|
||||
"source_ref": "rom.h:48-49", "expected": {"size": 512, "crc32": "0d973c9d"}},
|
||||
]
|
||||
lines = _format_ground_truth_verbose(gt)
|
||||
self.assertEqual(len(lines), 1)
|
||||
self.assertIn("handy", lines[0])
|
||||
self.assertIn("size=512", lines[0])
|
||||
self.assertIn("crc32=0d973c9d", lines[0])
|
||||
self.assertIn("[rom.h:48-49]", lines[0])
|
||||
|
||||
def test_122_format_ground_truth_verbose_no_source_ref(self):
|
||||
"""Verbose format omits bracket when source_ref is None."""
|
||||
from verify import _format_ground_truth_verbose
|
||||
gt = [
|
||||
{"emulator": "core_a", "checks": ["existence"], "source_ref": None, "expected": {}},
|
||||
]
|
||||
lines = _format_ground_truth_verbose(gt)
|
||||
self.assertEqual(len(lines), 1)
|
||||
self.assertNotIn("[", lines[0])
|
||||
|
||||
def test_123_ground_truth_full_chain_verbose(self):
|
||||
"""Full chain: file -> platform -> emulator -> source_ref visible in ground_truth."""
|
||||
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)
|
||||
for d in result["details"]:
|
||||
if d["name"] == "present_req.bin":
|
||||
gt = d["ground_truth"]
|
||||
for g in gt:
|
||||
if g["emulator"] == "test_validation":
|
||||
self.assertIn("size", g["checks"])
|
||||
self.assertEqual(g["source_ref"], "test.c:10-20")
|
||||
self.assertEqual(g["expected"]["size"], 16)
|
||||
return
|
||||
self.fail("present_req.bin / test_validation ground truth not found")
|
||||
|
||||
def test_124_ground_truth_json_includes_all(self):
|
||||
"""JSON output includes ground_truth on all detail entries."""
|
||||
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
|
||||
filtered = [d for d in result["details"] if d["status"] != Status.OK]
|
||||
for d in filtered:
|
||||
self.assertIn("ground_truth", d)
|
||||
# Also check OK entries have it (before filtering)
|
||||
ok_entries = [d for d in result["details"] if d["status"] == Status.OK]
|
||||
for d in ok_entries:
|
||||
self.assertIn("ground_truth", d)
|
||||
|
||||
def test_125_ground_truth_coverage_in_md5_mode(self):
|
||||
"""MD5 platform also gets ground truth coverage."""
|
||||
config = load_platform_config("test_md5", self.platforms_dir)
|
||||
profiles = load_emulator_profiles(self.emulators_dir)
|
||||
result = verify_platform(config, self.db, self.emulators_dir, profiles)
|
||||
gt = result["ground_truth_coverage"]
|
||||
self.assertEqual(gt["total"], result["total_files"])
|
||||
self.assertGreaterEqual(gt["with_validation"], 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user