mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-12 14:33:23 -05:00
fix: let the constrained declaration win in manifests too
A destination can be declared by more than one system, bare in one and hash-constrained in another. generate_pack resolves that with _preferred_entries so the constrained sibling claims the destination; generate_manifest never did, and named whatever answered to the name. RetroDECK's bios/d2fdc.zip is the case: declared with an md5 in the arcade system and bare under apple-ii. The pack carried the right archive, 262 bytes reached through the MAME clone map, while the manifest sent install.py to a 256-byte Apple II ROM. Downloading the ZIP and running the installer gave different files. A test now hashes every manifest entry a platform pins and compares it against what the platform declares: 3295 entries, on the platforms whose frontend reads the bytes. It accepts any of several declarations for one destination, the member-composite MD5 Recalbox pins for arcade archives, and Batocera's 29-character prefixes. Reinstating the old manifest entry fails it.
This commit is contained in:
1 parent
3a266be7a5
commit
232ef1a6b8
7 files changed
+359
-156
No files matched your search
+17
-17
@@ -238,7 +238,7 @@
|
||||
}
|
||||
],
|
||||
"total_files": 1828,
|
||||
"total_size": 4314448722,
|
||||
"total_size": 4314432234,
|
||||
"total_omitted": 9,
|
||||
"omitted_files": [
|
||||
{
|
||||
@@ -543,10 +543,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "d2fdc.zip",
|
||||
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4",
|
||||
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom",
|
||||
"sha1": "af56c948598291b284a528f3fce06b961dba55e3",
|
||||
"sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
|
||||
"size": 262,
|
||||
"repo_path": "bios/Arcade/Arcade/a3fdc.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
@@ -1399,10 +1399,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "coco_fdc_v11.zip",
|
||||
"sha1": "45795e083e635c7beab9a40793c628a16f9990e6",
|
||||
"sha256": "61e9bc795b0406b2efe4f3c5a07baa6e84ddc817299f89f26a09aa665423517e",
|
||||
"size": 23115,
|
||||
"repo_path": "bios/Arcade/Arcade/coco_fdc_v11.zip",
|
||||
"sha1": "533870dbc4671a84f0765a96a2b7fea24564afd1",
|
||||
"sha256": "d0159776a6e3ba40f32020c373c1050d9685c61fc3041fd4a3025221dcb2f5ba",
|
||||
"size": 7134,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/coco_fdc_v11.zip.e94f7c27",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
@@ -2473,10 +2473,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "pcw9512.zip",
|
||||
"sha1": "844988dc5a36917b002d0140d3a7692d1407f783",
|
||||
"sha256": "bdc25c862aa0f2a91b2fdac9aa4c91d00d34e95ae2ca50b76ece7a7e35c7cfed",
|
||||
"size": 2625,
|
||||
"repo_path": "bios/Amstrad/PCW/pcw9512.zip",
|
||||
"sha1": "675597949a7b0e1b393840bb4355376509cfa816",
|
||||
"sha256": "dd4bca9f7efd22e80a196444f877c7b60e328916f9e6a1d183238f34fcc6a7d0",
|
||||
"size": 2134,
|
||||
"repo_path": "bios/Arcade/Arcade/pcw9512.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
@@ -2935,10 +2935,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "ym2608.zip",
|
||||
"sha1": "06fc753d015b43ca1787f4cfd9331b1674202e64",
|
||||
"sha256": "187ede0f57d1439092a7ac50ea923533871ab7ee65e29baabc4dcdfa7ad4823f",
|
||||
"size": 7609,
|
||||
"repo_path": "bios/Arcade/Arcade/ym2608.zip",
|
||||
"sha1": "0b6519209766ed883e3fca4c61bf866804c89004",
|
||||
"sha256": "5ac777862d538bcef10e3bec3ee66c218bce6da3896e5c35e524b0ae8e6acfd5",
|
||||
"size": 7587,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/ym2608.zip.0b651920",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
|
||||
+32
-32
@@ -1328,38 +1328,6 @@
|
||||
"repo_path": "bios/Microsoft/MSX/MSX_8020-20bios.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_0.rom",
|
||||
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
|
||||
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_0.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_1.rom",
|
||||
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
|
||||
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_1.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_2.rom",
|
||||
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
|
||||
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_2.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_3.rom",
|
||||
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
|
||||
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_3.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88jisho.rom",
|
||||
"sha1": "deef0cc2a9734ba891a6d6c022aa70ffc66f783e",
|
||||
@@ -1448,6 +1416,38 @@
|
||||
"repo_path": "bios/NEC/PC-98/N88EXT3.ROM",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_0.rom",
|
||||
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
|
||||
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_0.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_1.rom",
|
||||
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
|
||||
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_1.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_2.rom",
|
||||
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
|
||||
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_2.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_3.rom",
|
||||
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
|
||||
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_3.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "np2kai/2608_BD.WAV",
|
||||
"sha1": "0a56c142ef40cec50f3ee56a6e42d0029c9e2818",
|
||||
|
||||
+32
-32
@@ -1567,38 +1567,6 @@
|
||||
"repo_path": "bios/Microsoft/MSX/MSX_8020-20bios.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_0.rom",
|
||||
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
|
||||
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_0.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_1.rom",
|
||||
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
|
||||
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_1.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_2.rom",
|
||||
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
|
||||
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_2.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_3.rom",
|
||||
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
|
||||
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_3.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88jisho.rom",
|
||||
"sha1": "deef0cc2a9734ba891a6d6c022aa70ffc66f783e",
|
||||
@@ -1687,6 +1655,38 @@
|
||||
"repo_path": "bios/NEC/PC-98/N88EXT3.ROM",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_0.rom",
|
||||
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
|
||||
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_0.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_1.rom",
|
||||
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
|
||||
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_1.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_2.rom",
|
||||
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
|
||||
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_2.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_3.rom",
|
||||
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
|
||||
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_3.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "np2kai/2608_BD.WAV",
|
||||
"sha1": "0a56c142ef40cec50f3ee56a6e42d0029c9e2818",
|
||||
|
||||
+41
-41
@@ -238,7 +238,7 @@
|
||||
}
|
||||
],
|
||||
"total_files": 3511,
|
||||
"total_size": 6661468439,
|
||||
"total_size": 6661468445,
|
||||
"total_omitted": 9,
|
||||
"omitted_files": [
|
||||
{
|
||||
@@ -469,14 +469,6 @@
|
||||
"repo_path": "bios/Amstrad/CPC/amsdos.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/a2diskiing.zip",
|
||||
"sha1": "d9bb189730e15d46c27e7246d7bb23137e7d761e",
|
||||
"sha256": "e73eee381d01faecb0fa7f767b0d999f38692f023bcdbe1f9f8d57ad87941f6d",
|
||||
"size": 371,
|
||||
"repo_path": "bios/Apple/Apple II/a2diskiing.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/apple2e.zip",
|
||||
"sha1": "891f44b09ab1a948d3d887f8a30b9c9265849ada",
|
||||
@@ -485,30 +477,6 @@
|
||||
"repo_path": "bios/Apple/Apple II/apple2e.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/d2fdc.zip",
|
||||
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4",
|
||||
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2",
|
||||
"size": 256,
|
||||
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/votrsc01.zip",
|
||||
"sha1": "a57f14469867ce0d0865995a645b69f41a0ea718",
|
||||
"sha256": "14217ca8e25435ea8497b272d8963ebc79ae29cdc21e972817ab09115e2f1b2d",
|
||||
"size": 588,
|
||||
"repo_path": "bios/Apple/Apple II/votrsc01.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/votrsc01a.zip",
|
||||
"sha1": "14a5ff2c8ca68d35a77ebfbc227ed9c8e19d7524",
|
||||
"sha256": "67e3bd12aa4db426a035ec65410fbdeee0d008ba89012285c7e61c0542a5c167",
|
||||
"size": 590,
|
||||
"repo_path": "bios/Arcade/Arcade/votrax.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/apple2gs.zip",
|
||||
"sha1": "799e2fc90d6bfd8cb74e331e04d5afd36f2f21a1",
|
||||
@@ -893,6 +861,14 @@
|
||||
"repo_path": "bios/Arcade/Arcade/a2corvus.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/a2diskiing.zip",
|
||||
"sha1": "d9bb189730e15d46c27e7246d7bb23137e7d761e",
|
||||
"sha256": "e73eee381d01faecb0fa7f767b0d999f38692f023bcdbe1f9f8d57ad87941f6d",
|
||||
"size": 371,
|
||||
"repo_path": "bios/Apple/Apple II/a2diskiing.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/a2grafex.zip",
|
||||
"sha1": "dc594fea200ce87d1bd33a4a1e73f0221b68b9eb",
|
||||
@@ -4669,6 +4645,14 @@
|
||||
"repo_path": "bios/Arcade/Arcade/cubo.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/d2fdc.zip",
|
||||
"sha1": "af56c948598291b284a528f3fce06b961dba55e3",
|
||||
"sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
|
||||
"size": 262,
|
||||
"repo_path": "bios/Arcade/Arcade/a3fdc.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/dc320b.zip",
|
||||
"sha1": "5f091ab41db54f929d4a4c484b34c3a18eb63f38",
|
||||
@@ -12037,6 +12021,14 @@
|
||||
"repo_path": "bios/Arcade/MAME/vision968.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/votrsc01.zip",
|
||||
"sha1": "a57f14469867ce0d0865995a645b69f41a0ea718",
|
||||
"sha256": "14217ca8e25435ea8497b272d8963ebc79ae29cdc21e972817ab09115e2f1b2d",
|
||||
"size": 588,
|
||||
"repo_path": "bios/Apple/Apple II/votrsc01.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/votraxtnt.zip",
|
||||
"sha1": "b32303333a66101a83d008af1a8c1966751ed156",
|
||||
@@ -12069,6 +12061,14 @@
|
||||
"repo_path": "bios/Arcade/Arcade/vp575.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/votrsc01a.zip",
|
||||
"sha1": "14a5ff2c8ca68d35a77ebfbc227ed9c8e19d7524",
|
||||
"sha256": "67e3bd12aa4db426a035ec65410fbdeee0d008ba89012285c7e61c0542a5c167",
|
||||
"size": 590,
|
||||
"repo_path": "bios/Arcade/Arcade/votrax.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/vrc5074.zip",
|
||||
"sha1": "1a1826d7962e6bf6b94fc1bc2b17b8633eaaa0f5",
|
||||
@@ -12461,14 +12461,6 @@
|
||||
"repo_path": "bios/Arcade/FBNeo/spec1282a.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/channelf.zip",
|
||||
"sha1": "1cb23b462b990241013deb4b5e07ce741af28267",
|
||||
"sha256": "c158f7c177c61c7b441eb1061e5148737597e871cbcfa1e2d60ace15e90c8e70",
|
||||
"size": 2705,
|
||||
"repo_path": "bios/Arcade/Arcade/channelf.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/ROM_OS_A_NTSC",
|
||||
"sha1": "f821911f16ed613d72d4b255b3e68ab2d07041ac",
|
||||
@@ -13213,6 +13205,14 @@
|
||||
"repo_path": "bios/Arcade/FBNeo/sl31253.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/channelf.zip",
|
||||
"sha1": "1cb23b462b990241013deb4b5e07ce741af28267",
|
||||
"sha256": "c158f7c177c61c7b441eb1061e5148737597e871cbcfa1e2d60ace15e90c8e70",
|
||||
"size": 2705,
|
||||
"repo_path": "bios/Arcade/Arcade/channelf.zip",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "bios/sl31254.bin",
|
||||
"sha1": "8f70d1b74483ba3a37e86cf16c849d601a8c3d2c",
|
||||
|
||||
+32
-32
@@ -1327,38 +1327,6 @@
|
||||
"repo_path": "bios/Microsoft/MSX/MSX_8020-20bios.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_0.rom",
|
||||
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
|
||||
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_0.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_1.rom",
|
||||
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
|
||||
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_1.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_2.rom",
|
||||
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
|
||||
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_2.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_3.rom",
|
||||
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
|
||||
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_3.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88jisho.rom",
|
||||
"sha1": "deef0cc2a9734ba891a6d6c022aa70ffc66f783e",
|
||||
@@ -1447,6 +1415,38 @@
|
||||
"repo_path": "bios/NEC/PC-98/N88EXT3.ROM",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_0.rom",
|
||||
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
|
||||
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_0.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_1.rom",
|
||||
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
|
||||
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_1.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_2.rom",
|
||||
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
|
||||
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_2.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "quasi88/n88_3.rom",
|
||||
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
|
||||
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
|
||||
"size": 8192,
|
||||
"repo_path": "bios/NEC/PC-98/n88_3.rom",
|
||||
"cores": null
|
||||
},
|
||||
{
|
||||
"dest": "np2kai/2608_BD.WAV",
|
||||
"sha1": "0a56c142ef40cec50f3ee56a6e42d0029c9e2818",
|
||||
|
||||
@@ -635,9 +635,11 @@ def generate_pack(
|
||||
|
||||
preferred_entries: dict[str, int] = {}
|
||||
if source != "truth":
|
||||
# A manifest lists the full set and carries no data directories, so
|
||||
# the preference is resolved with neither narrowing nor a cache.
|
||||
preferred_entries = _preferred_entries(
|
||||
pack_systems, db, bios_dir, base_dest, required_only,
|
||||
zip_contents, data_registry, offline,
|
||||
pack_systems, db, bios_dir, base_dest, False,
|
||||
zip_contents, None, offline,
|
||||
)
|
||||
|
||||
# Region selection is decided once, over both the platform baseline and the
|
||||
@@ -2830,6 +2832,17 @@ def generate_manifest(
|
||||
|
||||
# Phase 1: baseline files
|
||||
if source != "truth":
|
||||
# The same destination can be declared by more than one system, bare
|
||||
# in one and hash-constrained in another. The builder lets the
|
||||
# constrained sibling claim it; without the same rule here the
|
||||
# manifest named whatever answered to the name, so the pack and the
|
||||
# installer disagreed about which file a destination means.
|
||||
# A manifest lists the full set and carries no data directories, so
|
||||
# the preference is resolved with neither narrowing nor a cache.
|
||||
preferred_entries = _preferred_entries(
|
||||
pack_systems, db, bios_dir, base_dest, False,
|
||||
zip_contents, None, offline,
|
||||
)
|
||||
for sys_id, system in sorted(pack_systems.items()):
|
||||
for file_entry in system.get("files", []):
|
||||
dest = sanitize_pack_path(file_entry.get("destination", file_entry["name"]))
|
||||
@@ -2839,6 +2852,10 @@ def generate_manifest(
|
||||
continue
|
||||
full_dest = f"{base_dest}/{dest}" if base_dest else dest
|
||||
|
||||
preferred = preferred_entries.get(full_dest)
|
||||
if preferred is not None and id(file_entry) != preferred:
|
||||
continue
|
||||
|
||||
dedup_key = full_dest
|
||||
if dedup_key in seen_destinations:
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
"""What ships must satisfy the declaration that put it there.
|
||||
|
||||
A file reaches a pack because something asked for it: a platform's own list,
|
||||
or a profile describing what a core loads. Whichever it was, the bytes shipped
|
||||
have to satisfy that declaration. Two files in the collection did not, and
|
||||
both were distributed for months before anyone compared them: a C128 kernal
|
||||
that was two ROMs concatenated, and a Cave Story build the core would not
|
||||
accept.
|
||||
|
||||
This is checked against the published manifests rather than the built packs,
|
||||
because the manifest is what install.py fetches and it names the repository
|
||||
path each entry comes from.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
|
||||
import common # noqa: E402
|
||||
from nativemode import reads_file_contents # noqa: E402
|
||||
|
||||
|
||||
def _declared_digests(entry: dict) -> dict[str, set[str]]:
|
||||
"""Every content value a declaration commits to, lowercased."""
|
||||
out: dict[str, set[str]] = {}
|
||||
for algo in ("md5", "sha1"):
|
||||
raw = entry.get(algo)
|
||||
values = raw if isinstance(raw, list) else [raw]
|
||||
cleaned = set()
|
||||
for value in values:
|
||||
if isinstance(value, str) and value.strip():
|
||||
# Recalbox publishes several acceptable hashes per entry.
|
||||
cleaned.update(v.strip().lower() for v in value.split(",") if v.strip())
|
||||
if cleaned:
|
||||
out[algo] = cleaned
|
||||
return out
|
||||
|
||||
|
||||
class ShippedFilesSatisfyTheirDeclaration(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.install = ROOT / "install"
|
||||
if not self.install.is_dir() or not list(self.install.glob("*.json")):
|
||||
self.skipTest("no published manifests in install/")
|
||||
|
||||
def test_every_manifest_entry_matches_its_platform_declaration(self):
|
||||
"""A pinned hash and the shipped bytes must be the same file.
|
||||
|
||||
Only on platforms whose frontend reads the bytes. Under an existence
|
||||
mode the code never opens the file, so an upstream hash contradicted
|
||||
by the local dump is reported and the file still ships: withholding
|
||||
it would deny the frontend something it would have loaded.
|
||||
|
||||
Only entries the platform pins by hash are checkable here. An entry
|
||||
with no declared hash commits to nothing, and one whose bytes come
|
||||
from a release asset is not in the working tree to hash.
|
||||
"""
|
||||
violations = []
|
||||
checked = 0
|
||||
for manifest_path in sorted(self.install.glob("*.json")):
|
||||
platform = manifest_path.stem
|
||||
try:
|
||||
config = common.load_platform_config(platform, str(ROOT / "platforms"))
|
||||
except (FileNotFoundError, KeyError):
|
||||
continue
|
||||
if not reads_file_contents(config.get("verification_mode")):
|
||||
continue
|
||||
# A destination may be declared by more than one system, bare in
|
||||
# one and hash-constrained in another; the builder lets whichever
|
||||
# resolves claim it, so any of them satisfying the bytes is enough.
|
||||
declared: dict[str, list[dict]] = {}
|
||||
for system in config.get("systems", {}).values():
|
||||
for entry in system.get("files", []):
|
||||
dest = entry.get("destination") or entry.get("name", "")
|
||||
if dest:
|
||||
declared.setdefault(
|
||||
common.sanitize_pack_path(dest), []
|
||||
).append(entry)
|
||||
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
for shipped in manifest.get("files", []):
|
||||
entries = declared.get(
|
||||
common.sanitize_pack_path(shipped.get("dest", "")), []
|
||||
)
|
||||
entries = [e for e in entries if not e.get("zipped_file")]
|
||||
candidates = [(e, _declared_digests(e)) for e in entries]
|
||||
candidates = [(e, w) for e, w in candidates if w]
|
||||
if not candidates:
|
||||
continue
|
||||
repo_path = shipped.get("repo_path", "")
|
||||
if not repo_path:
|
||||
continue
|
||||
blob_path = ROOT / repo_path
|
||||
if not blob_path.is_file():
|
||||
continue
|
||||
blob = blob_path.read_bytes()
|
||||
checked += 1
|
||||
digests = {
|
||||
algo: [hashlib.new(algo, blob).hexdigest()]
|
||||
for algo in ("md5", "sha1")
|
||||
}
|
||||
# Recalbox pins some arcade archives by the MD5 of their
|
||||
# members rather than of the container, which is what
|
||||
# md5_composite computes and what the resolver matches on.
|
||||
# A variant carries its hash after the extension
|
||||
# (cdimono1.zip.e6714b3d), so the name is not the test.
|
||||
try:
|
||||
digests["md5"].append(common.md5_composite(str(blob_path)))
|
||||
except (zipfile.BadZipFile, OSError):
|
||||
pass
|
||||
# Batocera publishes 29-character prefixes, so compare by prefix.
|
||||
satisfied = any(
|
||||
all(
|
||||
any(
|
||||
got.startswith(a)
|
||||
for got in digests[algo]
|
||||
for a in accepted
|
||||
)
|
||||
for algo, accepted in wanted.items()
|
||||
)
|
||||
for _e, wanted in candidates
|
||||
)
|
||||
if not satisfied:
|
||||
first = candidates[0][1]
|
||||
algo = sorted(first)[0]
|
||||
violations.append(
|
||||
f"{platform}: {shipped['dest']} ships {repo_path} "
|
||||
f"with {algo} {digests[algo][0]}, declared "
|
||||
f"{sorted(first[algo])[0]}"
|
||||
)
|
||||
self.assertEqual(
|
||||
violations, [],
|
||||
f"{len(violations)} of {checked} pinned entries ship contradicted "
|
||||
"bytes:\n " + "\n ".join(violations[:12]),
|
||||
)
|
||||
self.assertGreater(checked, 200, "the check reached almost nothing")
|
||||
|
||||
|
||||
class ProfileContradictionsAreKnown(unittest.TestCase):
|
||||
"""A profile's hash and the file answering to it may legitimately differ.
|
||||
|
||||
Emulators ship their own build of a shared data file: four hiscore.dat
|
||||
versions are held, and ti99sim converts its cartridges differently from
|
||||
the release Recalbox pinned. What must not happen is such a file reaching
|
||||
a pack, because there the platform's own declaration governs and is
|
||||
checked above. This test records how many profile declarations no held
|
||||
file satisfies, so the number cannot grow unnoticed.
|
||||
"""
|
||||
|
||||
def test_the_count_of_unsatisfied_profile_hashes_is_known(self):
|
||||
database = ROOT / "database.json"
|
||||
if not database.is_file():
|
||||
self.skipTest("database.json not built")
|
||||
db = common.load_database(str(database))
|
||||
profiles = common.load_emulator_profiles(str(ROOT / "emulators"))
|
||||
unsatisfied = []
|
||||
for name, profile in sorted(profiles.items()):
|
||||
if profile.get("type") in ("alias", "launcher"):
|
||||
continue
|
||||
for entry in profile.get("files", []):
|
||||
if not isinstance(entry, dict) or entry.get("unsourceable"):
|
||||
continue
|
||||
if not _declared_digests(entry):
|
||||
continue
|
||||
path, status = common.resolve_local_file(
|
||||
entry, db, dest_hint=entry.get("path", "")
|
||||
)
|
||||
if path and status == "hash_mismatch":
|
||||
unsatisfied.append(f"{name}/{entry.get('name','')}")
|
||||
self.assertLessEqual(
|
||||
len(unsatisfied), 20,
|
||||
"profile declarations no held file satisfies grew past the known "
|
||||
f"set ({len(unsatisfied)}):\n " + "\n ".join(sorted(unsatisfied)),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user