mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-15 13:22:31 -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 |
13
README.md
13
README.md
@@ -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
bios/Arcade/Arcade/.variants/naomi2.zip.533ef12a
Executable file
BIN
bios/Arcade/Arcade/.variants/naomi2.zip.533ef12a
Executable file
Binary file not shown.
BIN
bios/Arcade/Arcade/.variants/pgm.zip.c0c001ec
Normal file
BIN
bios/Arcade/Arcade/.variants/pgm.zip.c0c001ec
Normal file
Binary file not shown.
BIN
bios/Id Software/Doom/.variants/DOOM.WAD.997bae5e
Normal file
BIN
bios/Id Software/Doom/.variants/DOOM.WAD.997bae5e
Normal file
Binary file not shown.
BIN
bios/Id Software/Doom/.variants/DOOM2.WAD.c745f04a
Normal file
BIN
bios/Id Software/Doom/.variants/DOOM2.WAD.c745f04a
Normal file
Binary file not shown.
BIN
bios/Id Software/Doom/.variants/DOOM64.WAD.d041456b
Normal file
BIN
bios/Id Software/Doom/.variants/DOOM64.WAD.d041456b
Normal file
Binary file not shown.
BIN
bios/Id Software/Doom/.variants/PLUTONIA.WAD.816c7c6b
Normal file
BIN
bios/Id Software/Doom/.variants/PLUTONIA.WAD.816c7c6b
Normal file
Binary file not shown.
BIN
bios/Id Software/Doom/.variants/TNT.WAD.9820e2a3
Normal file
BIN
bios/Id Software/Doom/.variants/TNT.WAD.9820e2a3
Normal file
Binary file not shown.
BIN
bios/Microsoft/Xbox/.variants/Complex_4627.bin.6639b669
Normal file
BIN
bios/Microsoft/Xbox/.variants/Complex_4627.bin.6639b669
Normal file
Binary file not shown.
BIN
bios/SNK/Neo Geo CD/.variants/neocdz.zip.bf6b379c
Executable file
BIN
bios/SNK/Neo Geo CD/.variants/neocdz.zip.bf6b379c
Executable file
Binary file not shown.
BIN
bios/Sega/SC-3000/.variants/sc3000.zip.12de390b
Normal file
BIN
bios/Sega/SC-3000/.variants/sc3000.zip.12de390b
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
240
database.json
240
database.json
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,7 +306,8 @@ def generate_pack(
|
||||
if already_packed:
|
||||
continue
|
||||
seen_destinations.add(dedup_key)
|
||||
seen_lower.add(dedup_key.lower())
|
||||
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.")
|
||||
instr_name = f"INSTRUCTIONS_{file_entry['name']}.txt"
|
||||
@@ -326,7 +332,8 @@ def generate_pack(
|
||||
else:
|
||||
zf.write(tmp_path, full_dest)
|
||||
seen_destinations.add(dedup_key)
|
||||
seen_lower.add(dedup_key.lower())
|
||||
if case_insensitive:
|
||||
seen_lower.add(dedup_key.lower())
|
||||
file_status.setdefault(dedup_key, "ok")
|
||||
total_files += 1
|
||||
else:
|
||||
@@ -401,7 +408,8 @@ def generate_pack(
|
||||
if already_packed:
|
||||
continue
|
||||
seen_destinations.add(dedup_key)
|
||||
seen_lower.add(dedup_key.lower())
|
||||
if case_insensitive:
|
||||
seen_lower.add(dedup_key.lower())
|
||||
|
||||
extract = file_entry.get("extract", False)
|
||||
if extract and local_path.endswith(".zip"):
|
||||
@@ -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,7 +457,8 @@ def generate_pack(
|
||||
else:
|
||||
zf.write(local_path, full_dest)
|
||||
seen_destinations.add(full_dest)
|
||||
seen_lower.add(full_dest.lower())
|
||||
if case_insensitive:
|
||||
seen_lower.add(full_dest.lower())
|
||||
core_count += 1
|
||||
total_files += 1
|
||||
|
||||
@@ -468,16 +477,22 @@ 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)
|
||||
seen_lower.add(full.lower())
|
||||
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`.",
|
||||
|
||||
@@ -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,15 +348,21 @@ def find_exclusion_notes(
|
||||
})
|
||||
continue
|
||||
|
||||
# Count standalone-only files
|
||||
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]]
|
||||
more = f" +{len(standalone_files)-3}" if len(standalone_files) > 3 else ""
|
||||
notes.append({
|
||||
"emulator": emu_display, "reason": "standalone_only",
|
||||
"detail": f"{len(standalone_files)} files for standalone mode only ({', '.join(names)}{more})",
|
||||
})
|
||||
# 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]]
|
||||
more = f" +{len(standalone_files)-3}" if len(standalone_files) > 3 else ""
|
||||
notes.append({
|
||||
"emulator": emu_display, "reason": "standalone_only",
|
||||
"detail": f"{len(standalone_files)} files for standalone mode only ({', '.join(names)}{more})",
|
||||
})
|
||||
|
||||
return notes
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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