fix: keep install manifests and release assets true

This commit is contained in:
Abdessamad Derraz committed 2026-09-14 01:17:49 +02:00
1 parent 4f67e3cad3
commit 08bce47668
22 files changed
+877 -262

No files matched your search

+5 -9
View File
@@ -15,10 +15,6 @@ node_modules/
docs/ docs/
# Large files stored as GitHub Release assets (> 50MB) # Large files stored as GitHub Release assets (> 50MB)
bios/Arcade/Arcade/Firmware.19.0.0.zip
bios/Arcade/Arcade/maclc3.zip
bios/Nintendo/Nintendo DS/dsi_nand.bin
bios/Other/Batocera42/dsi_nand.bin
bios/Sony/PlayStation 3/PS3UPDAT.PUP bios/Sony/PlayStation 3/PS3UPDAT.PUP
bios/Sony/PlayStation 3/.variants/PS3UPDAT.PUP.ed8ab192 bios/Sony/PlayStation 3/.variants/PS3UPDAT.PUP.ed8ab192
bios/Sony/PlayStation Vita/PSVUPDAT.PUP bios/Sony/PlayStation Vita/PSVUPDAT.PUP
@@ -66,20 +62,20 @@ tmp/
# FBA2012 Toaplan samples (>100MB) # FBA2012 Toaplan samples (>100MB)
bios/Arcade/fba2012/samples/fireshrk.zip bios/Arcade/fba2012/samples/fireshrk.zip
bios/Arcade/fba2012/samples/ghox.zip bios/Arcade/fba2012/samples/ghox.zip
bios/Arcade/fba2012/samples/samesame.zip
bios/Arcade/fba2012/samples/tekipaki.zip bios/Arcade/fba2012/samples/tekipaki.zip
bios/Arcade/fba2012/samples/vimana.zip bios/Arcade/fba2012/samples/vimana.zip
bios/Arcade/fba2012/samples/fireshrk.zip
bios/Arcade/fba2012/samples/samesame.zip
bios/Arcade/fba2012/samples/vimana.zip
bios/Nintendo/Switch/Firmware_batocera43.zip bios/Nintendo/Switch/Firmware_batocera43.zip
bios/Nintendo/DS/.variants/dsi_nand.bin.8b79a73c bios/Nintendo/DS/.variants/dsi_nand.bin.8b79a73c
# Wii NAND backup (528MB, fetched from large-files release) # Wii NAND backup (528MB, fetched from large-files release)
bios/Nintendo/Wii/nand.bin bios/Nintendo/Wii/nand.bin
# FBNeo audio samples (97MB and 155MB, fetched from large-files release) # FBNeo audio samples (96MB, 148MB and 199MB, fetched from large-files release)
bios/Other/fbneo/fbneo/samples/donpachi.zip
bios/Other/fbneo/fbneo/samples/sfz3mix.zip bios/Other/fbneo/fbneo/samples/sfz3mix.zip
bios/Other/fbneo/fbneo/samples/twotiger.zip bios/Other/fbneo/fbneo/samples/twotiger.zip
bios/Other/starboard/starboard-rootfs.tar.gz bios/Other/starboard/starboard-rootfs.tar.gz
bios/Other/cspect/cspect-next-8gb.zip bios/Other/cspect/cspect-next-8gb.zip
# Output-directory locks (artifact_lock)
.lock
+14 -14
View File
@@ -39,18 +39,18 @@ Every release ships `SHA256SUMS.txt` and a detached signature of it, checkable a
| Platform | Extracted size | Extract to | Download | | Platform | Extracted size | Extract to | Download |
|----------|---------------:|-----------|----------| |----------|---------------:|-----------|----------|
| Batocera | 4.0 GB | `/userdata/bios/` | [Download](../../releases/latest) | | Batocera | 4.2 GB | `/userdata/bios/` | [Download](../../releases/latest) |
| BizHawk | 2.2 GB | `Firmware/` | [Download](../../releases/latest) | | BizHawk | 2.4 GB | `Firmware/` | [Download](../../releases/latest) |
| EmuDeck | 3.2 GB | `~/Emulation/bios/` | [Download](../../releases/latest) | | EmuDeck | 3.2 GB | `~/Emulation/bios/` | [Download](../../releases/latest) |
| Lakka | 5.5 GB | `/storage/system/` | [Download](../../releases/latest) | | Lakka | 5.7 GB | `/storage/system/` | [Download](../../releases/latest) |
| MiSTer FPGA | 24 MB | `/media/fat/games/` | [Download](../../releases/latest) | | MiSTer FPGA | 24 MB | `/media/fat/games/` | [Download](../../releases/latest) |
| ROCKNIX | 5.3 GB | `/storage/roms/bios/` | [Download](../../releases/latest) | | ROCKNIX | 5.5 GB | `/storage/roms/bios/` | [Download](../../releases/latest) |
| Recalbox | 3.6 GB | `/recalbox/share/bios/` | [Download](../../releases/latest) | | Recalbox | 3.8 GB | `/recalbox/share/bios/` | [Download](../../releases/latest) |
| RetroArch | 5.5 GB | `system/` | [Download](../../releases/latest) | | RetroArch | 5.7 GB | `system/` | [Download](../../releases/latest) |
| RetroBat | 4.4 GB | `bios/` | [Download](../../releases/latest) | | RetroBat | 4.6 GB | `bios/` | [Download](../../releases/latest) |
| RetroDECK | 6.2 GB | `~/retrodeck/` | [Download](../../releases/latest) | | RetroDECK | 6.4 GB | `~/retrodeck/` | [Download](../../releases/latest) |
| RetroPie * | 5.5 GB | `~/RetroPie/BIOS/` | [Download](../../releases/latest) | | RetroPie * | 5.7 GB | `~/RetroPie/BIOS/` | [Download](../../releases/latest) |
| RomM | 1.3 GB | `bios/{platform_slug}/` | [Download](../../releases/latest) | | RomM | 1.5 GB | `bios/{platform_slug}/` | [Download](../../releases/latest) |
The RetroDECK pack already contains its own `bios/` folder, so it extracts into `~/retrodeck/` rather than into the BIOS folder. The RetroDECK pack already contains its own `bios/` folder, so it extracts into `~/retrodeck/` rather than into the BIOS folder.
@@ -67,9 +67,9 @@ Each file is checked the way your platform checks it. Most compare a checksum, t
- **12 platforms** supported with platform-specific verification - **12 platforms** supported with platform-specific verification
- **449 emulators** profiled from source (RetroArch cores + standalone) - **449 emulators** profiled from source (RetroArch cores + standalone)
- **470 systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...) - **470 systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **10,330 files**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 4,985 system files, 2,806 arcade ROM sets, 2,539 game and engine data files - **10,331 files**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 4,986 system files, 2,806 arcade ROM sets, 2,539 game and engine data files
- **553 of 4,985 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index - **553 of 4,986 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index
- **12706 MB** total collection size - **12909 MB** total collection size
## Supported systems ## Supported systems
@@ -174,4 +174,4 @@ The scripts and tooling are released under the [MIT License](LICENSE).
The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed. The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed.
The reasoning, and where it is weakest, is in the [FAQ](https://abdess.github.io/retrobios/wiki/faq/#is-this-legal). The reasoning, and where it is weakest, is in the [FAQ](https://abdess.github.io/retrobios/wiki/faq/#is-this-legal).
*Auto-generated on 2026-09-07T02:33:05Z* *Auto-generated on 2026-09-13T22:17:47Z*
-14
View File
@@ -173,20 +173,6 @@
"sha1": "2d7d999e2acb4da55c26d1517934d39e7d0a0c86", "sha1": "2d7d999e2acb4da55c26d1517934d39e7d0a0c86",
"total_copies": 1 "total_copies": 1
}, },
"bbb.zip": {
"clones": [
"aaa_long_name.zip"
],
"sha1": "0ebda75e11e0fdce192562b5bb65a00e3e3bf240",
"total_copies": 1
},
"bbc_m87.zip": {
"clones": [
"bbc_24bbc.zip"
],
"sha1": "0ebda75e11e0fdce192562b5bb65a00e3e3bf240",
"total_copies": 1
},
"beebsid.zip": { "beebsid.zip": {
"clones": [ "clones": [
"bbc_opus3.zip" "bbc_opus3.zip"
+45 -19
View File
@@ -1,8 +1,8 @@
{ {
"schema_version": 1, "schema_version": 1,
"generated_at": "2026-09-07T02:25:37Z", "generated_at": "2026-09-13T22:10:18Z",
"total_files": 10330, "total_files": 10331,
"total_size": 13322705446, "total_size": 13535566159,
"files": { "files": {
"520d3d1b5897800af47f92efd2444a26b7a7dead": { "520d3d1b5897800af47f92efd2444a26b7a7dead": {
"path": "bios/3DO Company/3DO/3do_arcade_saot.bin", "path": "bios/3DO Company/3DO/3do_arcade_saot.bin",
@@ -51128,15 +51128,25 @@
"crc32": "1294ff68", "crc32": "1294ff68",
"adler32": "27661d30" "adler32": "27661d30"
}, },
"227815ce9e55db427d68e075182caffe6ad60ad1": { "d380fb29287eb7fc9ff901a7653ad40785f7deb1": {
"path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"name": "donpachi.zip",
"size": 208549253,
"sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"md5": "aa8c5e742ba27cb30ce7a872edd684bf",
"sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"crc32": "8651ffbb",
"adler32": "2f229899"
},
"e39ee9abb19076ef095f67469ca95ff5e7f600dc": {
"path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"name": "sfz3mix.zip", "name": "sfz3mix.zip",
"size": 96705917, "size": 101017377,
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"md5": "486c88c15387b4bd83514899e68dec3f", "md5": "ce49f09c41c1c4b59078b2350ff1acaf",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"crc32": "c6ab431d", "crc32": "ed686ecb",
"adler32": "ecbdccac" "adler32": "498e0209"
}, },
"b286035624978c94194ef5b83b5d784ec00c3a3c": { "b286035624978c94194ef5b83b5d784ec00c3a3c": {
"path": "bios/Other/fbneo/fbneo/samples/twotiger.zip", "path": "bios/Other/fbneo/fbneo/samples/twotiger.zip",
@@ -113272,7 +113282,8 @@
"0eb6b12f36508b24f285ffd793438ef3": "d124045eccc562ff0423b23a240e27ad740fa0c9", "0eb6b12f36508b24f285ffd793438ef3": "d124045eccc562ff0423b23a240e27ad740fa0c9",
"c78ac640fcf02eb866b79718f5ed15fa": "20fdca7131478e1ee12691bdafd2d5bb74cbd16f", "c78ac640fcf02eb866b79718f5ed15fa": "20fdca7131478e1ee12691bdafd2d5bb74cbd16f",
"5bcfc3374fc9b0c16070f1ac42f3dc63": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "5bcfc3374fc9b0c16070f1ac42f3dc63": "c0c0b5db93a61276a6fe6842049cb53e564b8413",
"486c88c15387b4bd83514899e68dec3f": "227815ce9e55db427d68e075182caffe6ad60ad1", "aa8c5e742ba27cb30ce7a872edd684bf": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"ce49f09c41c1c4b59078b2350ff1acaf": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"733f951177e613819deef4c8f6feb4e7": "b286035624978c94194ef5b83b5d784ec00c3a3c", "733f951177e613819deef4c8f6feb4e7": "b286035624978c94194ef5b83b5d784ec00c3a3c",
"659d579ba9aef5b025d87323044e83f4": "a8b7dce572b74e02e65cb949b2c366c87625157f", "659d579ba9aef5b025d87323044e83f4": "a8b7dce572b74e02e65cb949b2c366c87625157f",
"e612770bc5da7ee1a21d499ce419ce2c": "8251a8e151e31201dcc346d75158ee3996895e74", "e612770bc5da7ee1a21d499ce419ce2c": "8251a8e151e31201dcc346d75158ee3996895e74",
@@ -132449,10 +132460,11 @@
"20fdca7131478e1ee12691bdafd2d5bb74cbd16f" "20fdca7131478e1ee12691bdafd2d5bb74cbd16f"
], ],
"donpachi.zip": [ "donpachi.zip": [
"c0c0b5db93a61276a6fe6842049cb53e564b8413" "c0c0b5db93a61276a6fe6842049cb53e564b8413",
"d380fb29287eb7fc9ff901a7653ad40785f7deb1"
], ],
"sfz3mix.zip": [ "sfz3mix.zip": [
"227815ce9e55db427d68e075182caffe6ad60ad1" "e39ee9abb19076ef095f67469ca95ff5e7f600dc"
], ],
"twotiger.zip": [ "twotiger.zip": [
"b286035624978c94194ef5b83b5d784ec00c3a3c" "b286035624978c94194ef5b83b5d784ec00c3a3c"
@@ -156840,7 +156852,8 @@
"88d0be26": "d124045eccc562ff0423b23a240e27ad740fa0c9", "88d0be26": "d124045eccc562ff0423b23a240e27ad740fa0c9",
"3c79ef05": "20fdca7131478e1ee12691bdafd2d5bb74cbd16f", "3c79ef05": "20fdca7131478e1ee12691bdafd2d5bb74cbd16f",
"1294ff68": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "1294ff68": "c0c0b5db93a61276a6fe6842049cb53e564b8413",
"c6ab431d": "227815ce9e55db427d68e075182caffe6ad60ad1", "8651ffbb": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"ed686ecb": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"b5d5b941": "b286035624978c94194ef5b83b5d784ec00c3a3c", "b5d5b941": "b286035624978c94194ef5b83b5d784ec00c3a3c",
"353fdbcd": "a8b7dce572b74e02e65cb949b2c366c87625157f", "353fdbcd": "a8b7dce572b74e02e65cb949b2c366c87625157f",
"31669117": "8251a8e151e31201dcc346d75158ee3996895e74", "31669117": "8251a8e151e31201dcc346d75158ee3996895e74",
@@ -167172,7 +167185,8 @@
"f76b62e73e7410ab63813b9db36839be12213e4adfd3ee513eb382b7bc3a4a39": "d124045eccc562ff0423b23a240e27ad740fa0c9", "f76b62e73e7410ab63813b9db36839be12213e4adfd3ee513eb382b7bc3a4a39": "d124045eccc562ff0423b23a240e27ad740fa0c9",
"6597f75e97e5337198736b42a4d1ea66d4fdf01d685772482811a55e1eddf8f1": "20fdca7131478e1ee12691bdafd2d5bb74cbd16f", "6597f75e97e5337198736b42a4d1ea66d4fdf01d685772482811a55e1eddf8f1": "20fdca7131478e1ee12691bdafd2d5bb74cbd16f",
"a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c": "c0c0b5db93a61276a6fe6842049cb53e564b8413",
"6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe": "227815ce9e55db427d68e075182caffe6ad60ad1", "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"fe5408e3f513c44a64f573e7a8b68885bce9b263bc9c67bfef13fef13f3e976e": "b286035624978c94194ef5b83b5d784ec00c3a3c", "fe5408e3f513c44a64f573e7a8b68885bce9b263bc9c67bfef13fef13f3e976e": "b286035624978c94194ef5b83b5d784ec00c3a3c",
"569541852ed3ff8fdacd04b1c6a57fd491a361ab6c41339bbddfd916bced241e": "a8b7dce572b74e02e65cb949b2c366c87625157f", "569541852ed3ff8fdacd04b1c6a57fd491a361ab6c41339bbddfd916bced241e": "a8b7dce572b74e02e65cb949b2c366c87625157f",
"ea2e9f8eccb2d590a0d004e0eaffa95ca4eea36f15466b5aa1a9e47393bc004f": "8251a8e151e31201dcc346d75158ee3996895e74", "ea2e9f8eccb2d590a0d004e0eaffa95ca4eea36f15466b5aa1a9e47393bc004f": "8251a8e151e31201dcc346d75158ee3996895e74",
@@ -211209,17 +211223,29 @@
"fbneo/donpachi.zip": [ "fbneo/donpachi.zip": [
"c0c0b5db93a61276a6fe6842049cb53e564b8413" "c0c0b5db93a61276a6fe6842049cb53e564b8413"
], ],
"Other/fbneo/fbneo/samples/donpachi.zip": [
"d380fb29287eb7fc9ff901a7653ad40785f7deb1"
],
"fbneo/fbneo/samples/donpachi.zip": [
"d380fb29287eb7fc9ff901a7653ad40785f7deb1"
],
"fbneo/samples/donpachi.zip": [
"d380fb29287eb7fc9ff901a7653ad40785f7deb1"
],
"samples/donpachi.zip": [
"d380fb29287eb7fc9ff901a7653ad40785f7deb1"
],
"Other/fbneo/fbneo/samples/sfz3mix.zip": [ "Other/fbneo/fbneo/samples/sfz3mix.zip": [
"227815ce9e55db427d68e075182caffe6ad60ad1" "e39ee9abb19076ef095f67469ca95ff5e7f600dc"
], ],
"fbneo/fbneo/samples/sfz3mix.zip": [ "fbneo/fbneo/samples/sfz3mix.zip": [
"227815ce9e55db427d68e075182caffe6ad60ad1" "e39ee9abb19076ef095f67469ca95ff5e7f600dc"
], ],
"fbneo/samples/sfz3mix.zip": [ "fbneo/samples/sfz3mix.zip": [
"227815ce9e55db427d68e075182caffe6ad60ad1" "e39ee9abb19076ef095f67469ca95ff5e7f600dc"
], ],
"samples/sfz3mix.zip": [ "samples/sfz3mix.zip": [
"227815ce9e55db427d68e075182caffe6ad60ad1" "e39ee9abb19076ef095f67469ca95ff5e7f600dc"
], ],
"Other/fbneo/fbneo/samples/twotiger.zip": [ "Other/fbneo/fbneo/samples/twotiger.zip": [
"b286035624978c94194ef5b83b5d784ec00c3a3c" "b286035624978c94194ef5b83b5d784ec00c3a3c"
+18 -16
View File
@@ -5,7 +5,7 @@
"platform": "batocera", "platform": "batocera",
"display_name": "Batocera", "display_name": "Batocera",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -238,7 +238,7 @@
} }
], ],
"total_files": 1832, "total_files": 1832,
"total_size": 4314998020, "total_size": 4522217423,
"total_omitted": 9, "total_omitted": 9,
"omitted_files": [ "omitted_files": [
{ {
@@ -2271,10 +2271,10 @@
}, },
{ {
"dest": "firmware.bin", "dest": "firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -2311,10 +2311,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -8041,19 +8041,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
+18 -16
View File
@@ -5,7 +5,7 @@
"platform": "bizhawk", "platform": "bizhawk",
"display_name": "BizHawk", "display_name": "BizHawk",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "Firmware", "base_destination": "Firmware",
"detect": [ "detect": [
{ {
@@ -21,7 +21,7 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 539, "total_files": 539,
"total_size": 2328691103, "total_size": 2535910506,
"total_omitted": 1, "total_omitted": 1,
"omitted_files": [ "omitted_files": [
{ {
@@ -1464,19 +1464,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -3348,10 +3350,10 @@
}, },
{ {
"dest": "firmware.bin", "dest": "firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": [ "cores": [
"melonds" "melonds"
] ]
@@ -3378,10 +3380,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": [ "cores": [
"melonds" "melonds"
] ]
+7 -7
View File
@@ -5,7 +5,7 @@
"platform": "emudeck", "platform": "emudeck",
"display_name": "EmuDeck", "display_name": "EmuDeck",
"version": "1.0", "version": "1.0",
"generated": "2026-09-03T20:45:52Z", "generated": "2026-09-13T17:22:08Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -313,10 +313,10 @@
}, },
{ {
"dest": "firmware.bin", "dest": "firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -3991,10 +3991,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": [ "cores": [
"melonds" "melonds"
] ]
+23 -23
View File
@@ -5,7 +5,7 @@
"platform": "lakka", "platform": "lakka",
"display_name": "Lakka", "display_name": "Lakka",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "system", "base_destination": "system",
"detect": [ "detect": [
{ {
@@ -17,7 +17,7 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 1871, "total_files": 1871,
"total_size": 5865446694, "total_size": 6072666103,
"total_omitted": 1, "total_omitted": 1,
"omitted_files": [ "omitted_files": [
{ {
@@ -1626,10 +1626,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -6912,19 +6912,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -9368,10 +9370,10 @@
}, },
{ {
"dest": "d2fdc.zip", "dest": "d2fdc.zip",
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4", "sha1": "af56c948598291b284a528f3fce06b961dba55e3",
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2", "sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
"size": 256, "size": 262,
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom", "repo_path": "bios/Arcade/Arcade/a3fdc.zip",
"cores": [ "cores": [
"mamemess" "mamemess"
] ]
@@ -13380,9 +13382,7 @@
"repo_path": "bios/Other/sdlpal/sdlpal/5.avi", "repo_path": "bios/Other/sdlpal/sdlpal/5.avi",
"cores": [ "cores": [
"sdlpal" "sdlpal"
], ]
"storage": "release",
"release_asset": "5.avi"
}, },
{ {
"dest": "sdlpal/6.avi", "dest": "sdlpal/6.avi",
@@ -15966,10 +15966,10 @@
}, },
{ {
"dest": "SkyEmu/firmware.bin", "dest": "SkyEmu/firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": [ "cores": [
"skyemu" "skyemu"
] ]
+19 -17
View File
@@ -5,7 +5,7 @@
"platform": "recalbox", "platform": "recalbox",
"display_name": "Recalbox", "display_name": "Recalbox",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -238,7 +238,7 @@
} }
], ],
"total_files": 1328, "total_files": 1328,
"total_size": 3900250204, "total_size": 4107469613,
"total_omitted": 4, "total_omitted": 4,
"omitted_files": [ "omitted_files": [
{ {
@@ -5927,19 +5927,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -8191,10 +8193,10 @@
}, },
{ {
"dest": "d2fdc.zip", "dest": "d2fdc.zip",
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4", "sha1": "af56c948598291b284a528f3fce06b961dba55e3",
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2", "sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
"size": 256, "size": 262,
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom", "repo_path": "bios/Arcade/Arcade/a3fdc.zip",
"cores": [ "cores": [
"mamemess" "mamemess"
] ]
@@ -8681,10 +8683,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": [ "cores": [
"melonds" "melonds"
] ]
+23 -23
View File
@@ -5,7 +5,7 @@
"platform": "retroarch", "platform": "retroarch",
"display_name": "RetroArch", "display_name": "RetroArch",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "system", "base_destination": "system",
"detect": [ "detect": [
{ {
@@ -256,7 +256,7 @@
} }
], ],
"total_files": 1871, "total_files": 1871,
"total_size": 5865446694, "total_size": 6072666103,
"total_omitted": 1, "total_omitted": 1,
"omitted_files": [ "omitted_files": [
{ {
@@ -1865,10 +1865,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -7151,19 +7151,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -9607,10 +9609,10 @@
}, },
{ {
"dest": "d2fdc.zip", "dest": "d2fdc.zip",
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4", "sha1": "af56c948598291b284a528f3fce06b961dba55e3",
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2", "sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
"size": 256, "size": 262,
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom", "repo_path": "bios/Arcade/Arcade/a3fdc.zip",
"cores": [ "cores": [
"mamemess" "mamemess"
] ]
@@ -13619,9 +13621,7 @@
"repo_path": "bios/Other/sdlpal/sdlpal/5.avi", "repo_path": "bios/Other/sdlpal/sdlpal/5.avi",
"cores": [ "cores": [
"sdlpal" "sdlpal"
], ]
"storage": "release",
"release_asset": "5.avi"
}, },
{ {
"dest": "sdlpal/6.avi", "dest": "sdlpal/6.avi",
@@ -16205,10 +16205,10 @@
}, },
{ {
"dest": "SkyEmu/firmware.bin", "dest": "SkyEmu/firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": [ "cores": [
"skyemu" "skyemu"
] ]
+22 -20
View File
@@ -5,7 +5,7 @@
"platform": "retrobat", "platform": "retrobat",
"display_name": "RetroBat", "display_name": "RetroBat",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -237,7 +237,7 @@
} }
], ],
"total_files": 1464, "total_files": 1464,
"total_size": 4733583386, "total_size": 4940802795,
"total_omitted": 3, "total_omitted": 3,
"omitted_files": [ "omitted_files": [
{ {
@@ -514,10 +514,10 @@
}, },
{ {
"dest": "d2fdc.zip", "dest": "d2fdc.zip",
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4", "sha1": "af56c948598291b284a528f3fce06b961dba55e3",
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2", "sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
"size": 256, "size": 262,
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom", "repo_path": "bios/Arcade/Arcade/a3fdc.zip",
"cores": null "cores": null
}, },
{ {
@@ -2282,10 +2282,10 @@
}, },
{ {
"dest": "firmware.bin", "dest": "firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -2322,10 +2322,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -5430,19 +5430,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
+19 -19
View File
@@ -5,7 +5,7 @@
"platform": "retrodeck", "platform": "retrodeck",
"display_name": "RetroDECK", "display_name": "RetroDECK",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "", "base_destination": "",
"detect": [ "detect": [
{ {
@@ -238,7 +238,7 @@
} }
], ],
"total_files": 3515, "total_files": 3515,
"total_size": 6656314549, "total_size": 6863533952,
"total_omitted": 4, "total_omitted": 4,
"omitted_files": [ "omitted_files": [
{ {
@@ -19433,19 +19433,21 @@
}, },
{ {
"dest": "bios/fbneo/samples/donpachi.zip", "dest": "bios/fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "bios/fbneo/samples/sfz3mix.zip", "dest": "bios/fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -21549,10 +21551,10 @@
}, },
{ {
"dest": "bios/dsi_firmware.bin", "dest": "bios/dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": [ "cores": [
"melonds" "melonds"
] ]
@@ -25815,9 +25817,7 @@
"repo_path": "bios/Other/sdlpal/sdlpal/5.avi", "repo_path": "bios/Other/sdlpal/sdlpal/5.avi",
"cores": [ "cores": [
"sdlpal" "sdlpal"
], ]
"storage": "release",
"release_asset": "5.avi"
}, },
{ {
"dest": "bios/sdlpal/6.avi", "dest": "bios/sdlpal/6.avi",
@@ -29865,10 +29865,10 @@
}, },
{ {
"dest": "bios/SkyEmu/firmware.bin", "dest": "bios/SkyEmu/firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": [ "cores": [
"skyemu" "skyemu"
] ]
+23 -23
View File
@@ -5,7 +5,7 @@
"platform": "retropie", "platform": "retropie",
"display_name": "RetroPie", "display_name": "RetroPie",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "system", "base_destination": "system",
"detect": [ "detect": [
{ {
@@ -16,7 +16,7 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 1871, "total_files": 1871,
"total_size": 5865446694, "total_size": 6072666103,
"total_omitted": 1, "total_omitted": 1,
"omitted_files": [ "omitted_files": [
{ {
@@ -1625,10 +1625,10 @@
}, },
{ {
"dest": "dsi_firmware.bin", "dest": "dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": null "cores": null
}, },
{ {
@@ -6911,19 +6911,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -9367,10 +9369,10 @@
}, },
{ {
"dest": "d2fdc.zip", "dest": "d2fdc.zip",
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4", "sha1": "af56c948598291b284a528f3fce06b961dba55e3",
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2", "sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
"size": 256, "size": 262,
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom", "repo_path": "bios/Arcade/Arcade/a3fdc.zip",
"cores": [ "cores": [
"mamemess" "mamemess"
] ]
@@ -13379,9 +13381,7 @@
"repo_path": "bios/Other/sdlpal/sdlpal/5.avi", "repo_path": "bios/Other/sdlpal/sdlpal/5.avi",
"cores": [ "cores": [
"sdlpal" "sdlpal"
], ]
"storage": "release",
"release_asset": "5.avi"
}, },
{ {
"dest": "sdlpal/6.avi", "dest": "sdlpal/6.avi",
@@ -15965,10 +15965,10 @@
}, },
{ {
"dest": "SkyEmu/firmware.bin", "dest": "SkyEmu/firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": [ "cores": [
"skyemu" "skyemu"
] ]
+20 -20
View File
@@ -5,7 +5,7 @@
"platform": "rocknix", "platform": "rocknix",
"display_name": "ROCKNIX", "display_name": "ROCKNIX",
"version": "1.0", "version": "1.0",
"generated": "2026-09-05T09:26:20Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -18,7 +18,7 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 1738, "total_files": 1738,
"total_size": 5743476850, "total_size": 5950696259,
"total_omitted": 1, "total_omitted": 1,
"omitted_files": [ "omitted_files": [
{ {
@@ -4015,19 +4015,21 @@
}, },
{ {
"dest": "fbneo/samples/donpachi.zip", "dest": "fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "fbneo/samples/sfz3mix.zip", "dest": "fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -7211,10 +7213,10 @@
}, },
{ {
"dest": "d2fdc.zip", "dest": "d2fdc.zip",
"sha1": "bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4", "sha1": "af56c948598291b284a528f3fce06b961dba55e3",
"sha256": "e5e30615040567c1e7a2d21599681f8dac820edbdcda177b816a64d74b3a12f2", "sha256": "69ee0f8f0862e1a623bcbe9870f9531f7976312f292a20d1aff7ed61b6085a69",
"size": 256, "size": 262,
"repo_path": "bios/Apple/Apple II/DiskII/state-machine-16.rom", "repo_path": "bios/Arcade/Arcade/a3fdc.zip",
"cores": [ "cores": [
"mamemess" "mamemess"
] ]
@@ -12543,9 +12545,7 @@
"repo_path": "bios/Other/sdlpal/sdlpal/5.avi", "repo_path": "bios/Other/sdlpal/sdlpal/5.avi",
"cores": [ "cores": [
"sdlpal" "sdlpal"
], ]
"storage": "release",
"release_asset": "5.avi"
}, },
{ {
"dest": "sdlpal/6.avi", "dest": "sdlpal/6.avi",
@@ -15389,10 +15389,10 @@
}, },
{ {
"dest": "SkyEmu/firmware.bin", "dest": "SkyEmu/firmware.bin",
"sha1": "5b2acbd499b39fb1af1b36ef7de5eb047d83401c", "sha1": "cfe072921ee3fb93f688743f8beef89043c3e9ad",
"sha256": "7ea4f06ca110f8b7ff7c946f6e4e228d94977dadd4bb51570d14c68488edb32a", "sha256": "57a1d6b43c1399d219ea18644240c4d4c6787c829f0a67ded216472867857cd0",
"size": 262144, "size": 262144,
"repo_path": "bios/Arcade/Arcade/firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/firmware.bin",
"cores": [ "cores": [
"skyemu" "skyemu"
] ]
+15 -13
View File
@@ -5,7 +5,7 @@
"platform": "romm", "platform": "romm",
"display_name": "RomM", "display_name": "RomM",
"version": "1.0", "version": "1.0",
"generated": "2026-09-03T20:45:52Z", "generated": "2026-09-13T22:10:18Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -16,7 +16,7 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 615, "total_files": 615,
"total_size": 1366505831, "total_size": 1573725234,
"total_omitted": 0, "total_omitted": 0,
"omitted_files": [], "omitted_files": [],
"files": [ "files": [
@@ -3332,19 +3332,21 @@
}, },
{ {
"dest": "colecovision/fbneo/samples/donpachi.zip", "dest": "colecovision/fbneo/samples/donpachi.zip",
"sha1": "c0c0b5db93a61276a6fe6842049cb53e564b8413", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1",
"sha256": "a686ebf893d511b5b7667176cd6844593e914595a8bb9ab656b2c66969013b5c", "sha256": "78a237f1e2774eb396e2f14999f1270597e0767a392729e7a4539455888b6fe0",
"size": 5641310, "size": 208549253,
"repo_path": "bios/Other/fbneo/donpachi.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/donpachi.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
] ],
"storage": "release",
"release_asset": "donpachi.zip"
}, },
{ {
"dest": "colecovision/fbneo/samples/sfz3mix.zip", "dest": "colecovision/fbneo/samples/sfz3mix.zip",
"sha1": "227815ce9e55db427d68e075182caffe6ad60ad1", "sha1": "e39ee9abb19076ef095f67469ca95ff5e7f600dc",
"sha256": "6a720035646b3488a047ab163a6975b7be82fe75c5eec88155c8503c93a631fe", "sha256": "2c2e67ab762102f50df641280721164ddff0316e71081d5fa62f2a67365dea66",
"size": 96705917, "size": 101017377,
"repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip", "repo_path": "bios/Other/fbneo/fbneo/samples/sfz3mix.zip",
"cores": [ "cores": [
"fbneo" "fbneo"
@@ -3808,10 +3810,10 @@
}, },
{ {
"dest": "nds/dsi_firmware.bin", "dest": "nds/dsi_firmware.bin",
"sha1": "93c0598b08192a1f630e5edb2cd9f60d1e3ea7b3", "sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1f89935da2888cd3e401af2774e06a6b587ca059e85f922826494f317d31c6ae", "sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072, "size": 131072,
"repo_path": "bios/Arcade/Arcade/dsi_firmware.bin", "repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"cores": [ "cores": [
"melonds" "melonds"
] ]
+336
View File
@@ -0,0 +1,336 @@
#!/usr/bin/env python3
"""Compare the large-files release with the files the collection keeps out of git.
Every gitignored file under bios/ is served to the installer as an asset of
the large-files release, and install.py refuses a download whose
Content-Length differs from the manifest size. The manifest size is that of
the local copy, so an asset uploaded before the local file was rebuilt fails
every install of it until it is uploaded again.
Usage:
python scripts/check_release_assets.py
python scripts/check_release_assets.py --json
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import subprocess
import sys
sys.path.insert(0, os.path.dirname(__file__))
from common import load_database
from largefiles import LARGE_FILES_RELEASE, LARGE_FILES_REPO
Finding = tuple[str, str, int, int | None]
def expected_assets(db: dict, gitignore_text: str) -> dict[str, int]:
"""Map each gitignored database path to the size the manifests carry."""
ignored = {
line.strip()
for line in gitignore_text.splitlines()
if line.strip().startswith("bios/")
}
return {
entry["path"]: entry["size"]
for entry in db.get("files", {}).values()
if entry.get("path") in ignored
}
def _asset_names(path: str) -> list[str]:
"""Names the release may publish a file under.
GitHub rewrites spaces to dots in asset names, so a file whose name
carries spaces is published under the dotted form.
"""
name = os.path.basename(path)
return [name, name.replace(" ", ".")] if " " in name else [name]
def compare(
expected: dict[str, int], assets: dict[str, int], notes_current: bool = True
) -> list[Finding]:
"""Files the release lacks or serves at another size, sorted by kind.
A release description that no longer matches what render_notes() would
write is a finding of its own: the page is what a reader checks a
download against.
"""
findings: list[Finding] = []
if not notes_current:
findings.append(("notes", "release description", 0, None))
for path, size in sorted(expected.items()):
published = next(
(assets[name] for name in _asset_names(path) if name in assets), None
)
if published is None:
findings.append(("missing", path, size, None))
elif published != size:
findings.append(("size", path, size, published))
return sorted(findings)
ASSET_URL = f"https://github.com/{LARGE_FILES_REPO}/releases/download/{LARGE_FILES_RELEASE}/"
NOT_INDEXED = "Not indexed"
SECTIONS = (
"Console firmware",
"Arcade",
"Computer",
"Game engine data",
"Virtual machine firmware",
"Other",
NOT_INDEXED,
)
_ROW = re.compile(
r"^\| \[(?P<name>[^\]]+)\]\([^)]*\) \| (?P<desc>[^|]*?) \| [^|]* \|"
r"(?: `(?P<sha1>[0-9a-f]{40})` \|)?$",
re.M,
)
_HEADING = re.compile(r"^## (.+)$", re.M)
def parse_notes(body: str) -> dict[str, tuple[str, str, str]]:
"""Name -> (section, description, sha1) for every row of a release page."""
rows: dict[str, tuple[str, str, str]] = {}
section = ""
for line in body.splitlines():
heading = _HEADING.match(line)
if heading:
section = heading.group(1).strip()
continue
row = _ROW.match(line)
if row:
rows[row.group("name")] = (
section, row.group("desc").strip(), row.group("sha1") or ""
)
return rows
def _section_for(path: str) -> str:
parts = path.split("/")
top = parts[1] if len(parts) > 1 else ""
if top in ("Nintendo", "Sony", "Sega", "Microsoft", "NEC", "SNK"):
return "Console firmware"
if top == "Arcade" or "/samples/" in path:
return "Arcade"
if top == "QEMU":
return "Virtual machine firmware"
if top in ("RPG Maker", "ScummVM"):
return "Game engine data"
if top in ("Apple", "Commodore", "Atari", "Sinclair", "Amstrad"):
return "Computer"
return "Other"
def _mib(size: int) -> str:
return f"{round(size / 1048576)} MB"
def render_notes(
db: dict,
gitignore_text: str,
assets: dict[str, int],
previous: str,
descriptions: dict[str, str],
bundles: dict[str, str],
cache_sha1: dict[str, str],
) -> str:
"""The release page, from what the release actually serves.
Every asset gets one row. A file the database indexes carries its
database SHA1; a data-directory bundle carries the hash of its cached
copy when one exists; anything else is listed apart with its size only,
so the page never vouches for bytes nobody indexed. Descriptions and
sections already written by hand are kept by name.
"""
known = parse_notes(previous)
ignored = {
line.strip()
for line in gitignore_text.splitlines()
if line.strip().startswith("bios/")
}
indexed: dict[str, tuple[str, str]] = {}
for sha1, entry in db.get("files", {}).items():
path = entry.get("path", "")
if path in ignored:
name = os.path.basename(path)
for candidate in (name, name.replace(" ", ".")):
indexed[candidate] = (sha1, path)
rows: dict[str, list[tuple[int, str]]] = {section: [] for section in SECTIONS}
for name, size in assets.items():
section, description, previous_sha1 = known.get(name, ("", "", ""))
if name in indexed:
sha1, path = indexed[name]
section = section if section in SECTIONS[:-1] else _section_for(path)
stem = name.split(".")[0] + "." + name.split(".")[1] if name.count(".") > 1 else name
description = description or descriptions.get(name) or descriptions.get(stem) or path
elif name in bundles:
sha1 = cache_sha1.get(name, previous_sha1)
section = section if section in SECTIONS[:-1] else "Game engine data"
description = description or bundles[name]
else:
sha1 = ""
section = NOT_INDEXED
description = description or name
link = f"[{name}]({ASSET_URL}{name})"
if section == NOT_INDEXED:
line = f"| {link} | {description} | {_mib(size)} |"
else:
line = f"| {link} | {description} | {_mib(size)} | `{sha1}` |"
rows[section].append((-size, line))
out = [
"Files too large for the git repository (GitHub 100MB limit), plus game engine data packs.",
"Downloaded automatically by `generate_pack.py` when not present locally.",
"Manual: `gh release download large-files`",
]
for section in SECTIONS:
if not rows[section]:
continue
out += ["", f"## {section}", ""]
if section == NOT_INDEXED:
out += [
"Assets the collection does not index: no database entry names them, "
"so no hash is vouched for here.",
"",
"| File | Description | Size |",
"|------|-------------|-----:|",
]
else:
out += ["| File | Description | Size | SHA1 |", "|------|-------------|-----:|------|"]
out += [line for _, line in sorted(rows[section])]
total = sum(assets.values())
out += ["", f"{len(assets)} files, {total / 1073741824:.1f} GB total. Verify a download with `sha1sum <file>`.", ""]
return "\n".join(out)
def profile_descriptions(emulators_dir: str = "emulators") -> dict[str, str]:
"""Name -> description, from the first profile that declares the file."""
from common import load_emulator_profiles
found: dict[str, str] = {}
for profile in load_emulator_profiles(emulators_dir).values():
for entry in profile.get("files", []):
name = str(entry.get("name") or "")
if name and entry.get("description"):
found.setdefault(name, str(entry["description"]))
return found
def registry_bundles(registry_path: str = "platforms/_data_dirs.yml") -> dict[str, str]:
"""Asset name -> description for data directories served from the release."""
from common import yaml_load
with open(registry_path, encoding="utf-8") as handle:
registry = yaml_load(handle) or {}
bundles: dict[str, str] = {}
for entry in (registry.get("data_directories") or registry).values():
if not isinstance(entry, dict):
continue
url = str(entry.get("source_url") or "")
if url.startswith(ASSET_URL):
bundles[url[len(ASSET_URL):]] = str(entry.get("description") or "")
return bundles
def cache_hashes(names: set[str], cache_dir: str = ".cache/large") -> dict[str, str]:
"""SHA1 of the cached copy of each named asset that the cache holds."""
hashes: dict[str, str] = {}
for name in names:
path = os.path.join(cache_dir, name)
if os.path.isfile(path):
digest = hashlib.sha1()
with open(path, "rb") as handle:
for chunk in iter(lambda: handle.read(1 << 20), b""):
digest.update(chunk)
hashes[name] = digest.hexdigest()
return hashes
def fetch_release() -> tuple[dict[str, int], str]:
"""Name and size of every asset of the large-files release, and its page."""
result = subprocess.run(
[
"gh", "release", "view", LARGE_FILES_RELEASE,
"-R", LARGE_FILES_REPO, "--json", "assets,body",
],
capture_output=True, text=True, check=True,
)
data = json.loads(result.stdout)
return {a["name"]: a["size"] for a in data["assets"]}, data.get("body", "")
def fetch_assets() -> dict[str, int]:
"""Name and size of every asset of the large-files release."""
result = subprocess.run(
[
"gh", "release", "view", LARGE_FILES_RELEASE,
"-R", LARGE_FILES_REPO, "--json", "assets",
],
capture_output=True, text=True, check=True,
)
return {a["name"]: a["size"] for a in json.loads(result.stdout)["assets"]}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--db", default="database.json")
parser.add_argument("--gitignore", default=".gitignore")
parser.add_argument("--json", action="store_true")
parser.add_argument(
"--notes", metavar="FILE",
help="write the release page rendered from the collection to FILE",
)
args = parser.parse_args()
with open(args.gitignore, encoding="utf-8") as handle:
gitignore_text = handle.read()
db = load_database(args.db)
expected = expected_assets(db, gitignore_text)
try:
assets, body = fetch_release()
except (subprocess.CalledProcessError, FileNotFoundError, KeyError) as exc:
print(f"ERROR: cannot list release assets: {exc}", file=sys.stderr)
return 2
bundles = registry_bundles()
rendered = render_notes(
db, gitignore_text, assets, body, profile_descriptions(), bundles,
cache_hashes(set(bundles)),
)
if args.notes:
with open(args.notes, "w", encoding="utf-8") as handle:
handle.write(rendered)
findings = compare(expected, assets, notes_current=rendered.strip() == body.strip())
if args.json:
print(json.dumps(
[
{"kind": kind, "path": path, "expected": size, "published": published}
for kind, path, size, published in findings
],
indent=2,
))
else:
print(f"{len(expected)} gitignored files, {len(assets)} release assets")
for kind, path, size, published in findings:
if kind == "missing":
print(f" MISSING {path} ({size} bytes)")
elif kind == "size":
print(f" SIZE {path}: local {size} != release {published}")
else:
print(" NOTES release description differs from the rendered page"
" (--notes FILE, then gh release edit large-files --notes-file FILE)")
if not findings:
print(" release assets and description match the collection")
return 1 if findings else 0
if __name__ == "__main__":
sys.exit(main())
+6 -1
View File
@@ -230,7 +230,12 @@ def deduplicate(bios_dir: str, dry_run: bool = False) -> dict:
# Write MAME clone mapping # Write MAME clone mapping
if mame_clones: if mame_clones:
clone_path = "_mame_clones.json" # Beside the scanned tree, where get_mame_clone_map() reads it for
# the real layout. A path relative to the working directory let a
# scan of a fixture tree rewrite the repository's own map.
clone_path = os.path.join(
os.path.dirname(os.path.abspath(bios_dir)), "_mame_clones.json"
)
# A group is only visible while both copies are on disk, and this run # A group is only visible while both copies are on disk, and this run
# has just deleted the clone. Writing only what was seen this time # has just deleted the clone. Writing only what was seen this time
# therefore erases every mapping an earlier run recorded, and the # therefore erases every mapping an earlier run recorded, and the
+11 -8
View File
@@ -2707,13 +2707,16 @@ def _load_gitignore_entries(repo_root: str) -> set[str]:
return entries return entries
def _is_large_file(local_path: str, repo_root: str) -> bool: def _is_release_asset(local_path: str, repo_root: str) -> bool:
"""Check if a file is a large file (>50MB or in .gitignore).""" """Whether the installer fetches this file from the large-files release.
if local_path and os.path.exists(local_path):
if os.path.getsize(local_path) > 50_000_000: The repository serves every committed file from its raw URL; only the
return True files kept out of git live as release assets, and .gitignore is the
ledger of those. Size is not the criterion: a committed file over 50 MB
is still served by the repository, and announcing it as a release asset
sends the installer to an asset nobody uploaded.
"""
gitignore = _load_gitignore_entries(repo_root) gitignore = _load_gitignore_entries(repo_root)
# Check if the path relative to repo root is in .gitignore
try: try:
rel = os.path.relpath(local_path, repo_root) rel = os.path.relpath(local_path, repo_root)
except ValueError: except ValueError:
@@ -2826,7 +2829,7 @@ def _manifest_core_entries(
"cores": [source_emu] if source_emu else [], "cores": [source_emu] if source_emu else [],
} }
if _is_large_file(local_path or "", repo_root): if _is_release_asset(local_path or "", repo_root):
entry["storage"] = "release" entry["storage"] = "release"
entry["release_asset"] = ( entry["release_asset"] = (
os.path.basename(local_path) if local_path else fe["name"] os.path.basename(local_path) if local_path else fe["name"]
@@ -3021,7 +3024,7 @@ def generate_manifest(
sha256 = hashes["sha256"] sha256 = hashes["sha256"]
repo_path = _get_repo_path(sha1, db) if sha1 else "" repo_path = _get_repo_path(sha1, db) if sha1 else ""
is_release_asset = _is_large_file(local_path or "", repo_root) is_release_asset = _is_release_asset(local_path or "", repo_root)
# An entry needs somewhere to be fetched from. Resolution can # An entry needs somewhere to be fetched from. Resolution can
# land on a file the database does not index -- a data # land on a file the database does not index -- a data
+15
View File
@@ -6,6 +6,7 @@ Steps:
1b. provenance_report.py (dump-catalog coverage from provenance/) 1b. provenance_report.py (dump-catalog coverage from provenance/)
1c. romset_recipes.py (archive identification, reconstruction targets) 1c. romset_recipes.py (archive identification, reconstruction targets)
2. refresh_data_dirs.py (update Dolphin Sys, PPSSPP, etc.) 2. refresh_data_dirs.py (update Dolphin Sys, PPSSPP, etc.)
2b2. check_release_assets.py (large-files release vs gitignored files, online)
3. verify.py --all (check all platforms) 3. verify.py --all (check all platforms)
4. generate_pack.py --all (build ZIP packs) 4. generate_pack.py --all (build ZIP packs)
4b. generate install manifests 4b. generate install manifests
@@ -346,6 +347,20 @@ def main():
elif args.check_buildbot: elif args.check_buildbot:
print("\n--- 2b check buildbot system: SKIPPED (--offline) ---") print("\n--- 2b check buildbot system: SKIPPED (--offline) ---")
# Step 2b2: The release must serve the bytes the manifests describe. The
# installer compares Content-Length with the manifest size, so an asset
# uploaded before its local file was rebuilt fails every install of it.
if not args.offline:
ok, _ = run(
[sys.executable, "scripts/check_release_assets.py"],
"2b2 check release assets",
)
results["check_release_assets"] = ok
all_ok = all_ok and ok
else:
print("\n--- 2b2 check release assets: SKIPPED (--offline) ---")
results["check_release_assets"] = SKIPPED
# Step 2c: Generate truth YAMLs # Step 2c: Generate truth YAMLs
# A targeted run writes its model in a subdirectory of its own, and the # A targeted run writes its model in a subdirectory of its own, and the
# diff has to read the same one or it compares a narrowed model against # diff has to read the same one or it compares a narrowed model against
+205
View File
@@ -1184,6 +1184,211 @@ class EveryManifestEntryIsFetchable(unittest.TestCase):
for entry in data.get("omitted_files", []): for entry in data.get("omitted_files", []):
self.assertIn(entry.get("reason"), allowed) self.assertIn(entry.get("reason"), allowed)
def _manifests(self) -> list[Path]:
paths = sorted((ROOT / "install").glob("*.json"))
paths += sorted((ROOT / "install" / "targets").glob("*.json"))
self.assertTrue(paths, "no install manifests to check")
return paths
def test_repo_paths_name_the_file_the_database_holds(self):
"""A manifest written before a file moved sends the installer to 404.
database.json is regenerated whenever a file is refiled; the manifests
are a separate artefact and were left behind once (the two DS
firmware dumps of the 7 September refile). The installer fetches
repo_path from raw.githubusercontent.com, so a stale one is a
download that can never succeed.
"""
database = ROOT / "database.json"
if not database.is_file():
self.skipTest("database.json not generated")
files = json.loads(database.read_text(encoding="utf-8"))["files"]
for path in self._manifests():
with self.subTest(manifest=path.name):
data = json.loads(path.read_text(encoding="utf-8"))
stale = [
(entry["dest"], entry["repo_path"], files.get(entry["sha1"], {}).get("path"))
for entry in data.get("files", [])
if entry.get("repo_path")
and files.get(entry.get("sha1", ""), {}).get("path") != entry["repo_path"]
]
self.assertEqual(stale, [], f"{path.name} names moved files: {stale[:3]}")
def test_release_entries_are_exactly_the_files_git_does_not_hold(self):
"""The installer has two sources, and the manifest must pick the right one.
A committed file is served by the repository; a gitignored one by
the large-files release. Marking a committed file as a release asset
sends the installer to an asset nobody uploaded (sdlpal/5.avi, 83 MB
in git, was announced as release asset `5.avi`). Marking a gitignored
file as a repo file sends it to a raw URL that has no content.
"""
tracked = set(
subprocess.run(
["git", "ls-files", "-z", "--", "bios"],
capture_output=True, text=True, cwd=ROOT, check=True,
).stdout.split("\0")
)
if not tracked:
self.skipTest("not a git checkout")
for path in self._manifests():
with self.subTest(manifest=path.name):
data = json.loads(path.read_text(encoding="utf-8"))
wrong = []
for entry in data.get("files", []):
repo_path = entry.get("repo_path")
if not repo_path:
continue
committed = repo_path in tracked
if bool(entry.get("release_asset")) == committed:
wrong.append((entry["dest"], repo_path, "committed" if committed else "gitignored"))
self.assertEqual(wrong, [], f"{path.name} picks the wrong source: {wrong[:3]}")
class ReleaseAssetCriterion(unittest.TestCase):
"""What decides whether the installer fetches from the release is git, not size.
Files over 50 MB are supposed to be gitignored and uploaded to the
large-files release, but four committed files break that rule and one of
them reached a manifest as a release asset that does not exist. The
truthful question is whether the repository serves the bytes, and the
ledger of what it does not serve is .gitignore.
"""
def _repo(self, directory: str, ignored: str) -> str:
Path(directory, ".gitignore").write_text(ignored + "\n", encoding="utf-8")
return directory
def test_a_large_committed_file_is_served_by_the_repository(self):
from scripts import generate_pack
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
root = self._repo(directory, "bios/elsewhere.bin")
big = Path(root, "bios", "big.avi")
big.parent.mkdir()
with big.open("wb") as handle:
handle.truncate(60_000_000)
generate_pack._GITIGNORE_ENTRIES = None
try:
self.assertFalse(generate_pack._is_release_asset(str(big), root))
finally:
generate_pack._GITIGNORE_ENTRIES = None
def test_a_gitignored_file_is_served_by_the_release_whatever_its_size(self):
from scripts import generate_pack
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
root = self._repo(directory, "bios/small.zip")
small = Path(root, "bios", "small.zip")
small.parent.mkdir()
small.write_bytes(b"PK")
generate_pack._GITIGNORE_ENTRIES = None
try:
self.assertTrue(generate_pack._is_release_asset(str(small), root))
finally:
generate_pack._GITIGNORE_ENTRIES = None
class ReleaseAssetsMatchTheCollection(unittest.TestCase):
"""The bytes the release serves must be the bytes the manifests describe.
install.py checks Content-Length against the manifest size before it
downloads, and the manifest size is that of the local copy. Both FBNeo
sample archives were rebuilt locally after their upload and never
re-uploaded: same members, different container, every install of them
failed. The comparison is offline here on a fixture; the script runs it
against the live release.
"""
def test_missing_and_resized_assets_are_reported_and_nothing_else(self):
from scripts import check_release_assets
expected = {
"bios/Other/fbneo/fbneo/samples/twotiger.zip": 154985212,
"bios/Arcade/MAME/MAME 0.174 Arcade XML.dat": 53677408,
"bios/Other/sdlpal/sdlpal/5.avi": 82833972,
}
assets = {
"twotiger.zip": 154888877,
"MAME.0.174.Arcade.XML.dat": 53677408,
"unrelated.zip": 1,
}
findings = check_release_assets.compare(expected, assets)
self.assertEqual(
findings,
[
("missing", "bios/Other/sdlpal/sdlpal/5.avi", 82833972, None),
("size", "bios/Other/fbneo/fbneo/samples/twotiger.zip", 154985212, 154888877),
],
)
def test_expected_assets_are_the_gitignored_database_entries(self):
from scripts import check_release_assets
db = {
"files": {
"a" * 40: {"path": "bios/Other/x/big.zip", "size": 10},
"b" * 40: {"path": "bios/Other/x/small.bin", "size": 2},
}
}
gitignore = "# large\nbios/Other/x/big.zip\ntmp/\n"
self.assertEqual(
check_release_assets.expected_assets(db, gitignore),
{"bios/Other/x/big.zip": 10},
)
def test_release_notes_are_rendered_from_the_collection(self):
"""The release page is written by hand and drifts; it is rendered instead.
Three assets had no row, three rows carried the SHA1 of an asset
that had since been replaced, and the footer counted 41 files for 44.
Hand-written descriptions survive by name; a new row takes its
description from the profile that declares the file; an asset the
collection does not index is listed apart, with its size only.
"""
from scripts import check_release_assets
db = {"files": {
"a" * 40: {"path": "bios/Nintendo/Wii/nand.bin", "size": 553649152},
"b" * 40: {"path": "bios/Other/fbneo/fbneo/samples/twotiger.zip", "size": 154985212},
"c" * 40: {"path": "bios/Other/x/small.bin", "size": 2},
}}
gitignore = "bios/Nintendo/Wii/nand.bin\nbios/Other/fbneo/fbneo/samples/twotiger.zip\n"
assets = {"nand.bin": 553649152, "twotiger.zip": 154985212,
"sdlpal-data.zip": 140773720, "whoopee.zip": 222665810}
previous = (
"## Arcade\n\n| File | Description | Size | SHA1 |\n|---|---|---|---|\n"
"| [twotiger.zip](u) | Two Tigers samples | 148 MB | `" + "0" * 40 + "` |\n"
"| [whoopee.zip](u) | Toaplan Whoopee samples | 212 MB | `" + "9" * 40 + "` |\n"
)
descriptions = {"nand.bin": "BootMii NAND backup"}
bundles = {"sdlpal-data.zip": "SDLPAL Chinese Paladin game data (.mkf archives)"}
body = check_release_assets.render_notes(
db, gitignore, assets, previous, descriptions, bundles, {"sdlpal-data.zip": "d" * 40}
)
self.assertIn("| [twotiger.zip](https://github.com/Abdess/retrobios/releases/download/large-files/twotiger.zip) | Two Tigers samples | 148 MB | `" + "b" * 40 + "` |", body)
self.assertIn("| [nand.bin](https://github.com/Abdess/retrobios/releases/download/large-files/nand.bin) | BootMii NAND backup | 528 MB | `" + "a" * 40 + "` |", body)
self.assertIn("| [sdlpal-data.zip](https://github.com/Abdess/retrobios/releases/download/large-files/sdlpal-data.zip) | SDLPAL Chinese Paladin game data (.mkf archives) | 134 MB | `" + "d" * 40 + "` |", body)
self.assertIn("## Not indexed", body)
self.assertIn("| [whoopee.zip](https://github.com/Abdess/retrobios/releases/download/large-files/whoopee.zip) | Toaplan Whoopee samples | 212 MB |", body)
self.assertNotIn("9" * 40, body)
self.assertNotIn("small.bin", body)
self.assertIn("4 files, 1.0 GB total", body)
self.assertEqual(body.index("## Console firmware"), min(
body.index(h) for h in ("## Console firmware", "## Arcade", "## Not indexed")
))
def test_stale_notes_are_a_finding(self):
from scripts import check_release_assets
findings = check_release_assets.compare({}, {}, notes_current=False)
self.assertEqual(findings, [("notes", "release description", 0, None)])
def test_pipeline_runs_the_check_online_and_marks_it_skipped_offline(self):
source = (ROOT / "scripts" / "pipeline.py").read_text(encoding="utf-8")
self.assertIn("check_release_assets.py", source)
self.assertIn('results["check_release_assets"] = SKIPPED', source)
class PreservedBytesAreNeverNormalised(unittest.TestCase): class PreservedBytesAreNeverNormalised(unittest.TestCase):
"""git must not rewrite a preserved file's line endings. """git must not rewrite a preserved file's line endings.
+17
View File
@@ -204,6 +204,23 @@ class CloneMapSurvivesASecondRun(_Bios):
path = Path(self._tmp.name) / "_mame_clones.json" path = Path(self._tmp.name) / "_mame_clones.json"
return json.loads(path.read_text()) if path.exists() else {} return json.loads(path.read_text()) if path.exists() else {}
def test_the_map_is_written_beside_the_scanned_tree_not_the_cwd(self):
"""A test or a run from another directory must not touch the repo's map.
The map was written to the working directory. The test suite runs
from the repo root, so a fixture scan of a temporary tree rewrote the
committed _mame_clones.json with two fixture entries, and a later
rewrite left 2 of its 71 parents.
"""
elsewhere = Path(self._tmp.name) / "elsewhere"
elsewhere.mkdir()
os.chdir(elsewhere)
self.write("Arcade/MAME/bbc_m87.zip")
self.write("Arcade/MAME/bbc_24bbc.zip")
self.run_dedup()
self.assertFalse((elsewhere / "_mame_clones.json").exists())
self.assertIn("bbc_m87.zip", self._map())
def test_a_recorded_mapping_survives_a_later_run(self): def test_a_recorded_mapping_survives_a_later_run(self):
self.write("Arcade/MAME/bbc_m87.zip") self.write("Arcade/MAME/bbc_m87.zip")
self.write("Arcade/MAME/bbc_24bbc.zip") self.write("Arcade/MAME/bbc_24bbc.zip")
+16
View File
@@ -132,6 +132,22 @@ gh release upload large-files "bios/Sony/PS3/PS3UPDAT.PUP#PS3UPDAT.PUP"
**Local cache.** `generate_pack.py` calls `fetch_large_file()` which downloads **Local cache.** `generate_pack.py` calls `fetch_large_file()` which downloads
from the release and caches in `.cache/large/` for subsequent runs. from the release and caches in `.cache/large/` for subsequent runs.
**Check.** The installer refuses a download whose `Content-Length` differs
from the manifest size, and the manifest size is that of the local file. A
file rebuilt locally after its upload therefore fails every install until it
is uploaded again (`--clobber`). `python scripts/check_release_assets.py`
compares every gitignored `bios/` path in the database with the asset of the
same name, and the release page with the one it renders from the collection;
the online pipeline runs it as step 2b2. To refresh the page:
```bash
python scripts/check_release_assets.py --notes tmp/notes.md
gh release edit large-files --notes-file tmp/notes.md
```
An asset no database entry names is listed on the page under "Not indexed",
with its size only: the collection vouches for no hash it does not hold.
## Cutting a release ## Cutting a release
Releasing is deliberate and local. Nothing on GitHub builds a pack: the Releasing is deliberate and local. Nothing on GitHub builds a pack: the