Author SHA1 Message Date
Abdessamad Derraz 60ce4dff27 chore: refresh mame bios hashes to 0.289 2026-10-10 00:58:21 +02:00
Abdessamad Derraz f1d2b8adab fix: a plain build starts with no split paths 2026-10-09 23:51:58 +02:00
Abdessamad Derraz 8b76ea0310 refactor: name the two prologues of validate_pr 2026-10-09 23:37:40 +02:00
Abdessamad Derraz ac0c92e60a chore: regenerate manifests and readme 2026-10-09 23:18:37 +02:00
Abdessamad Derraz 6a2f9bf2e7 chore: read the three copy counts 2026-10-09 23:18:36 +02:00
Abdessamad Derraz d3e3c08b6c chore: name the slug omission in the schema 2026-10-09 23:18:36 +02:00
Abdessamad Derraz ed49b26c12 fix: a declared alias settles the entry 2026-10-09 23:18:36 +02:00
Abdessamad Derraz fab7d4eb2f chore: read the exit code of the region runs 2026-10-09 22:57:15 +02:00
Abdessamad Derraz 5c72a85ad6 chore: only a platform's profiles disarm its check 2026-10-09 22:57:15 +02:00
Abdessamad Derraz 3dd17ddead chore: pin every no-dedup directory 2026-10-09 22:57:15 +02:00
Abdessamad Derraz cc04ff78c9 fix: a step that could not run says so 2026-10-09 22:57:15 +02:00
Abdessamad Derraz 4a30cbf882 fix: count a refused standalone copy as an error 2026-10-09 22:57:15 +02:00
Abdessamad Derraz 1a862bc318 fix: retrobat defaults to its installer directory 2026-10-09 22:57:15 +02:00
Abdessamad Derraz c13d5c5368 fix: slots read the one gap scope rule 2026-10-09 22:57:15 +02:00
Abdessamad Derraz d295e51ad6 fix: list systems the way --system selects 2026-10-09 22:57:15 +02:00
Abdessamad Derraz 6207e81d9c refactor: read the grouping where it lives 2026-10-09 22:45:58 +02:00
Abdessamad Derraz 8fb65b8e97 fix: exporters keep what the maintainer wrote 2026-10-09 22:45:57 +02:00
Abdessamad Derraz 3baec1794b fix: match a loose dump by crc32 and size 2026-10-09 22:45:57 +02:00
Abdessamad Derraz 9cb152ef23 fix: file the psp bios where pcsx_rearmed does 2026-10-09 22:45:57 +02:00
Abdessamad Derraz 8a0c3b34ea fix: resolve the region audit like the pack 2026-10-09 22:45:57 +02:00
Abdessamad Derraz 45275811f0 fix: keep the quasi88 group to the extension roms 2026-10-09 22:45:57 +02:00
Abdessamad Derraz 82bdcfa1b1 fix: read the clone map beside the scanned tree 2026-10-09 22:45:57 +02:00
Abdessamad Derraz be2daab00c fix: judge the gap report on the flat declared set 2026-10-09 22:45:57 +02:00
Abdessamad Derraz 6e6ae77ccd fix: one selection and one name per system 2026-10-09 22:34:20 +02:00
Abdessamad Derraz b9cf7c7415 fix: report the extras a platform cannot place 2026-10-09 22:34:20 +02:00
Abdessamad Derraz f47088fe37 fix: write artifacts whole or not at all 2026-10-09 22:34:20 +02:00
Abdessamad Derraz 53bd42b2f4 feat: collect the pcsx2 forks' pinned resources 2026-10-09 22:20:00 +02:00
Abdessamad Derraz 5090208a64 fix: keep another emulator's tree out of resolution 2026-10-09 22:20:00 +02:00
Abdessamad Derraz 5ebb141dbf fix: score a losing twin against the shipped file 2026-10-09 22:20:00 +02:00
Abdessamad Derraz 8944486597 fix: a listed hash the other layer holds is agreement 2026-10-09 21:59:38 +02:00
Abdessamad Derraz 606d91ebab fix: prefer among the entries a reduced pack keeps 2026-10-09 21:59:38 +02:00
Abdessamad Derraz 3dc353dc73 fix: hold verify and pack to the same count both ways 2026-10-09 21:59:38 +02:00
Abdessamad Derraz 919f00a6ee fix: cite pcsx2 bundled resources at the pin 2026-10-09 17:56:02 +02:00
Abdessamad Derraz 5e0ec6f442 chore: hoist test imports to module level 2026-10-06 13:59:50 +02:00
Abdessamad Derraz d8ad89902b refactor: split helpers out of grown functions 2026-10-06 13:59:50 +02:00
Abdessamad Derraz ba7c185d8a refactor: share the gap scope rule 2026-10-06 13:59:50 +02:00
Abdessamad Derraz 12feecd3d5 chore: regenerate database, manifests and readme 2026-10-06 13:06:27 +02:00
Abdessamad Derraz 87a591a1ae fix: cite lrps2 bios loaders at the pin 2026-10-06 13:01:09 +02:00
Abdessamad Derraz 71aaf4bb04 fix: cite pokemini's bios reload at the pin 2026-10-06 13:00:09 +02:00
Abdessamad Derraz 0f6b948ca5 fix: cite the msx-emu fdc mapper at the pin 2026-10-06 13:00:00 +02:00
Abdessamad Derraz 6f9e2b3fc9 fix: cite the casloopy rom block at the pin 2026-10-06 12:59:43 +02:00
Abdessamad Derraz 4117589405 fix: a cited range past the file end is gone 2026-10-06 12:59:23 +02:00
Abdessamad Derraz 707a3e0447 fix: cite dolphin's NAND import where it is 2026-10-06 12:59:23 +02:00
Abdessamad Derraz 4dcc329e7e fix: pin kronos on the branch the buildbot builds 2026-10-06 12:56:44 +02:00
Abdessamad Derraz 1937f0c509 fix: name a file's readers by resolution 2026-10-06 12:55:41 +02:00
Abdessamad Derraz ed7ec8b6f8 fix: show unsourceable entries apart on the site 2026-10-06 12:53:10 +02:00
Abdessamad Derraz 580a082b0f fix: slot emulator entries where the pack puts them 2026-10-06 12:52:24 +02:00
Abdessamad Derraz c1b930a6fd fix: keep save and content files out of the truth 2026-10-06 12:48:58 +02:00
Abdessamad Derraz 29e2e13d65 fix: cross-reference the build a platform runs 2026-10-06 12:47:50 +02:00
Abdessamad Derraz ed7e83e99b fix: default each platform to the folder it reads 2026-10-06 12:46:35 +02:00
Abdessamad Derraz a7de9b9c9c fix: read extract folders from the registry 2026-10-06 12:44:32 +02:00
Abdessamad Derraz 908d75a86c fix: an untagged bios serves every region 2026-10-06 12:43:09 +02:00
Abdessamad Derraz 9304931845 fix: unmark files of standalone-only profiles 2026-10-06 12:39:34 +02:00
Abdessamad Derraz e39ac455cd fix: lock the pack directory while splitting 2026-10-06 12:34:31 +02:00
Abdessamad Derraz cfb038429d fix: hold dist/ for the whole pipeline run 2026-10-06 12:33:59 +02:00
Abdessamad Derraz e071233567 fix: refetch a large file the release replaced 2026-10-06 12:30:51 +02:00
Abdessamad Derraz 0dc0813bcd fix: check targeted packs the way --all builds 2026-10-06 12:29:40 +02:00
Abdessamad Derraz fc4451c59f docs: split parts are named, not renamed 2026-10-06 12:29:10 +02:00
Abdessamad Derraz 6fc879ce39 fix: name split parts directly, version their dir 2026-10-06 12:29:03 +02:00
Abdessamad Derraz 0c39fc479d fix: drop the --include-extras flag nothing read 2026-10-06 12:27:21 +02:00
Abdessamad Derraz ae333f80a6 fix: run every scraper contract class directly 2026-10-06 12:27:11 +02:00
Abdessamad Derraz c39588807f fix: refuse --include-archived without --all 2026-10-06 12:26:47 +02:00
Abdessamad Derraz db26a1276e fix: fail the fileless check on real errors 2026-10-06 12:25:18 +02:00
Abdessamad Derraz 1d00516d96 fix: read profile tree status from its own repo 2026-10-06 12:24:31 +02:00
Abdessamad Derraz 8bf8fdba9e fix: refuse a libretro scrape without core info 2026-10-06 12:23:48 +02:00
Abdessamad Derraz 1e61b749cf fix: refuse an emudeck scrape with no cores 2026-10-06 12:23:48 +02:00
Abdessamad Derraz 0eecb23724 fix: refuse batocera targets with unread boards 2026-10-06 12:22:51 +02:00
Abdessamad Derraz 548ea5935b fix: stop a scrape whose release tag is unread 2026-10-06 12:22:51 +02:00
Abdessamad Derraz cb5ce96103 fix: quote system.dat names as the original does 2026-10-06 12:16:23 +02:00
Abdessamad Derraz e787fcee8e fix: leave bizhawk's declared sha1 to bizhawk 2026-10-06 12:15:50 +02:00
Abdessamad Derraz 4727734c24 fix: refuse a one-md5 addition of several accepted 2026-10-06 12:15:49 +02:00
Abdessamad Derraz ecdfbdf64f fix: never pair files whose contents or dirs differ 2026-10-06 12:15:40 +02:00
Abdessamad Derraz e476758f73 feat: collect emucorex and dojo bundled resources 2026-10-06 12:13:27 +02:00
Abdessamad Derraz 06de56002d fix: prefer the owner's copy among equal path tails 2026-10-06 12:13:27 +02:00
Abdessamad Derraz b17d1f13ff fix: rank the owner's copy above shared tree names 2026-10-06 12:09:51 +02:00
Abdessamad Derraz bd669f6cee fix: look inside every zipped_file archive 2026-10-06 12:07:34 +02:00
Abdessamad Derraz 5b341dbe54 fix: report contradicted hashes in existence verify 2026-10-06 12:05:56 +02:00
Abdessamad Derraz 500535b7a6 fix: state no size for several accepted sizes 2026-10-06 12:04:24 +02:00
Abdessamad Derraz f3d01aeeeb refactor: split this round's fixes into small helpers 2026-10-06 11:16:06 +02:00
Abdessamad Derraz 70d6f431e3 fix: count variant choices made through validated_choice 2026-10-06 10:23:20 +02:00
Abdessamad Derraz 79cdbc24cf chore: regenerate database, manifests and readme 2026-10-06 09:59:51 +02:00
Abdessamad Derraz a24e17535f fix: count core archives among the core files 2026-10-06 09:54:33 +02:00
Abdessamad Derraz 55983f6b01 fix: pack undistributed core extras in a split part 2026-10-06 09:53:09 +02:00
Abdessamad Derraz c6681c0a63 fix: compute coverage from the profiles the site reads 2026-10-06 09:48:15 +02:00
Abdessamad Derraz 3730fa78c1 refactor: write one contributing text for both pages 2026-10-06 09:47:09 +02:00
Abdessamad Derraz 5c89ac6edc docs: name jsonschema where the build needs it 2026-10-06 09:46:10 +02:00
Abdessamad Derraz e9653069a2 fix: require the jsonschema format checkers 2026-10-06 09:45:25 +02:00
Abdessamad Derraz 892cff38bc fix: diff every declaration of a repeated destination 2026-10-06 09:43:58 +02:00
Abdessamad Derraz 7822de38fb fix: count site cores with the builder's resolution 2026-10-06 09:43:38 +02:00
Abdessamad Derraz a70ff264d2 fix: refuse an unknown mame or fbneo version 2026-10-06 09:42:33 +02:00
Abdessamad Derraz a848730a80 fix: count mister corrections only where it writes 2026-10-06 09:41:53 +02:00
Abdessamad Derraz aac09aa6ad fix: count bizhawk corrections only where it writes 2026-10-06 09:41:28 +02:00
Abdessamad Derraz d085fd895b fix: write emudeck corrections, count only what lands 2026-10-06 09:41:04 +02:00
Abdessamad Derraz e4616f7098 fix: never let a romm addition replace their entry 2026-10-06 09:40:09 +02:00
Abdessamad Derraz c9689ebad7 fix: keep platform hashes the truth does not contradict 2026-10-06 09:39:07 +02:00
Abdessamad Derraz a4009e3bbe fix: recale prose onto the code the pins carry 2026-10-06 09:38:27 +02:00
Abdessamad Derraz 4e3ae6a941 fix: tag regions only where the code picks by area 2026-10-06 09:37:44 +02:00
Abdessamad Derraz 3183ad982c fix: read gaps by normalized system, sha256, file names 2026-10-06 09:32:32 +02:00
Abdessamad Derraz 7a21b8d814 fix: keep refs for another version out of the pin check 2026-10-06 09:28:51 +02:00
Abdessamad Derraz 7224114a4b fix: realign stale prose before a pin moves 2026-10-06 09:27:33 +02:00
Abdessamad Derraz 2d000b8056 fix: leave mode-keyed pins to a hand edit 2026-10-06 09:25:23 +02:00
Abdessamad Derraz 5bf25d1964 fix: move a ref only on a hash found elsewhere 2026-10-06 09:24:49 +02:00
Abdessamad Derraz cdecb096a1 fix: refuse --refresh-data where nothing refreshes 2026-10-06 09:24:24 +02:00
Abdessamad Derraz 38ee085722 fix: hold the data dir lock in emulator packs 2026-10-06 09:20:42 +02:00
Abdessamad Derraz 0c53142585 fix: build system packs beside, not over, emulator packs 2026-10-06 09:19:18 +02:00
Abdessamad Derraz bb6669c362 fix: reduce emulator packs by region after required-only 2026-10-06 09:13:15 +02:00
Abdessamad Derraz 2e2fe2944c fix: read the data registry from --platforms-dir 2026-10-06 09:10:57 +02:00
Abdessamad Derraz d205d0bbc8 fix: keep display names out of pack file paths 2026-10-06 09:09:02 +02:00
Abdessamad Derraz 3b2a232a9d fix: install the variant the pack ships 2026-10-06 09:08:33 +02:00
Abdessamad Derraz 9419cd94c7 fix: restore release assets through a scratch file 2026-10-06 08:58:40 +02:00
Abdessamad Derraz 0dd6abab2c fix: refuse unread verify flags, read platforms dir 2026-10-06 08:58:21 +02:00
Abdessamad Derraz 74bc75fb7c fix: anchor the slot mode test on the override lookup 2026-10-06 08:53:58 +02:00
Abdessamad Derraz 177896a021 fix: count exclusions from the full pack only 2026-10-06 08:52:51 +02:00
Abdessamad Derraz 5a776fc06e fix: give np2kai scsi.rom a read ceiling, not a size 2026-10-06 08:52:26 +02:00
Abdessamad Derraz 947fd23421 fix: read numbered master keys in the switch test 2026-10-06 08:51:16 +02:00
Abdessamad Derraz 776e50f3c2 fix: file the older switch keys as variants 2026-10-06 08:50:38 +02:00
Abdessamad Derraz dc41a75615 fix: read the hle index scope from its comprehension 2026-10-06 07:37:41 +02:00
Abdessamad Derraz 354aa30bfb refactor: name unused mock arguments, flatten withs 2026-10-06 07:06:53 +02:00
Abdessamad Derraz 05ae30ce48 refactor: simplify pack label, hash merge, diff exit 2026-10-06 07:06:22 +02:00
Abdessamad Derraz c8a30de493 refactor: let the retropie core check take none 2026-10-06 07:05:05 +02:00
Abdessamad Derraz ebe4a1bbcc refactor: extract the alias scope helpers 2026-10-06 07:04:54 +02:00
Abdessamad Derraz 8d98e1c659 refactor: extract companion extras and the kept filter 2026-10-06 07:03:59 +02:00
Abdessamad Derraz 0eb824c69c refactor: extract emulator archive placement 2026-10-06 07:00:22 +02:00
Abdessamad Derraz 49a4b181bf refactor: one mismatch rule for builder and verifier 2026-10-06 06:57:01 +02:00
Abdessamad Derraz fb9426dfb7 refactor: extract verify listing and resolution steps 2026-10-06 06:50:59 +02:00
Abdessamad Derraz a711f8c456 refactor: move the truth steps out of pipeline main 2026-10-06 06:47:56 +02:00
Abdessamad Derraz 2c602596d1 refactor: pull mismatch and region steps from manifest 2026-10-06 06:45:45 +02:00
Abdessamad Derraz d3c4198eb9 refactor: extract retrodeck key matching helpers 2026-10-06 06:40:41 +02:00
Abdessamad Derraz 20dc2654f5 refactor: split truth merge into match, fold, create 2026-10-06 06:40:07 +02:00
Abdessamad Derraz b9086262b7 refactor: split the pin gate from bump_commit 2026-10-06 06:39:02 +02:00
Abdessamad Derraz 226a5df74a refactor: hoist test imports, rank pairs at module level 2026-10-06 06:38:35 +02:00
Abdessamad Derraz a5f3a5db25 fix: name only pinned claims as left unserved 2026-10-06 06:33:36 +02:00
Abdessamad Derraz 70bd093a59 fix: accept verified_members in pack manifests 2026-10-06 06:31:10 +02:00
Abdessamad Derraz b788078590 chore: regenerate database, manifests and readme 2026-10-06 05:46:44 +02:00
Abdessamad Derraz 6b57377111 fix: name each retropie package once per target 2026-10-06 05:33:18 +02:00
Abdessamad Derraz 2c0f37ff85 fix: give solarus and eka2l1 their own pad mappings 2026-10-06 05:26:54 +02:00
Abdessamad Derraz d3e02c8c5e fix: redeploy the site on any script it imports 2026-10-06 05:23:27 +02:00
Abdessamad Derraz 240a30f1d7 fix: read retropie targets the way retropie does 2026-10-06 05:22:34 +02:00
Abdessamad Derraz 29a285d37e fix: stop target scrapes on a failed request 2026-10-06 05:19:05 +02:00
Abdessamad Derraz 7f9e68dc4a fix: tell same-name files apart by path in truth 2026-10-06 05:18:07 +02:00
Abdessamad Derraz 8f4dd17507 fix: preserve release assets by registered path 2026-10-06 05:13:34 +02:00
Abdessamad Derraz 969026b88d fix: one game data list for notes and composition 2026-10-06 05:11:41 +02:00
Abdessamad Derraz 95b5a038f2 fix: give a targeted export the full truth model 2026-10-06 05:09:45 +02:00
Abdessamad Derraz c67fd96b0f fix: fail a truth diff that compared nothing 2026-10-06 05:09:11 +02:00
Abdessamad Derraz b307de5719 fix: skip schema tests when jsonschema is absent 2026-10-06 05:08:40 +02:00
Abdessamad Derraz 1e4ffa3db1 fix: point the retrobat guide at install.ps1 2026-10-06 05:06:15 +02:00
Abdessamad Derraz 3067865b02 fix: read hle fallbacks from the platform cores only 2026-10-06 05:06:04 +02:00
Abdessamad Derraz 62bae63bbf fix: fail a pack member the collection cannot name 2026-10-06 05:05:14 +02:00
Abdessamad Derraz 64516f2bdc fix: propose retropie names per core, never into or-lists 2026-10-06 05:01:10 +02:00
Abdessamad Derraz 0e7ed498e3 fix: keep recalbox notes its own 2026-10-06 05:00:36 +02:00
Abdessamad Derraz 9a8c60e12e fix: correct one retrodeck revision, not all of them 2026-10-06 05:00:24 +02:00
Abdessamad Derraz 683ba4ef62 fix: export one file's identity, never two mixed 2026-10-06 05:00:02 +02:00
Abdessamad Derraz 217fff8e06 chore: point seven profiles' refs at their values 2026-10-06 04:59:20 +02:00
Abdessamad Derraz ec41f28f3e chore: point five profiles' refs at their values 2026-10-06 04:59:20 +02:00
Abdessamad Derraz 456eefa656 chore: recale wqxemu and emucorex onto head 2026-10-06 04:59:20 +02:00
Abdessamad Derraz 7bc5e795f1 feat: profile visual-pinball hrtf, collect the sofa 2026-10-06 04:59:20 +02:00
Abdessamad Derraz df70a1458c chore: recale jazz2-native and linapple onto head 2026-10-06 04:59:20 +02:00
Abdessamad Derraz 883c812b70 fix: drop cpc firmware regions, a keyboard picks 2026-10-06 04:58:59 +02:00
Abdessamad Derraz 6bffa5d2d8 fix: drop saturn bios regions, the list is fixed 2026-10-06 04:58:59 +02:00
Abdessamad Derraz 21cb6dca39 fix: drop pce system card regions, an option picks 2026-10-06 04:58:59 +02:00
Abdessamad Derraz a1ce1d2fa0 fix: hold the data dir lock while packing it 2026-10-06 04:56:16 +02:00
Abdessamad Derraz c9f8f4b230 fix: choose regions among the files required-only keeps 2026-10-06 04:54:05 +02:00
Abdessamad Derraz ce135b788f fix: compare only the full pack to verify 2026-10-06 04:49:21 +02:00
Abdessamad Derraz e8884e4341 fix: finalize only the packs this run names 2026-10-06 04:48:32 +02:00
Abdessamad Derraz d130f43705 fix: keep large-file revisions and clean failed fetches 2026-10-06 04:47:10 +02:00
Abdessamad Derraz c37d7fc95a fix: apply required-only to emulator pack archives 2026-10-06 04:46:03 +02:00
Abdessamad Derraz c5566b7d06 fix: count undistributed split extras with the helper 2026-10-06 04:43:49 +02:00
Abdessamad Derraz 7498304e44 fix: give system packs their core extras 2026-10-06 04:42:35 +02:00
Abdessamad Derraz 7b62f7f702 fix: refuse every flag a quick mode does not read 2026-10-06 04:39:08 +02:00
Abdessamad Derraz a6e321c1d6 chore: recale geolith jg.c refs onto upstream head 2026-10-06 04:37:48 +02:00
Abdessamad Derraz 4a32ef2bb3 fix: judge each profile ref by its own repository 2026-10-06 04:37:48 +02:00
Abdessamad Derraz fe877ff79c fix: keep cross-named aliases inside their profile 2026-10-06 04:35:35 +02:00
Abdessamad Derraz 619c282477 fix: arbitrate slots on the strongest profile proof 2026-10-06 04:32:19 +02:00
Abdessamad Derraz 2cafeb1d33 fix: key the casefold index on its source dict 2026-10-06 04:30:11 +02:00
Abdessamad Derraz 1373964746 fix: pack windows platforms case-insensitively 2026-10-06 04:29:49 +02:00
Abdessamad Derraz b5feaccc2a fix: carry pcsx2 companions under the names it opens 2026-10-06 04:29:16 +02:00
Abdessamad Derraz ac4776b2d0 fix: withhold a file only on the frontend digest 2026-10-06 04:21:47 +02:00
Abdessamad Derraz dbc8d63318 fix: one agnostic substitute rule for pack and verify 2026-10-06 04:19:40 +02:00
Abdessamad Derraz b2e488e0a4 fix: carry identity in extras, refile rom b 2026-10-06 04:12:08 +02:00
Abdessamad Derraz 93f3560d19 refactor: split the all-variants flag refusals 2026-10-06 03:11:07 +02:00
Abdessamad Derraz ab75123320 refactor: move flag refusals out of the main functions 2026-10-06 03:08:35 +02:00
Abdessamad Derraz a2e5d73bf5 chore: regenerate database, manifests and readme 2026-10-06 02:47:55 +02:00
Abdessamad Derraz ae0d4cbd82 fix: read accepted revision lists in profile aliases 2026-10-06 02:06:01 +02:00
Abdessamad Derraz f004e9f400 test: check every registered platform pack 2026-10-06 01:54:11 +02:00
Abdessamad Derraz b14a35b788 test: keep validation index on platform cores 2026-10-06 01:49:24 +02:00
Abdessamad Derraz 33184102c9 test: pin exact sets in the shipped content ratchets 2026-10-06 01:49:09 +02:00
Abdessamad Derraz 820ed9cff0 test: fail, not skip, when slot conflicts vanish 2026-10-06 01:48:17 +02:00
Abdessamad Derraz 2a25274fcf fix: read platform hashes as verify does in PR checks 2026-10-06 01:47:48 +02:00
Abdessamad Derraz d6afb09663 refactor: one upstream name index for core lookups 2026-10-06 01:47:16 +02:00
Abdessamad Derraz 4bde2f0080 fix: lay out truth with the pack mode rule 2026-10-06 01:43:30 +02:00
Abdessamad Derraz 077351bab3 feat: list retrodeck standalone emulators 2026-10-06 01:43:30 +02:00
Abdessamad Derraz e0141fab15 fix: apply changed-only to every profile_sync output 2026-10-06 01:42:00 +02:00
Abdessamad Derraz d257ecc6ab fix: decrypt otp.bin with the standard library 2026-10-06 01:41:27 +02:00
Abdessamad Derraz fdc7fc8326 fix: default profile report containers to empty 2026-10-06 01:39:55 +02:00
Abdessamad Derraz 2a56fccc85 fix: refuse targets when their list cannot be read 2026-10-06 01:39:31 +02:00
Abdessamad Derraz 403c7ff910 fix: register yaps2 resources, check data dir refs 2026-10-06 01:38:39 +02:00
Abdessamad Derraz 6b0cb815aa fix: build the database without network calls 2026-10-06 01:37:40 +02:00
Abdessamad Derraz 41474596d0 fix: gate on slot crashes and empty consistency 2026-10-06 01:34:59 +02:00
Abdessamad Derraz ec9bbd3424 fix: fail the run when a platform pack errors 2026-10-06 01:34:30 +02:00
Abdessamad Derraz 5771f195b0 fix: send retropie users to the retropie pack 2026-10-06 01:33:58 +02:00
Abdessamad Derraz 236d8322b0 fix: point pack guides at the path each platform reads 2026-10-06 01:33:33 +02:00
Abdessamad Derraz 16be9476cb fix: dedup core extras on their real destination 2026-10-06 01:32:56 +02:00
Abdessamad Derraz cd2919400a fix: skip archive members by their yaml key 2026-10-06 01:32:33 +02:00
Abdessamad Derraz 6d818d4da0 fix: count romset members as held dumps 2026-10-06 01:32:09 +02:00
Abdessamad Derraz 25692f98fc fix: recale pcsx_rearmed bios refs, pal to oceania 2026-10-06 01:31:31 +02:00
Abdessamad Derraz 96f538c2f3 fix: serve pal psx bios to oceania too 2026-10-06 01:31:27 +02:00
Abdessamad Derraz e6fa6c76c9 fix: drop fmsx font regions, fonts are options 2026-10-06 01:31:27 +02:00
Abdessamad Derraz 58f49e421b fix: drop prosystem regions, pal is a signal 2026-10-06 01:31:27 +02:00
Abdessamad Derraz 9934109c84 fix: drop o2em regions, the bios is a model 2026-10-06 01:31:27 +02:00
Abdessamad Derraz aee3c09b57 fix: drop opera regions, the bios is an option 2026-10-06 01:31:27 +02:00
Abdessamad Derraz b59fede88d chore: regenerate database, manifests and readme 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 95d10ee812 fix: count what each export format writes 2026-10-06 01:22:56 +02:00
Abdessamad Derraz bb789872b2 feat: collect the pc-88 font rom for quasi88 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 7437df78b3 fix: attach profile aliases to a unique name only 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 6d6cd7d668 fix: map pc-8801, dsi and cd-i system slugs 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 7da259ec6a fix: refresh data dirs under a lock, by rename 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 9e4b321886 feat: profile aps3e 2.43 on rpcs3 0.0.42 2026-10-06 00:26:36 +02:00
Abdessamad Derraz ce90dae4b9 feat: profile cap32 at c238550 with the 664 rom 2026-10-06 00:26:36 +02:00
Abdessamad Derraz d0a3563c3e fix: keep prose citations within reach and pinned 2026-10-06 00:26:36 +02:00
Abdessamad Derraz d18cd82d4b chore: recale gpsp refs onto their pin 2026-10-06 00:26:36 +02:00
Abdessamad Derraz 5c5e95218b chore: point desmume refs at the bios loads 2026-10-06 00:26:36 +02:00
Abdessamad Derraz f259634c38 fix: group variant slots across their systems 2026-10-06 00:26:36 +02:00
Abdessamad Derraz 1879c984f4 fix: decide slots on the region pass grouping 2026-10-06 00:26:36 +02:00
Abdessamad Derraz c4a080b0f4 fix: group regions by the target in pack integrity 2026-10-06 00:13:38 +02:00
Abdessamad Derraz 64f84e7ca6 fix: verify the pack the flags name, by exact name 2026-10-06 00:13:38 +02:00
Abdessamad Derraz 9be5a9bd0e chore: regenerate database, manifests and readme 2026-10-05 22:55:32 +02:00
Abdessamad Derraz 8b7e16caf6 feat: collect nethersx2-turnip assets from v0.7 2026-10-05 22:55:31 +02:00
Abdessamad Derraz 533dd7586f fix: scan free bios names only in the bios folder 2026-10-05 22:55:31 +02:00
Abdessamad Derraz 15e9f5486b feat: store pcsx2 achievement sound under its tail 2026-10-05 22:22:22 +02:00
Abdessamad Derraz 8e532db6aa fix: order same-named files by their owner's copy 2026-10-05 22:22:22 +02:00
Abdessamad Derraz f625039190 fix: no collision where one file passes every declaration 2026-10-05 22:12:23 +02:00
Abdessamad Derraz c61d35c41e fix: judge a slot on the declaration the pack ships 2026-10-05 22:10:48 +02:00
Abdessamad Derraz 30223faa00 fix: check every zipped member a platform declares 2026-10-05 22:05:10 +02:00
Abdessamad Derraz 8e41ddc297 fix: ship the archive every member declaration accepts 2026-10-05 22:05:10 +02:00
Abdessamad Derraz 138260c788 chore: regenerate install manifests 2026-10-05 22:00:13 +02:00
Abdessamad Derraz 29940abed6 fix: keep unsourceable entries out of the asset store 2026-10-05 22:00:13 +02:00
Abdessamad Derraz ed469ad8c9 chore: regenerate install manifests 2026-10-05 21:53:59 +02:00
Abdessamad Derraz b12a3280e6 fix: give the install manifest the arbitrated file 2026-10-05 21:53:59 +02:00
Abdessamad Derraz 3177a57103 fix: emulator region report reads the pack drop set 2026-10-05 21:43:32 +02:00
Abdessamad Derraz 012bf2fec2 fix: one rule for the variant an emulator accepts 2026-10-05 21:36:14 +02:00
Abdessamad Derraz 54016ad89b fix: keep profile identity on second-pass extras 2026-10-05 21:27:35 +02:00
Abdessamad Derraz eab1c03a52 fix: resolve held catacomb files, profile refs 2026-10-05 02:51:29 +02:00
Abdessamad Derraz e53eb8a43c chore: regenerate database, manifests and readme 2026-10-05 02:51:23 +02:00
Abdessamad Derraz 3f65defbf0 feat: collect ap6, shareddata, introset, a2 scsi 2026-10-05 02:51:23 +02:00
Abdessamad Derraz 15568b37aa feat: reprofile beetle_saturn, collect mpeg cards 2026-10-05 02:51:22 +02:00
Abdessamad Derraz a116192b3d feat: build engine data at pinned commits 2026-10-05 02:51:22 +02:00
Abdessamad Derraz 0d3a2b87ed fix: name homonymous release assets by path 2026-10-05 02:51:13 +02:00
Abdessamad Derraz 0d7939588e fix: one size predicate, size_options retired 2026-10-05 02:51:13 +02:00
70 changed files with 270462 additions and 2967 deletions

No files matched your search

+15 -15
View File
@@ -72,9 +72,9 @@ Each file is checked the way your platform checks it. Most compare a checksum, t
- **12 platforms** supported with platform-specific verification
- **506 emulators** profiled from source (RetroArch cores + standalone)
- **546 systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **18,997 files in the collection**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 5,261 system files, 2,808 arcade ROM sets, 10,928 game and engine data files. That is every platform and emulator together, so no pack holds them all: each carries what its own emulators load, counted in the download table
- **562 of 5,261 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index
- **16677 MB** total collection size
- **19,002 files in the collection**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 5,266 system files, 2,808 arcade ROM sets, 10,928 game and engine data files. That is every platform and emulator together, so no pack holds them all: each carries what its own emulators load, counted in the download table
- **562 of 5,266 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index
- **16687 MB** total collection size
## Supported systems
@@ -86,18 +86,18 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
| Platform | On its BIOS list | Files its emulators load | Checked by |
|----------|-----------------:|-------------------------:|------------|
| Batocera | 353/353 | 12,306/12,737 | MD5 hash |
| BizHawk | 118/118 | 806/807 | SHA1 hash |
| EmuDeck | 168/168 | 431/433 | MD5 hash |
| Lakka | 527/527 | 5,264/5,294 | file presence |
| Batocera | 353/353 | 12,287/12,718 | MD5 hash |
| BizHawk | 118/118 | 800/801 | SHA1 hash |
| EmuDeck | 168/168 | 430/432 | MD5 hash |
| Lakka | 526/526 | 5,256/5,286 | file presence |
| MiSTer FPGA | 81/81 | - | MD5 hash |
| ROCKNIX | 38/38 | 5,698/5,728 | MD5 hash |
| Recalbox | 351/351 | 2,730/2,738 | MD5 hash |
| RetroArch | 527/527 | 5,264/5,294 | file presence |
| RetroBat | 348/348 | 11,889/12,319 | MD5 hash |
| RetroDECK | 2,008/2,008 | 5,407/5,457 | MD5 hash |
| RetroPie * | 527/527 | 9,183/9,325 | file presence |
| RomM | 381/381 | 751/757 | MD5 hash |
| ROCKNIX | 38/38 | 5,695/5,725 | MD5 hash |
| Recalbox | 351/351 | 2,721/2,729 | MD5 hash |
| RetroArch | 526/526 | 5,256/5,286 | file presence |
| RetroBat | 348/348 | 11,875/12,305 | MD5 hash |
| RetroDECK | 2,008/2,008 | 5,387/5,437 | MD5 hash |
| RetroPie * | 526/526 | 9,171/9,313 | file presence |
| RomM | 381/381 | 746/752 | MD5 hash |
Each fraction is what the pack has over what is needed, counting required and optional files alike since both ship. The first column is the BIOS list the platform publishes. The second counts files its emulators load that this list never mentions, found by reading their source code, and it is routinely several times larger. A short fraction means files are still missing, and they are named in the [gap analysis](https://abdess.github.io/retrobios/gaps/).
That second number is a floor, not a ceiling: an emulator that accepts any file handed to it names none in its code, so nothing there can be counted.
@@ -180,4 +180,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 reasoning, and where it is weakest, is in the [FAQ](https://abdess.github.io/retrobios/wiki/faq/#is-this-legal).
*Auto-generated on 2026-10-06T11:05:39Z*
*Auto-generated on 2026-10-09T21:03:52Z*
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+166 -46
View File
@@ -1,8 +1,9 @@
{
"schema_version": 1,
"generated_at": "2026-10-06T10:11:17Z",
"total_files": 18997,
"total_size": 17487414657,
"generated_at": "2026-10-09T20:41:14Z",
"total_files": 19002,
"total_size": 17497698358,
"bios_dir": "bios",
"files": {
"738c7f5fd0c8b57ee2e87ae7a97bf8e21a821d07": {
"path": "bios/3D Realms/Duke Nukem 3D/DUKE.RTS",
@@ -131659,6 +131660,16 @@
"crc32": "6b7152b1",
"adler32": "d84f79e3"
},
"71fc274cbe27f382afb0324024ebdc6f59aa666d": {
"path": "bios/Other/aethersx2/resources/GameIndex.yaml",
"name": "GameIndex.yaml",
"size": 984983,
"sha1": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"md5": "3758db793dae7bc206fbbd799c3cbb05",
"sha256": "e7c70faf16ce180e6a17c040dc2bb326c6fcca63e7fb3f83f992d65fbe480443",
"crc32": "5acc416a",
"adler32": "986d242e"
},
"8acfbc4a51909de382e33a221a82f0b8efc04794": {
"path": "bios/Other/altirra/.variants/810-archiver.rom.8080d51e",
"name": "810-archiver.rom",
@@ -132709,16 +132720,6 @@
"crc32": "a5b357e6",
"adler32": "44a50462"
},
"dceb4536fba7fb89452864fc512a4d071a19eae0": {
"path": "bios/Other/armsx2/RedumpDatabase.yaml",
"name": "RedumpDatabase.yaml",
"size": 1906463,
"sha1": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"md5": "dbf48fe7b36fab316a1682961178ffd7",
"sha256": "755962450efd5effd2f3fe2f9a60c73d6cb3f9b4acea964abf5bb25a86ee25bc",
"crc32": "0496a60f",
"adler32": "c76311d2"
},
"00d62fbdc8d5dec4c659005e116d0ba2ee63b547": {
"path": "bios/Other/armsx2/Roboto-Regular.ttf",
"name": "Roboto-Regular.ttf",
@@ -132959,6 +132960,16 @@
"crc32": "6e8b2b47",
"adler32": "f5bc538e"
},
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8": {
"path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"name": "GameIndex.yaml",
"size": 2690355,
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"md5": "ec65dbfe7379cfac816b4d02259c0ca5",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"crc32": "1a8e4cf7",
"adler32": "479c1f04"
},
"456e74b0805d1931a7786d3d04673a3b02dd4dd3": {
"path": "bios/Other/armsx2/pcsx2/resources/fonts/NotoSansArabic-Regular.ttf",
"name": "NotoSansArabic-Regular.ttf",
@@ -133029,6 +133040,16 @@
"crc32": "05eb51ca",
"adler32": "cd7061bf"
},
"f7d0f1606c87019f00109ebdf0f260299f0a72da": {
"path": "bios/Other/armsx2/pcsx2/resources/patches.zip",
"name": "patches.zip",
"size": 2007224,
"sha1": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"md5": "809868f006eebec4e4615dd8bc397ef7",
"sha256": "8d95f1fba3acc83195bbac2d3e042b5f34c3c399856d9865e681d173f4cee609",
"crc32": "5120eae7",
"adler32": "68c47b84"
},
"8618516b26a0bc319ed174b66fd2d990eb5b74a3": {
"path": "bios/Other/armsx2/pcsx2/resources/shaders/common/ffx_a_fsr1.h",
"name": "ffx_a_fsr1.h",
@@ -135676,6 +135697,16 @@
"crc32": "e8038ec9",
"adler32": "933aaa5d"
},
"dceb4536fba7fb89452864fc512a4d071a19eae0": {
"path": "bios/Other/emucorex/resources/RedumpDatabase.yaml",
"name": "RedumpDatabase.yaml",
"size": 1906463,
"sha1": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"md5": "dbf48fe7b36fab316a1682961178ffd7",
"sha256": "755962450efd5effd2f3fe2f9a60c73d6cb3f9b4acea964abf5bb25a86ee25bc",
"crc32": "0496a60f",
"adler32": "c76311d2"
},
"a3d939af27fe0929e61b8e20d49ef58316c0a66c": {
"path": "bios/Other/emucorex/resources/patches.zip",
"name": "patches.zip",
@@ -192831,6 +192862,16 @@
"crc32": "0b805686",
"adler32": "5b7b9c1d"
},
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1": {
"path": "bios/Sony/PlayStation 2/resources/.variants/GameIndex.yaml.fc3c1420",
"name": "GameIndex.yaml",
"size": 2683897,
"sha1": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"md5": "fc3c14208a929c386445635b19baa3f2",
"sha256": "6531239315d759d0588985b31576696d2672c1a0e192633ea2d4f3d011bfdb37",
"crc32": "a7541e89",
"adler32": "078dc6f1"
},
"40ecf6138c99a0aba775ef93240b295025a45500": {
"path": "bios/Sony/PlayStation 2/resources/GameIndex.yaml",
"name": "GameIndex.yaml",
@@ -192841,6 +192882,16 @@
"crc32": "a3e573a0",
"adler32": "e808396a"
},
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a": {
"path": "bios/Sony/PlayStation 2/resources/RedumpDatabase.yaml",
"name": "RedumpDatabase.yaml",
"size": 1917242,
"sha1": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"md5": "0525ad1f1381152f20bfb05775cf39cd",
"sha256": "302b57fdd1f9472ef590789e4b347381cc8442a04a765c7eae3d3f2e4b06394c",
"crc32": "28709e73",
"adler32": "ffcd6c8e"
},
"c54f04b63ac6f04e0a0944ef260b5497e4494dbd": {
"path": "bios/Sony/PlayStation 2/resources/sounds/achievements/message.wav",
"name": "message.wav",
@@ -208066,6 +208117,7 @@
"dfdd9924295f491d54f30569874287f8": "0c15d6753cf0ae2a1737b6e857e8e82ee135a697",
"9a293eaaebca601f671070ad56cb81b6": "56306e7234bcffd14b4f977d42d237c4ad9c12d5",
"b494140c6250b2faa8a801e0efef08d5": "042ce2001fb438a0a34e0c5d0001a9d95d5516df",
"3758db793dae7bc206fbbd799c3cbb05": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"8080d51e2d787aa3fa181e4ddbd455ed": "8acfbc4a51909de382e33a221a82f0b8efc04794",
"5bac80439b16ee31feb5b45489a1a5da": "ad28302b3bdd8aef667969fceb073f0845942017",
"888b281fa986de2eaccb0f9c8b591395": "7fad1628fd7da3b7a4bad6a7040f9fb4ed945d3a",
@@ -208171,7 +208223,6 @@
"dcde75e5d5c62a03f7bf35312f5116a9": "0f37f2795ca071eb712d5243247675f140897c50",
"65e1739196c3163ec5e77f49eea118ae": "bd9262879f8a72ec610a9554b503d747194a0f49",
"4981d3d22bda9c3d0791c13cb612e95d": "343f90558952049a0232faf6cf136fc2b8d4be84",
"dbf48fe7b36fab316a1682961178ffd7": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"86da78cb59576328483a11c6ef74bc2b": "00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"8ad82b1dc550319993a7d6c932b2656d": "9056e59b69c6f1160fdfb92a0773f17f6307f3ad",
"a7470da305b78df13804a1e62c7bb41e": "660c7fe74de23ee604bea47673f84710bd34f075",
@@ -208196,6 +208247,7 @@
"083257a65f869a927d8f5a4534f46106": "36cc8f46014a321b7602e52c062ab23d7c256ccc",
"95f294d24b5e97ceab96282f1284190f": "8dba66121f3bbc4b2bdf67722ef4c2b6b6b23ee6",
"6f47c5daf33a759df3395db8ebe62ed1": "7b0bb79d38c573648de218645c6fb16449e156e0",
"ec65dbfe7379cfac816b4d02259c0ca5": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"75527903c6a793772b02807c5343f4c8": "456e74b0805d1931a7786d3d04673a3b02dd4dd3",
"46dbe14f577b56ade221acf89631fd7b": "69253a2722f999ba4205f2e679d0b75e3b17109f",
"5199b3892ba596a6bf8d0f930729157f": "9dc5783345f258d0c66c17028d39652daa234473",
@@ -208203,6 +208255,7 @@
"e910afbd441c5247227fb4a731d65799": "83d3c03dc1fc16f2a5b8bbf929d4cc21a078282c",
"19498325f22381f551df06ce1b4cad4f": "01299427e4d789295c614752c8398261f006479b",
"ad18e8fc265252917dd3698e268977d7": "59815234d228a7f390364c6d6380cc518ea63db1",
"809868f006eebec4e4615dd8bc397ef7": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"d91939cc76fb66f536476320987d0dc0": "8618516b26a0bc319ed174b66fd2d990eb5b74a3",
"7547b120ce49d79d50addee5ea6fe38b": "fb33e36d016aedea36478eb4bb15b35ca1b7bb0c",
"2c928cbd0410a7e7edae6b2808a1f566": "b478ce9d1343b8dd201c045f010bd1b64ba158ae",
@@ -208467,6 +208520,7 @@
"f4ae6809bd8c31573370e8da72514012": "e18b9aabbda0cc178436855ef8d04bce96533049",
"bf5e583b038933c5f7bc4f0a6f70617c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"602ea57b1d141e81b2a96f20a4b8098c": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"dbf48fe7b36fab316a1682961178ffd7": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"ed4d4e8f81d9707dbf4931a786b6b56d": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"efa6392d3dd6e66abf94542159889e4c": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"b56eb507fac5ac64a7079514c02b8ca7": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
@@ -213919,7 +213973,9 @@
"2e70ad008d4ec8549aada8002fdf42fb": "caa18ed04854a91e68f2d61a782560edd6373bbf",
"b53d51edc7fc086685e31b811dc32aad": "34a81db03ab617fbfdd7f9b861692dd2ecd57b82",
"5dea62f70439682a6cee16ba3823d11e": "6eddec30056cde7c664a0cf508dcad29353a12bb",
"fc3c14208a929c386445635b19baa3f2": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"de6da198a7359d1200c3eeb6df9c7eda": "40ecf6138c99a0aba775ef93240b295025a45500",
"0525ad1f1381152f20bfb05775cf39cd": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"d66b0bccd743bd11eaca55bcaec925da": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"44552702b05697a14ccbe2ca22ee7139": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"362e2dac5e4de13591c67fb5eceeb94a": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -247296,6 +247352,20 @@
"b624812e3aa0ef2f75847f47907296e1f370cecd",
"ca84ef31b6d48c6ab9530b743da373790a4f723b"
],
"GameIndex.yaml": [
"71fc274cbe27f382afb0324024ebdc6f59aa666d",
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"997fdfb1c1873c1a43972b2984ac0da61fd47b14",
"fca26d4f7c1fe0abf6534b470da2ee5ad461eba5",
"58fb1f8ea6cf2d526904349640453e682fe388a1",
"7efb2ed721cf0c0f0677324e09be311a8337e793",
"57369df9ca82a460e596cea7835c5a0797dee668",
"b22389650f6c0a1e276b48213fed8a9c1c6476ce",
"6eddec30056cde7c664a0cf508dcad29353a12bb",
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"810-archiver.rom": [
"8acfbc4a51909de382e33a221a82f0b8efc04794",
"1eb8a3ee2366c605f7e47f8025a9e714ee9cf129"
@@ -247611,9 +247681,6 @@
"NotoColorEmoji-Regular.ttf": [
"343f90558952049a0232faf6cf136fc2b8d4be84"
],
"RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"Roboto-Regular.ttf": [
"00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"d0860202be85147f3ce96d71aa88541d939a1aea"
@@ -247712,6 +247779,12 @@
"NotoSansTC-Regular.ttf": [
"59815234d228a7f390364c6d6380cc518ea63db1"
],
"patches.zip": [
"f7d0f1606c87019f00109ebdf0f260299f0a72da",
"a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"2562e542d66db945ccf158d7a221c2a28e3b6be4",
"541a74165496fb7ef35ce6ec03f711ebbe263e11"
],
"ffx_a_fsr1.h": [
"8618516b26a0bc319ed174b66fd2d990eb5b74a3"
],
@@ -248488,21 +248561,9 @@
"games.db": [
"0e2ee74d1f9ebc3a6faf9dbaebbacc2ff053f4df"
],
"GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"997fdfb1c1873c1a43972b2984ac0da61fd47b14",
"fca26d4f7c1fe0abf6534b470da2ee5ad461eba5",
"58fb1f8ea6cf2d526904349640453e682fe388a1",
"7efb2ed721cf0c0f0677324e09be311a8337e793",
"57369df9ca82a460e596cea7835c5a0797dee668",
"b22389650f6c0a1e276b48213fed8a9c1c6476ce",
"6eddec30056cde7c664a0cf508dcad29353a12bb",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"2562e542d66db945ccf158d7a221c2a28e3b6be4",
"541a74165496fb7ef35ce6ec03f711ebbe263e11"
"RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0",
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a"
],
"libopenglplugin.so": [
"407f0fde89ac1a628fed0329072dfae1f8d304b1",
@@ -283870,6 +283931,7 @@
"feeddf41": "0c15d6753cf0ae2a1737b6e857e8e82ee135a697",
"2b3e5a61": "56306e7234bcffd14b4f977d42d237c4ad9c12d5",
"6b7152b1": "042ce2001fb438a0a34e0c5d0001a9d95d5516df",
"5acc416a": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"bd90e77d": "8acfbc4a51909de382e33a221a82f0b8efc04794",
"196eed02": "ad28302b3bdd8aef667969fceb073f0845942017",
"55dfc9ac": "7fad1628fd7da3b7a4bad6a7040f9fb4ed945d3a",
@@ -283975,7 +284037,6 @@
"84ad7d58": "0f37f2795ca071eb712d5243247675f140897c50",
"1094a89e": "bd9262879f8a72ec610a9554b503d747194a0f49",
"a5b357e6": "343f90558952049a0232faf6cf136fc2b8d4be84",
"0496a60f": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"f49fbea6": "00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"27922b13": "9056e59b69c6f1160fdfb92a0773f17f6307f3ad",
"84d6cb99": "660c7fe74de23ee604bea47673f84710bd34f075",
@@ -284000,6 +284061,7 @@
"8216948a": "36cc8f46014a321b7602e52c062ab23d7c256ccc",
"2c7a1b2a": "8dba66121f3bbc4b2bdf67722ef4c2b6b6b23ee6",
"6e8b2b47": "7b0bb79d38c573648de218645c6fb16449e156e0",
"1a8e4cf7": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"92685e68": "456e74b0805d1931a7786d3d04673a3b02dd4dd3",
"b02dadf9": "69253a2722f999ba4205f2e679d0b75e3b17109f",
"96886a26": "9dc5783345f258d0c66c17028d39652daa234473",
@@ -284007,6 +284069,7 @@
"0fcd7543": "83d3c03dc1fc16f2a5b8bbf929d4cc21a078282c",
"07694737": "01299427e4d789295c614752c8398261f006479b",
"05eb51ca": "59815234d228a7f390364c6d6380cc518ea63db1",
"5120eae7": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"3a8aeca2": "8618516b26a0bc319ed174b66fd2d990eb5b74a3",
"6743e2ad": "fb33e36d016aedea36478eb4bb15b35ca1b7bb0c",
"ce9e57e4": "b478ce9d1343b8dd201c045f010bd1b64ba158ae",
@@ -284271,6 +284334,7 @@
"e1232465": "e18b9aabbda0cc178436855ef8d04bce96533049",
"ca72899c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"e8038ec9": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"0496a60f": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"8820b708": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"ea1fd68a": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"13eed5a5": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
@@ -289723,7 +289787,9 @@
"41391dd3": "caa18ed04854a91e68f2d61a782560edd6373bbf",
"25f6212a": "34a81db03ab617fbfdd7f9b861692dd2ecd57b82",
"0b805686": "6eddec30056cde7c664a0cf508dcad29353a12bb",
"a7541e89": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"a3e573a0": "40ecf6138c99a0aba775ef93240b295025a45500",
"28709e73": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"d766f908": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"2c3bcd32": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"0509cea5": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -302869,6 +302935,7 @@
"68757d34c21e28cce68c38ec1cc0de37b9fb046eed0716036ba4f1d0df3bcc5e": "0c15d6753cf0ae2a1737b6e857e8e82ee135a697",
"2a5448dd69dcd13c48c7b256e51907aa43bdc2ce2dde618a039c0b7cc009a57d": "56306e7234bcffd14b4f977d42d237c4ad9c12d5",
"9e7fb1335e56027f150b7ff9b7b6cddd9c0844af19be6dbc17347e1882022038": "042ce2001fb438a0a34e0c5d0001a9d95d5516df",
"e7c70faf16ce180e6a17c040dc2bb326c6fcca63e7fb3f83f992d65fbe480443": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"b861b939d32cb2c1e672cb9c50668394f1d83d723fd5f331727a19c36001f5ad": "8acfbc4a51909de382e33a221a82f0b8efc04794",
"5160ac22a55d6c82ee2304046b2b6eac13d153c0d0b1164bd693e93acc053623": "ad28302b3bdd8aef667969fceb073f0845942017",
"acb8f9c481ffe762021f980c0cee42bd1a512ccd210686e6e0ac7d6e34f5a374": "7fad1628fd7da3b7a4bad6a7040f9fb4ed945d3a",
@@ -302974,7 +303041,6 @@
"dedbaf271c3257db2a02493a53616d1da78302c678575c9b0f4d254cdb1e4293": "0f37f2795ca071eb712d5243247675f140897c50",
"c217cc125856b0ee9776208b8e0bf5f1a4f04561ff2fa644f690518f87f55942": "bd9262879f8a72ec610a9554b503d747194a0f49",
"1ca0775b9dc92d8d959cd06dd773e2d4dacc97301ad81e6b5aa02cd5e7233a27": "343f90558952049a0232faf6cf136fc2b8d4be84",
"755962450efd5effd2f3fe2f9a60c73d6cb3f9b4acea964abf5bb25a86ee25bc": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"797e35f7f5d6020a5c6ea13b42ecd668bcfb3bbc4baa0e74773527e5b6cb3174": "00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"4d901b560f20f455b3c82c6616b17b72360956747cb35c3808aa370052fe8e32": "9056e59b69c6f1160fdfb92a0773f17f6307f3ad",
"c4541eefde3f31e4216559ada91f52d1b7ec95b849f7e9afc7a05613a56c8bf2": "660c7fe74de23ee604bea47673f84710bd34f075",
@@ -302999,6 +303065,7 @@
"022c8b05d8f3e7e6046077a2d6ad2d3da1e7fa0e69c8321f3c556b9963be2477": "36cc8f46014a321b7602e52c062ab23d7c256ccc",
"c749cd9513bd345b9f12ff46eac0ffbf4e54c0b0884921df97ca8297396e3fea": "8dba66121f3bbc4b2bdf67722ef4c2b6b6b23ee6",
"ab829598e15b771112130cbafbd982736400690c11e3d7c81e3fc8c582c24f47": "7b0bb79d38c573648de218645c6fb16449e156e0",
"61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"90f50de40af560ad55f2fcf460af8205f842b534b057bd8b8bcf7d3e10d3223a": "456e74b0805d1931a7786d3d04673a3b02dd4dd3",
"11fa517464dbd568e855e449650a9004d1072c4a54ac1845aaafc462c0652b64": "69253a2722f999ba4205f2e679d0b75e3b17109f",
"1edcc5695148a632b8a5516ba41a86edf01f06289c5c2e84a1afafe833eb25da": "9dc5783345f258d0c66c17028d39652daa234473",
@@ -303006,6 +303073,7 @@
"9db318b65ee9c575a43e7efd273dbdd1afef26e467eea3e1073a50e1a6595f6d": "83d3c03dc1fc16f2a5b8bbf929d4cc21a078282c",
"ae82f4e2a55e1316a55bcc1d05e9555ce08d8bda07e893b486896b626fd852ff": "01299427e4d789295c614752c8398261f006479b",
"6b137e2eb57a2d1e4cf391c886ab1b783a0e5ddb5c75254748bde00c15cb8ff5": "59815234d228a7f390364c6d6380cc518ea63db1",
"8d95f1fba3acc83195bbac2d3e042b5f34c3c399856d9865e681d173f4cee609": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"f60e2722fcd13989523b9164d776ab382b3692791767f3bf8bb19967f763f3fb": "8618516b26a0bc319ed174b66fd2d990eb5b74a3",
"93c3922362ea7fc99cbcc698ca30c98de4f8c246d1fbb0b09e015ddef38ce3a5": "fb33e36d016aedea36478eb4bb15b35ca1b7bb0c",
"3d5b9cef750ea8bedceef43c4c52215e8280abb81b5b8fa002007bb3f97f0506": "b478ce9d1343b8dd201c045f010bd1b64ba158ae",
@@ -303270,6 +303338,7 @@
"306b53ecfb182a504dd8a7446093c316387d2fd8dc350d0792ed1753fe0996cd": "e18b9aabbda0cc178436855ef8d04bce96533049",
"cdefaf8efd47045f6820928eba84db5bed7557539328952b5f828315485e02ee": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"d5558a0e0aa033482b098cc8a6449e78137f5069beabf421ab60d9eccbb62d8f": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"755962450efd5effd2f3fe2f9a60c73d6cb3f9b4acea964abf5bb25a86ee25bc": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"aa14a46908c1114e0c0f1c06accbfa7efc83c1768fb501699e043cbdb1af797f": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"817eb2a258e44bb730b94ab77b00cac2480c2e27449078949e2897bad3cb3bc3": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"53e5ddc6825ecde7b502acb5fae3a173ba6696ec5b8bb7445f378f7b0ad1beb8": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
@@ -308722,7 +308791,9 @@
"6b61e27861c288a99ee9b4878862ed824173a037ebaca2bef0581017a85241e1": "caa18ed04854a91e68f2d61a782560edd6373bbf",
"e8eed9831c39fc298be087ba11d620673134bc683b97ccc909c555d29cc1b207": "34a81db03ab617fbfdd7f9b861692dd2ecd57b82",
"3e14a0d3db871704a9652a5d90d4ba62be2bceefbca6bb46da65275b09d4f78e": "6eddec30056cde7c664a0cf508dcad29353a12bb",
"6531239315d759d0588985b31576696d2672c1a0e192633ea2d4f3d011bfdb37": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"483face8f50f5e96d89528059522be3766b44f5e07e854bae1fa92ff0111d0a7": "40ecf6138c99a0aba775ef93240b295025a45500",
"302b57fdd1f9472ef590789e4b347381cc8442a04a765c7eae3d3f2e4b06394c": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"dfd8035387a463722ad07af3c91934e0a685f2afa57e31d305f0602656d1edb1": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"41de2047af8382988bfd568035ff26eec5f1cabc3efe773680546aa00a82857d": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"fc1b5e12e426b229fc4f114a4b8cbe6a5441f7e0ce6654abce382e677f1c051d": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -469525,6 +469596,18 @@
"aethersx2/game_controller_db.txt": [
"042ce2001fb438a0a34e0c5d0001a9d95d5516df"
],
"Other/aethersx2/resources/GameIndex.yaml": [
"71fc274cbe27f382afb0324024ebdc6f59aa666d"
],
"aethersx2/resources/GameIndex.yaml": [
"71fc274cbe27f382afb0324024ebdc6f59aa666d"
],
"resources/GameIndex.yaml": [
"71fc274cbe27f382afb0324024ebdc6f59aa666d",
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"Other/altirra/.variants/810-archiver.rom.8080d51e": [
"8acfbc4a51909de382e33a221a82f0b8efc04794"
],
@@ -470176,12 +470259,6 @@
"armsx2/NotoColorEmoji-Regular.ttf": [
"343f90558952049a0232faf6cf136fc2b8d4be84"
],
"Other/armsx2/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"armsx2/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"Other/armsx2/Roboto-Regular.ttf": [
"00d62fbdc8d5dec4c659005e116d0ba2ee63b547"
],
@@ -470329,6 +470406,15 @@
"armsx2/no-save.png": [
"7b0bb79d38c573648de218645c6fb16449e156e0"
],
"Other/armsx2/pcsx2/resources/GameIndex.yaml": [
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8"
],
"armsx2/pcsx2/resources/GameIndex.yaml": [
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8"
],
"pcsx2/resources/GameIndex.yaml": [
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8"
],
"Other/armsx2/pcsx2/resources/fonts/NotoSansArabic-Regular.ttf": [
"456e74b0805d1931a7786d3d04673a3b02dd4dd3"
],
@@ -470443,6 +470529,19 @@
"fonts/NotoSansTC-Regular.ttf": [
"59815234d228a7f390364c6d6380cc518ea63db1"
],
"Other/armsx2/pcsx2/resources/patches.zip": [
"f7d0f1606c87019f00109ebdf0f260299f0a72da"
],
"armsx2/pcsx2/resources/patches.zip": [
"f7d0f1606c87019f00109ebdf0f260299f0a72da"
],
"pcsx2/resources/patches.zip": [
"f7d0f1606c87019f00109ebdf0f260299f0a72da"
],
"resources/patches.zip": [
"f7d0f1606c87019f00109ebdf0f260299f0a72da",
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"Other/armsx2/pcsx2/resources/shaders/common/ffx_a_fsr1.h": [
"8618516b26a0bc319ed174b66fd2d990eb5b74a3"
],
@@ -473021,9 +473120,15 @@
"emucorex/resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039"
],
"resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"40ecf6138c99a0aba775ef93240b295025a45500"
"Other/emucorex/resources/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"emucorex/resources/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"resources/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0",
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a"
],
"Other/emucorex/resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
@@ -473031,9 +473136,6 @@
"emucorex/resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"Other/emucorex/resources/shaders/common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e"
],
@@ -544377,12 +544479,30 @@
".variants/GameIndex.yaml": [
"6eddec30056cde7c664a0cf508dcad29353a12bb"
],
"Sony/PlayStation 2/resources/.variants/GameIndex.yaml.fc3c1420": [
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1"
],
"PlayStation 2/resources/.variants/GameIndex.yaml.fc3c1420": [
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1"
],
"resources/.variants/GameIndex.yaml.fc3c1420": [
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1"
],
".variants/GameIndex.yaml.fc3c1420": [
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1"
],
"Sony/PlayStation 2/resources/GameIndex.yaml": [
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"PlayStation 2/resources/GameIndex.yaml": [
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"Sony/PlayStation 2/resources/RedumpDatabase.yaml": [
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a"
],
"PlayStation 2/resources/RedumpDatabase.yaml": [
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a"
],
"Sony/PlayStation 2/resources/sounds/achievements/message.wav": [
"c54f04b63ac6f04e0a0944ef260b5497e4494dbd"
],
+3
View File
@@ -148,6 +148,9 @@ files:
path: resources/GameIndex.yaml
required: false
bundled: true
sha1:
- 997fdfb1c1873c1a43972b2984ac0da61fd47b14
- 71fc274cbe27f382afb0324024ebdc6f59aa666d
description: "Game database"
note: "Read from the resources folder, keyed by serial, and carrying the per-game fixes, patches and settings overrides. Parsed into gamedb.cache in the cache folder and reparsed whenever the modification time moves."
source_ref: "aethersx2/pcsx2/GameDatabase.cpp:35-36, aethersx2/pcsx2/GameDatabase.cpp:348-352, aethersx2/pcsx2/GameDatabase.cpp:523-535"
+8
View File
@@ -272,6 +272,8 @@ files:
standalone_path: resources/GameIndex.yaml
required: false
bundled: true
sha1: 72bfdf91fa11a86e2810a7c167cdcf11e20418a8
size: 2690355
description: "Game database"
note: >-
Read from the resources folder, keyed by serial, carrying the per-game
@@ -297,6 +299,8 @@ files:
standalone_path: resources/RedumpDatabase.yaml
required: false
bundled: true
sha1: bf2c73d668cd671df8adfe2ec3c7074e8ba9176a
size: 1917242
description: "Disc track hash database"
note: >-
Loaded on the first hash lookup and used to tell the user whether a dump
@@ -308,6 +312,8 @@ files:
standalone_path: resources/patches.zip
required: false
bundled: true
sha1: f7d0f1606c87019f00109ebdf0f260299f0a72da
size: 2007224
description: "Game patch archive"
note: >-
Per-game pnach entries matched on serial and disc CRC. The patches and
@@ -787,6 +793,8 @@ files:
standalone_path: resources/sounds/achievements/unlock.wav
required: false
bundled: true
sha1: da328a0a3074ec86fee2d6741bfc2e5a1f68d063
size: 202702
description: "Achievement unlock sound"
source_ref: "pcsx2/Achievements.cpp:69, pcsx2/Achievements.cpp:1278-1284, pcsx2/Achievements.cpp:1581"
+8
View File
@@ -289,6 +289,8 @@ files:
system: sony-playstation-2
required: false
bundled: true
sha1: 2d9878fdd70979ecf6dd0da7652efd3797c26039
size: 2697551
category: game_data
description: "per-title compatibility and fix database"
note: >-
@@ -303,6 +305,8 @@ files:
system: sony-playstation-2
required: false
bundled: true
sha1: dceb4536fba7fb89452864fc512a4d071a19eae0
size: 1906463
category: game_data
description: "disc hash database"
note: >-
@@ -315,6 +319,8 @@ files:
system: sony-playstation-2
required: false
bundled: true
sha1: a3d939af27fe0929e61b8e20d49ef58316c0a66c
size: 2091144
category: game_data
description: "built-in game patch archive"
note: >-
@@ -554,6 +560,8 @@ files:
- name: unlock.wav
path: resources/sounds/achievements/unlock.wav
sha1: da328a0a3074ec86fee2d6741bfc2e5a1f68d063
size: 202702
system: sony-playstation-2
required: false
description: "achievement unlock sound"
+1153 -1046
View File
File diff suppressed because it is too large. Load diff
+443 -430
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -69,6 +69,8 @@ files:
path: resources/GameIndex.yaml
required: false
bundled: true
sha1: 5e2d50a3376dc6a0750505bec68626d39a5bd4c1
size: 2683897
category: game_data
description: "per-title compatibility and fix database"
source_ref: "pcsx2/GameDatabase.cpp:39, pcsx2/GameDatabase.cpp:1026-1035"
@@ -78,6 +80,8 @@ files:
path: resources/RedumpDatabase.yaml
required: false
bundled: true
sha1: bf2c73d668cd671df8adfe2ec3c7074e8ba9176a
size: 1917242
category: game_data
description: "disc hash database"
source_ref: "pcsx2/GameDatabase.cpp:1133, pcsx2/GameDatabase.cpp:1188-1197"
+4 -4
View File
@@ -59,14 +59,14 @@ files:
# -- Fallback BIOS filenames (searched in order after scph550x) --
- name: "psxonpsp660.bin"
description: "PSP embedded PS1 BIOS (region detected at runtime)"
region: [world]
description: "PSP embedded PS1 BIOS, filed as the Japanese BIOS"
region: [japan]
size: 524288
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[3])"
note: "Region detected from content at runtime. Must be 512 KB in libretro context."
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[3]), frontend/libretro.c:3654-3669 (try_use_bios region guess)"
note: "The core reads the region out of the image: bytes 0x7ff51 are ' J' in this dump, so it takes the JP slot (' E' takes EU, anything else US), one image per slot, first found kept. The disc licence picks the slot at boot (Config.Bios[Config.PsxRegion]); a US or EU disc only reaches this file when its own slot is empty. Must be 512 KB."
- name: "scph101.bin"
description: "SCPH-101 (v4.4 03-24-00 A) - PSone US"
+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "60e18da18b5a9a43b18d37020b676ad47babd6d12c5314e8fcc1370e4864c4e5"
$defaultInstallSha256 = "df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107"
$maximumInstallerBytes = 2MB
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
$temporary = $null
+19 -10
View File
@@ -120,7 +120,8 @@ def _os_default_dests(os_type: str) -> dict[str, Path]:
appdata = Path(os.environ.get("APPDATA", str(home / "AppData" / "Roaming")))
return {
"retroarch": appdata / "RetroArch" / "system",
"retrobat": profile / "RetroBat" / "bios",
# RetroBat's installer defaults to C:\RetroBat (installer.iss).
"retrobat": Path("C:/RetroBat/bios"),
"bizhawk": profile / "BizHawk" / "Firmware",
}
if os_type == "darwin":
@@ -1294,7 +1295,7 @@ def download_files(
def do_standalone_copies(
manifest: dict, bios_path: Path, os_type: str,
extra_dirs: dict[str, Path] | None = None,
) -> tuple[int, int]:
) -> tuple[int, int, int]:
"""Copy BIOS files to standalone emulator directories.
Supports:
@@ -1307,16 +1308,19 @@ def do_standalone_copies(
default per-OS locations, for setups such as LaunchBox that keep their
emulators outside them. The layout below the root is the same.
Returns (copied_count, skipped_count).
Returns (copied, skipped, failed): skipped is a target directory that
does not exist, failed a copy the system refused (permission, disk
full, read-only mount), which used to be counted as "dir not found".
"""
from fnmatch import fnmatch
copies = manifest.get("standalone_copies", [])
if not copies:
return 0, 0
return 0, 0, 0
copied = 0
skipped = 0
failed = 0
for entry in copies:
# Note entries: print message if emulator detected
@@ -1375,10 +1379,11 @@ def do_standalone_copies(
try:
shutil.copy2(src, dest)
copied += 1
except OSError:
skipped += 1
except OSError as exc:
print(f" ERROR: {dest}: {exc.strerror or exc}")
failed += 1
return copied, skipped
return copied, skipped, failed
def format_size(n: int) -> str:
@@ -1738,11 +1743,15 @@ def main() -> None:
print("\nStandalone emulators:")
lb_root = launchbox_root(os_type)
extra_dirs = launchbox_bios_dirs(lb_root) if lb_root else None
copied, skipped = do_standalone_copies(
copied, skipped, copy_failed = do_standalone_copies(
manifest, bios_path, os_type, extra_dirs
)
if copied or skipped:
print(f" {copied} copied, {skipped} skipped (dir not found)")
if copied or skipped or copy_failed:
print(
f" {copied} copied, {skipped} skipped (dir not found), "
f"{copy_failed} failed"
)
total_errors += copy_failed
elif manifest.get("standalone_copies") and not args.check:
print(
"\nStandalone copies skipped "
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="60e18da18b5a9a43b18d37020b676ad47babd6d12c5314e8fcc1370e4864c4e5"
DEFAULT_INSTALL_SHA256="df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+15 -195
View File
@@ -5,7 +5,7 @@
"platform": "batocera",
"display_name": "Batocera",
"version": "1.0",
"generated": "2026-10-06T06:50:25Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "bios",
"detect": [
{
@@ -237,10 +237,10 @@
}
}
],
"total_files": 12826,
"total_size": 8065338769,
"pack_files": 12828,
"pack_size": 8065338769,
"total_files": 12808,
"total_size": 8059515793,
"pack_files": 12810,
"pack_size": 8059515793,
"total_omitted": 30,
"omitted_files": [
{
@@ -3467,36 +3467,6 @@
"amiberry"
]
},
{
"dest": "cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cd32ext.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
"sha256": "d964826432559994ffe329f643fd4e42dbbff313362d9434945725d7119cae87",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-ext-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cdtvext.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -41017,16 +40987,6 @@
"clk"
]
},
{
"dest": "Acorn/basic.rom",
"sha1": "4a7393f3a45ea309f744441c16723e2ef447a281",
"sha256": "45bd55dc0f6f0f8f1fe9e2481de7def206565eec8f600ba3068b849ca4132079",
"size": 16384,
"repo_path": "bios/Acorn/BBC Micro/basic2.rom",
"cores": [
"clk"
]
},
{
"dest": "BBCMicro/dfs09.rom",
"sha1": "5cc0f14b8f46855c70eaa653cca4ad079b458732",
@@ -41607,16 +41567,6 @@
"clk"
]
},
{
"dest": "MSX/MSX.ROM",
"sha1": "409e82adac40f6bdd18eb6c84e8b2fbdc7fb5498",
"sha256": "999564a371dd2fdf7fbe8d853e82a68d557c27b7d87417639b2fa17704b83f78",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX.ROM",
"cores": [
"clk"
]
},
{
"dest": "MSX/msx-japanese.rom",
"sha1": "302afb5d8be26c758309ca3df611ae69cced2821",
@@ -41657,26 +41607,6 @@
"clk"
]
},
{
"dest": "MSX/MSX2.ROM",
"sha1": "6103b39f1e38d1aa2d84b1c3219c44f1abb5436e",
"sha256": "4bc4ae85ca5f28246cd3e7b7e017d298ddd375603657f84ef2c7954bc2d9b919",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX2.ROM",
"cores": [
"clk"
]
},
{
"dest": "MSX/MSX2EXT.ROM",
"sha1": "5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02",
"sha256": "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300",
"size": 16384,
"repo_path": "bios/Microsoft/MSX/MSX2EXT.ROM",
"cores": [
"clk"
]
},
{
"dest": "MSX/fmpac.rom",
"sha1": "9d789166e3caf28e4742fe933d962e99618c633d",
@@ -84773,36 +84703,6 @@
"mesen"
]
},
{
"dest": "dmg_boot.bin",
"sha1": "1db57a1e8b6e4096f811587f9eab0c6675fd9755",
"sha256": "6f64da4cecd7e54e2f928eb3e3ba7810a7a567d0d247cc71737d1771e073a916",
"size": 256,
"repo_path": "bios/Nintendo/Game Boy/dmg_boot.bin",
"cores": [
"mesen-s"
]
},
{
"dest": "cgb_boot.bin",
"sha1": "80e3ee82416c1a74827a5459fde32f04bf51a1f6",
"sha256": "119470a9cf9f0d85f0c7ba5a6cadffeec07eb9862719086d1a9808189605e388",
"size": 2304,
"repo_path": "bios/Nintendo/Game Boy Color/cgb_boot.bin",
"cores": [
"mesen-s"
]
},
{
"dest": "Mupen64plus/IPL.n64",
"sha1": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
"sha256": "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_v12_JPN.bin",
"cores": [
"mupen64plus_next"
]
},
{
"dest": "Mupen64plus/font.ttf",
"sha1": "1d7f6fa0c34e3d50be52c16a0816bebac309fe41",
@@ -89573,86 +89473,6 @@
"px68k"
]
},
{
"dest": "quasi88/n88.rom",
"sha1": "3518193b8207bdebf22c1380c2db8c554baff329",
"sha256": "d76f9118f9994eb391a0dc9c1fbfbfa2e9bc584a65da75f3ba7065d3c03cab76",
"size": 32768,
"repo_path": "bios/NEC/PC-98/n88.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_0.rom",
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_0.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_1.rom",
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_1.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_2.rom",
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_2.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_3.rom",
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_3.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88n.rom",
"sha1": "063609dd518c124a4fc9ba35d1bae35771666a34",
"sha256": "4db0b25bf25fd063b08fde9e76d7504440cd322a8ab6b72c4ee7d8ea98ce0839",
"size": 32768,
"repo_path": "bios/NEC/PC-98/n88n.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88sub.rom",
"sha1": "bb7103a0818850a039c67ff666a31ce49a8d516f",
"sha256": "9dc63118728a171ce2ed437ad38ef280b8b7bcf4a5a95fedf18946501b010456",
"size": 2048,
"repo_path": "bios/NEC/PC-98/N88SUB.ROM",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88knj1.rom",
"sha1": "82e11a177af6a5091dd67f50a2f4bafda84d6556",
"sha256": "7608040cffb1951e5cc567abb63f75b5746777a1ba96196c1b75606b793bb4bb",
"size": 131072,
"repo_path": "bios/NEC/PC-88/KANJI1.ROM",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88knj2.rom",
"sha1": "b86b932cb4ce34472e0fe0f01a4a4350e15dcb53",
@@ -89673,16 +89493,6 @@
"quasi88"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/font2.rom",
"sha1": "0a056b6d28b4b9e29a2d255bdaa25c24f176cf1e",
@@ -126211,6 +126021,16 @@
"fbneo"
]
},
{
"dest": "Acorn/basic.rom",
"sha1": "4a7393f3a45ea309f744441c16723e2ef447a281",
"sha256": "45bd55dc0f6f0f8f1fe9e2481de7def206565eec8f600ba3068b849ca4132079",
"size": 16384,
"repo_path": "bios/Acorn/BBC Micro/basic2.rom",
"cores": [
"clk"
]
},
{
"dest": "Triforce/segaboot.gcm",
"sha1": "7297a11be143011ceb75a0720f96855003415a5d",
+5 -65
View File
@@ -5,7 +5,7 @@
"platform": "bizhawk",
"display_name": "BizHawk",
"version": "1.0",
"generated": "2026-10-06T10:11:17Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "Firmware",
"detect": [
{
@@ -20,10 +20,10 @@
}
],
"standalone_copies": [],
"total_files": 954,
"total_size": 2588624930,
"pack_files": 956,
"pack_size": 2588624930,
"total_files": 948,
"total_size": 2585741346,
"pack_files": 950,
"pack_size": 2585741346,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -7839,16 +7839,6 @@
"puae"
]
},
{
"dest": "kick34005.A500",
"sha1": "891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
"sha256": "ee05862d8102a08436ac4056da7d549db31625c7d47b24dfb7b3c9a5c113ca53",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/Kickstart-v1.3-rev34.5-1987-Commodore-A500-A1000-A2000-CDTV.rom",
"cores": [
"puae"
]
},
{
"dest": "kick37175.A500",
"sha1": "c5839f5cb98a7a8947065c3ed2f14f5f42e334a1",
@@ -7859,16 +7849,6 @@
"puae"
]
},
{
"dest": "kick37350.A600",
"sha1": "02843c4253bbd29aba535b0aa3bd9a85034ecde4",
"sha256": "8d57d6e9d976df42d91ff9d10a5eedaede1a00e3b4b34cad750ca5ff7229f28a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-205-a600.rom",
"cores": [
"puae"
]
},
{
"dest": "kick40063.A600",
"sha1": "3b7f1493b27e212830f989f26ca76c02049f09ca",
@@ -7889,16 +7869,6 @@
"puae"
]
},
{
"dest": "kick40068.A1200",
"sha1": "e21545723fe8374e91342617604f1b3d703094f1",
"sha256": "6d43840d4099a74170ea0f0425b6257c3891ebcaa39c4d1840075a9ab22b5707",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a1200.rom",
"cores": [
"puae"
]
},
{
"dest": "kick39106.A4000",
"sha1": "f0b4e9e29e12218c2d5bd7020e4e785297d91fd7",
@@ -7909,16 +7879,6 @@
"puae"
]
},
{
"dest": "kick40068.A4000",
"sha1": "5fe04842d04a489720f0f4bb0e46948199406f49",
"sha256": "71236ed62c85394a3ad7d0d8d65405bac7a10083e06ba6b82120dacc7a00fe9a",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-a4000.rom",
"cores": [
"puae"
]
},
{
"dest": "kick34005.CDTV",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -7929,26 +7889,6 @@
"puae"
]
},
{
"dest": "kick40060.CD32",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"puae"
]
},
{
"dest": "kick40060.CD32.ext",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
"sha256": "d964826432559994ffe329f643fd4e42dbbff313362d9434945725d7119cae87",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-ext-310-cd32.rom",
"cores": [
"puae"
]
},
{
"dest": "rom.key",
"sha1": "801bbab32e3d37386dd1636d4f1d6fa7e21d5874",
+5 -15
View File
@@ -5,7 +5,7 @@
"platform": "emudeck",
"display_name": "EmuDeck",
"version": "1.0",
"generated": "2026-10-06T10:11:17Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "bios",
"detect": [
{
@@ -250,10 +250,10 @@
}
}
],
"total_files": 619,
"total_size": 3473031414,
"pack_files": 621,
"pack_size": 3473031414,
"total_files": 618,
"total_size": 3472900342,
"pack_files": 620,
"pack_size": 3472900342,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -2191,16 +2191,6 @@
"eden"
]
},
{
"dest": "dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
"sha256": "15a9bc3aa59fa998c03bfe0b1cf6cb1958c1660e8e72b3d7195b653f44038407",
"size": 131072,
"repo_path": "bios/Sega/Dreamcast/dc_nvmem.bin",
"cores": [
"flycast"
]
},
{
"dest": "dc/font.bin",
"sha1": "94c6ff701f79742ee23c0a028ac0be5aead77c73",
+22 -80
View File
@@ -5,7 +5,7 @@
"platform": "lakka",
"display_name": "Lakka",
"version": "1.0",
"generated": "2026-10-06T06:50:25Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "system",
"detect": [
{
@@ -16,10 +16,10 @@
}
],
"standalone_copies": [],
"total_files": 5867,
"total_size": 6399839286,
"pack_files": 8510,
"pack_size": 6440372450,
"total_files": 5861,
"total_size": 6398490526,
"pack_files": 8504,
"pack_size": 6439023690,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -1319,14 +1319,6 @@
"repo_path": "bios/NEC/PC-98/n88knj2.rom",
"cores": null
},
{
"dest": "quasi88/N88SUB.ROM",
"sha1": "bb7103a0818850a039c67ff666a31ce49a8d516f",
"sha256": "9dc63118728a171ce2ed437ad38ef280b8b7bcf4a5a95fedf18946501b010456",
"size": 2048,
"repo_path": "bios/NEC/PC-98/N88SUB.ROM",
"cores": null
},
{
"dest": "quasi88/N88EXT0.ROM",
"sha1": "d239c26ad7ac5efac6e947b0e9549b1534aa970d",
@@ -1867,10 +1859,10 @@
},
{
"dest": "BS-X.bin",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"sha1": "604556b2e62860af18db5a77f2956ebc75450020",
"sha256": "3ce321496edc5d77038de2034eb3fb354d7724afd0bc7fd0319f3eb5d57b984d",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"repo_path": "bios/Nintendo/SNES/Satellaview_BS-X.sfc",
"cores": null
},
{
@@ -4115,10 +4107,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"size": 2690355,
"repo_path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"cores": null
},
{
@@ -4269,16 +4261,6 @@
"amiberry"
]
},
{
"dest": "cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cd32ext.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
@@ -4289,16 +4271,6 @@
"amiberry"
]
},
{
"dest": "cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cdtvext.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -25539,16 +25511,6 @@
"mesence"
]
},
{
"dest": "SufamiTurbo.sfc",
"sha1": "ef86ea192eed03d5c413fdbbfd46043be1d7a127",
"sha256": "edacb453da14f825f05d1134d6035f4bf034e55f7cfb97c70c4ee107eabc7342",
"size": 262144,
"repo_path": "bios/Nintendo/SuFami Turbo/STBIOS.bin",
"cores": [
"mesence"
]
},
{
"dest": "ymf288_adpcm_rom.bin",
"sha1": "50b6c3e288eaa12ad275d4f323267bb72b0445df",
@@ -25579,26 +25541,6 @@
"mesence"
]
},
{
"dest": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Games Express CD Card (Japan).pce",
"sha1": "014881a959e045e00f4db8f52955200865d40280",
"sha256": "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d",
"size": 32768,
"repo_path": "bios/NEC/PC Engine/gecard.pce",
"cores": [
"mesence"
]
},
{
"dest": "Satellaview/BSX0120-0.bin",
"sha1": "f51c52bb26f2546fb167ab1084a56457475ff0a0",
@@ -53085,16 +53027,6 @@
"qemu"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/font2.rom",
"sha1": "0a056b6d28b4b9e29a2d255bdaa25c24f176cf1e",
@@ -56597,6 +56529,16 @@
"nekop2"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
+5 -95
View File
@@ -5,7 +5,7 @@
"platform": "recalbox",
"display_name": "Recalbox",
"version": "1.0",
"generated": "2026-10-06T06:50:25Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "bios",
"detect": [
{
@@ -237,10 +237,10 @@
}
}
],
"total_files": 3229,
"total_size": 4193661699,
"pack_files": 3231,
"pack_size": 4193661699,
"total_files": 3220,
"total_size": 4192446979,
"pack_files": 3222,
"pack_size": 4192446979,
"total_omitted": 1,
"omitted_files": [
{
@@ -3383,16 +3383,6 @@
"amiberry"
]
},
{
"dest": "cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cd32ext.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
@@ -3403,16 +3393,6 @@
"amiberry"
]
},
{
"dest": "cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cdtvext.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -4113,16 +4093,6 @@
"beetle_saturn"
]
},
{
"dest": "bk/BASIC10.ROM",
"sha1": "af7eae90c398ec12942a408fa5ac2722e331738c",
"sha256": "24be347356715352fcab480ed1559302d47d598255980a32150eba1431b462b8",
"size": 24576,
"repo_path": "bios/Elektronika/BK/BASIC10.ROM",
"cores": [
"bk"
]
},
{
"dest": "bk/TERAK.ROM",
"sha1": "273a9933b68a290c5aedcd6d69faa7b1d22c0344",
@@ -21453,16 +21423,6 @@
"fceumm"
]
},
{
"dest": "dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
"sha256": "15a9bc3aa59fa998c03bfe0b1cf6cb1958c1660e8e72b3d7195b653f44038407",
"size": 131072,
"repo_path": "bios/Sega/Dreamcast/dc_nvmem.bin",
"cores": [
"flycast"
]
},
{
"dest": "dc/font.bin",
"sha1": "94c6ff701f79742ee23c0a028ac0be5aead77c73",
@@ -21863,16 +21823,6 @@
"fuse"
]
},
{
"dest": "colecovision.rom",
"sha1": "45bedc4cbdeac66c7df59e9e599195c778d86a92",
"sha256": "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad",
"size": 8192,
"repo_path": "bios/Coleco/ColecoVision/BIOS.col",
"cores": [
"gearcoleco"
]
},
{
"dest": "eos.rom",
"sha1": "ad3c20ef444f10af7ae8eb75c81e500d9b1bba3d",
@@ -21993,16 +21943,6 @@
"gsplus"
]
},
{
"dest": "c600.rom",
"sha1": "d4181c9f046aafc3fb326b381baac809d9e38d16",
"sha256": "de1e3e035878bab43d0af8fe38f5839c527e9548647036598ee6fe7ec74d2a7d",
"size": 256,
"repo_path": "bios/Apple/Apple II/AppleIIe_DiskII.rom",
"cores": [
"gsplus"
]
},
{
"dest": "howard.o",
"sha1": "bdca7bbb647066cf7555ce5d555ce09de4ebc41b",
@@ -28043,16 +27983,6 @@
"px68k"
]
},
{
"dest": "quasi88/n88sub.rom",
"sha1": "bb7103a0818850a039c67ff666a31ce49a8d516f",
"sha256": "9dc63118728a171ce2ed437ad38ef280b8b7bcf4a5a95fedf18946501b010456",
"size": 2048,
"repo_path": "bios/NEC/PC-98/N88SUB.ROM",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88knj2.rom",
"sha1": "b86b932cb4ce34472e0fe0f01a4a4350e15dcb53",
@@ -28933,26 +28863,6 @@
"virtualjaguar"
]
},
{
"dest": "jagboot.rom",
"sha1": "af643900fea84234e08b616a6d27f03f0a61db96",
"sha256": "f0bd29599b3dfca7f5620c011256c1dc45c9d82c428a318f965c2ba3ded2e9f9",
"size": 131072,
"repo_path": "bios/Atari/Jaguar/boot.rom",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"sha1": "af643900fea84234e08b616a6d27f03f0a61db96",
"sha256": "f0bd29599b3dfca7f5620c011256c1dc45c9d82c428a318f965c2ba3ded2e9f9",
"size": 131072,
"repo_path": "bios/Atari/Jaguar/boot.rom",
"cores": [
"virtualjaguar"
]
},
{
"dest": "pinmame/roms/gts1.zip",
"sha1": "d4b0c8277c187a92279e2d2b71fc91b72d581257",
+22 -80
View File
@@ -5,7 +5,7 @@
"platform": "retroarch",
"display_name": "RetroArch",
"version": "1.0",
"generated": "2026-10-06T06:50:25Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "system",
"detect": [
{
@@ -255,10 +255,10 @@
}
}
],
"total_files": 5867,
"total_size": 6399839286,
"pack_files": 8510,
"pack_size": 6440372450,
"total_files": 5861,
"total_size": 6398490526,
"pack_files": 8504,
"pack_size": 6439023690,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -1558,14 +1558,6 @@
"repo_path": "bios/NEC/PC-98/n88knj2.rom",
"cores": null
},
{
"dest": "quasi88/N88SUB.ROM",
"sha1": "bb7103a0818850a039c67ff666a31ce49a8d516f",
"sha256": "9dc63118728a171ce2ed437ad38ef280b8b7bcf4a5a95fedf18946501b010456",
"size": 2048,
"repo_path": "bios/NEC/PC-98/N88SUB.ROM",
"cores": null
},
{
"dest": "quasi88/N88EXT0.ROM",
"sha1": "d239c26ad7ac5efac6e947b0e9549b1534aa970d",
@@ -2106,10 +2098,10 @@
},
{
"dest": "BS-X.bin",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"sha1": "604556b2e62860af18db5a77f2956ebc75450020",
"sha256": "3ce321496edc5d77038de2034eb3fb354d7724afd0bc7fd0319f3eb5d57b984d",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"repo_path": "bios/Nintendo/SNES/Satellaview_BS-X.sfc",
"cores": null
},
{
@@ -4354,10 +4346,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"size": 2690355,
"repo_path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"cores": null
},
{
@@ -4508,16 +4500,6 @@
"amiberry"
]
},
{
"dest": "cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cd32ext.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
@@ -4528,16 +4510,6 @@
"amiberry"
]
},
{
"dest": "cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cdtvext.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -25778,16 +25750,6 @@
"mesence"
]
},
{
"dest": "SufamiTurbo.sfc",
"sha1": "ef86ea192eed03d5c413fdbbfd46043be1d7a127",
"sha256": "edacb453da14f825f05d1134d6035f4bf034e55f7cfb97c70c4ee107eabc7342",
"size": 262144,
"repo_path": "bios/Nintendo/SuFami Turbo/STBIOS.bin",
"cores": [
"mesence"
]
},
{
"dest": "ymf288_adpcm_rom.bin",
"sha1": "50b6c3e288eaa12ad275d4f323267bb72b0445df",
@@ -25818,26 +25780,6 @@
"mesence"
]
},
{
"dest": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Games Express CD Card (Japan).pce",
"sha1": "014881a959e045e00f4db8f52955200865d40280",
"sha256": "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d",
"size": 32768,
"repo_path": "bios/NEC/PC Engine/gecard.pce",
"cores": [
"mesence"
]
},
{
"dest": "Satellaview/BSX0120-0.bin",
"sha1": "f51c52bb26f2546fb167ab1084a56457475ff0a0",
@@ -53324,16 +53266,6 @@
"qemu"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/font2.rom",
"sha1": "0a056b6d28b4b9e29a2d255bdaa25c24f176cf1e",
@@ -56836,6 +56768,16 @@
"nekop2"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
+10 -125
View File
@@ -5,9 +5,14 @@
"platform": "retrobat",
"display_name": "RetroBat",
"version": "1.0",
"generated": "2026-10-06T10:11:17Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "bios",
"detect": [
{
"os": "windows",
"method": "path_exists",
"path": "C:\\RetroBat\\bios"
},
{
"os": "windows",
"method": "path_exists",
@@ -236,10 +241,10 @@
}
}
],
"total_files": 12409,
"total_size": 8505437376,
"pack_files": 12411,
"pack_size": 8505437376,
"total_files": 12397,
"total_size": 8504927168,
"pack_files": 12399,
"pack_size": 8504927168,
"total_omitted": 30,
"omitted_files": [
{
@@ -41508,16 +41513,6 @@
"clk"
]
},
{
"dest": "MSX/MSX.ROM",
"sha1": "409e82adac40f6bdd18eb6c84e8b2fbdc7fb5498",
"sha256": "999564a371dd2fdf7fbe8d853e82a68d557c27b7d87417639b2fa17704b83f78",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX.ROM",
"cores": [
"clk"
]
},
{
"dest": "MSX/msx-japanese.rom",
"sha1": "302afb5d8be26c758309ca3df611ae69cced2821",
@@ -41558,26 +41553,6 @@
"clk"
]
},
{
"dest": "MSX/MSX2.ROM",
"sha1": "6103b39f1e38d1aa2d84b1c3219c44f1abb5436e",
"sha256": "4bc4ae85ca5f28246cd3e7b7e017d298ddd375603657f84ef2c7954bc2d9b919",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX2.ROM",
"cores": [
"clk"
]
},
{
"dest": "MSX/MSX2EXT.ROM",
"sha1": "5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02",
"sha256": "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300",
"size": 16384,
"repo_path": "bios/Microsoft/MSX/MSX2EXT.ROM",
"cores": [
"clk"
]
},
{
"dest": "Oric/basic10.rom",
"sha1": "333116e6884d85aaa4dfc7578a91cceeea66d016",
@@ -58056,16 +58031,6 @@
"fceumm"
]
},
{
"dest": "dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
"sha256": "15a9bc3aa59fa998c03bfe0b1cf6cb1958c1660e8e72b3d7195b653f44038407",
"size": 131072,
"repo_path": "bios/Sega/Dreamcast/dc_nvmem.bin",
"cores": [
"flycast"
]
},
{
"dest": "dc/font.bin",
"sha1": "94c6ff701f79742ee23c0a028ac0be5aead77c73",
@@ -58596,26 +58561,6 @@
"genesis_plus_gx"
]
},
{
"dest": "ROM",
"sha1": "e4fc7560b69d062cb2da5b1ffbe11cd1ca03cc37",
"sha256": "34cd454c6201bfd26839d6ace2ff6b1231e09d6279e49b4890311756dc3825fb",
"size": 131072,
"repo_path": "bios/Apple/Apple IIGS/ROM",
"cores": [
"gsplus"
]
},
{
"dest": "c600.rom",
"sha1": "d4181c9f046aafc3fb326b381baac809d9e38d16",
"sha256": "de1e3e035878bab43d0af8fe38f5839c527e9548647036598ee6fe7ec74d2a7d",
"size": 256,
"repo_path": "bios/Apple/Apple II/AppleIIe_DiskII.rom",
"cores": [
"gsplus"
]
},
{
"dest": "gzdoom.pk3",
"sha1": "810ed9968c5b3bdc2c348c9bc76e9f80fbe31308",
@@ -86502,46 +86447,6 @@
"px68k"
]
},
{
"dest": "quasi88/n88_0.rom",
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
"sha256": "8b584cd9143a670ddfce657ca65067c86f6be1db757a3085f48b1479ac6e8cf3",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_0.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_1.rom",
"sha1": "4ae4d37409ff99411a623da9f6a44192170a854e",
"sha256": "810936574cdf68849822a1570046091f0bad0590dee3635dbc89a4cc4e099555",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_1.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_2.rom",
"sha1": "e94278682ef9e9bbb82201f72c50382748dcea2a",
"sha256": "7d361b1bfd8523cd580bd9ca73902154556fbf45f60f0f539beef397d9462646",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_2.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_3.rom",
"sha1": "af452aed79b072c4d17985830b7c5dca64d4b412",
"sha256": "3dc431af6e0c724582a3aa49e5c22cb9cc99d8298a4cce500e62bbaf1bc4bffb",
"size": 8192,
"repo_path": "bios/NEC/PC-98/n88_3.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88knj1.rom",
"sha1": "82e11a177af6a5091dd67f50a2f4bafda84d6556",
@@ -86572,16 +86477,6 @@
"quasi88"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/font2.rom",
"sha1": "0a056b6d28b4b9e29a2d255bdaa25c24f176cf1e",
@@ -120612,16 +120507,6 @@
"virtualjaguar"
]
},
{
"dest": "jagboot.rom",
"sha1": "af643900fea84234e08b616a6d27f03f0a61db96",
"sha256": "f0bd29599b3dfca7f5620c011256c1dc45c9d82c428a318f965c2ba3ded2e9f9",
"size": 131072,
"repo_path": "bios/Atari/Jaguar/boot.rom",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"sha1": "af643900fea84234e08b616a6d27f03f0a61db96",
+95 -255
View File
@@ -5,7 +5,7 @@
"platform": "retrodeck",
"display_name": "RetroDECK",
"version": "1.0",
"generated": "2026-10-06T10:11:17Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "",
"detect": [
{
@@ -237,10 +237,10 @@
}
}
],
"total_files": 7807,
"total_size": 8628195246,
"pack_files": 7809,
"pack_size": 8628195246,
"total_files": 7791,
"total_size": 8622202286,
"pack_files": 7793,
"pack_size": 8622202286,
"total_omitted": 1,
"omitted_files": [
{
@@ -16371,16 +16371,6 @@
"amiberry"
]
},
{
"dest": "bios/cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "bios/cd32ext.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
@@ -16391,16 +16381,6 @@
"amiberry"
]
},
{
"dest": "bios/cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"amiberry"
]
},
{
"dest": "bios/cdtvext.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -18251,16 +18231,6 @@
"clk"
]
},
{
"dest": "bios/MSX/MSX.ROM",
"sha1": "409e82adac40f6bdd18eb6c84e8b2fbdc7fb5498",
"sha256": "999564a371dd2fdf7fbe8d853e82a68d557c27b7d87417639b2fa17704b83f78",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX.ROM",
"cores": [
"clk"
]
},
{
"dest": "bios/MSX/msx-japanese.rom",
"sha1": "302afb5d8be26c758309ca3df611ae69cced2821",
@@ -18291,26 +18261,6 @@
"clk"
]
},
{
"dest": "bios/MSX/MSX2.ROM",
"sha1": "6103b39f1e38d1aa2d84b1c3219c44f1abb5436e",
"sha256": "4bc4ae85ca5f28246cd3e7b7e017d298ddd375603657f84ef2c7954bc2d9b919",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX2.ROM",
"cores": [
"clk"
]
},
{
"dest": "bios/MSX/MSX2EXT.ROM",
"sha1": "5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02",
"sha256": "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300",
"size": 16384,
"repo_path": "bios/Microsoft/MSX/MSX2EXT.ROM",
"cores": [
"clk"
]
},
{
"dest": "bios/MSX/fmpac.rom",
"sha1": "9d789166e3caf28e4742fe933d962e99618c633d",
@@ -31839,16 +31789,6 @@
"fceumm"
]
},
{
"dest": "bios/dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
"sha256": "15a9bc3aa59fa998c03bfe0b1cf6cb1958c1660e8e72b3d7195b653f44038407",
"size": 131072,
"repo_path": "bios/Sega/Dreamcast/dc_nvmem.bin",
"cores": [
"flycast"
]
},
{
"dest": "bios/dc/font.bin",
"sha1": "94c6ff701f79742ee23c0a028ac0be5aead77c73",
@@ -32569,26 +32509,6 @@
"geolith"
]
},
{
"dest": "bios/ROM",
"sha1": "e4fc7560b69d062cb2da5b1ffbe11cd1ca03cc37",
"sha256": "34cd454c6201bfd26839d6ace2ff6b1231e09d6279e49b4890311756dc3825fb",
"size": 131072,
"repo_path": "bios/Apple/Apple IIGS/ROM",
"cores": [
"gsplus"
]
},
{
"dest": "bios/c600.rom",
"sha1": "d4181c9f046aafc3fb326b381baac809d9e38d16",
"sha256": "de1e3e035878bab43d0af8fe38f5839c527e9548647036598ee6fe7ec74d2a7d",
"size": 256,
"repo_path": "bios/Apple/Apple II/AppleIIe_DiskII.rom",
"cores": [
"gsplus"
]
},
{
"dest": "bios/gzdoom.pk3",
"sha1": "810ed9968c5b3bdc2c348c9bc76e9f80fbe31308",
@@ -38249,16 +38169,6 @@
"mesen"
]
},
{
"dest": "bios/sgb_boot.bin",
"sha1": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de",
"sha256": "b60d493a7944ccf74c81f1e7b6bf38c2c7029e296648ea0e74cb22b10dd1fcb8",
"size": 256,
"repo_path": "bios/Nintendo/Super Game Boy/sgb_boot.bin",
"cores": [
"mesen-s"
]
},
{
"dest": "bios/dsp1.rom",
"sha1": "188d471fefea71eb53f0ee7064697ff0971b1014",
@@ -38339,16 +38249,6 @@
"mesence"
]
},
{
"dest": "bios/SufamiTurbo.sfc",
"sha1": "ef86ea192eed03d5c413fdbbfd46043be1d7a127",
"sha256": "edacb453da14f825f05d1134d6035f4bf034e55f7cfb97c70c4ee107eabc7342",
"size": 262144,
"repo_path": "bios/Nintendo/SuFami Turbo/STBIOS.bin",
"cores": [
"mesence"
]
},
{
"dest": "bios/ymf288_adpcm_rom.bin",
"sha1": "50b6c3e288eaa12ad275d4f323267bb72b0445df",
@@ -38379,16 +38279,6 @@
"mesence"
]
},
{
"dest": "bios/[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": [
"mesence"
]
},
{
"dest": "bios/[BIOS] Games Express CD Card (Japan).pce",
"sha1": "014881a959e045e00f4db8f52955200865d40280",
@@ -59529,16 +59419,6 @@
"mu"
]
},
{
"dest": "bios/Mupen64plus/IPL.n64",
"sha1": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
"sha256": "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_v12_JPN.bin",
"cores": [
"mupen64plus_next"
]
},
{
"dest": "bios/Mupen64plus/font.ttf",
"sha1": "1d7f6fa0c34e3d50be52c16a0816bebac309fe41",
@@ -60349,76 +60229,6 @@
"noods"
]
},
{
"dest": "bios/np2kai/2608_bd.wav",
"sha1": "0a56c142ef40cec50f3ee56a6e42d0029c9e2818",
"sha256": "802fb0ce40878e98335fc56d11eff6382982e9f09ebc83055b0a469df13f7900",
"size": 19192,
"repo_path": "bios/NEC/PC-98/2608_bd.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_sd.wav",
"sha1": "3c79663ef74c0b0439d13351326eb1c52a657008",
"sha256": "e5877cd1e4821cbbc9a450491a189032c82906c05f7fc9f2e48ee0d06b3ad18c",
"size": 15558,
"repo_path": "bios/NEC/PC-98/2608_sd.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_top.wav",
"sha1": "aa4a8f766a86b830687d5083fd3b9db0652f46fc",
"sha256": "d7bcf2da99a09589096e9bcee98da43303c588cca4e89b1a263adace52038cd3",
"size": 57016,
"repo_path": "bios/NEC/PC-98/2608_top.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_hh.wav",
"sha1": "12f676cef249b82480b6f19c454e234b435ca7b6",
"sha256": "216c81ff9a84af0dfbf21dd8df6827c25fca9fdeefe9bc8426f7a00d6ff2264e",
"size": 36722,
"repo_path": "bios/NEC/PC-98/2608_hh.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_tom.wav",
"sha1": "9513fb4a3f41e75a972a273a5104cbd834c1e2c5",
"sha256": "8e2c9ccfa8caff03dbd0d945d7c1d4de15292817409fe6fabda07f008f97dbc6",
"size": 23092,
"repo_path": "bios/NEC/PC-98/2608_tom.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_rim.wav",
"sha1": "c65592330c9dd84011151daed52f9aec926b7e56",
"sha256": "6f7d1996001c74452a9ca68b41a2767925b5b947c699096a8ad15bbc658af5a3",
"size": 5288,
"repo_path": "bios/NEC/PC-98/2608_rim.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/ide.rom",
"sha1": "0877ffb4b4d1c18283468be3579b72ed8c22e3ac",
"sha256": "c15220d1c448bfd3aa268ef8de41dce14a9b3ba90171aaae539d6d94721ff4e0",
"size": 8192,
"repo_path": "bios/NEC/PC-98/ide.rom",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
@@ -66045,46 +65855,6 @@
"qemu"
]
},
{
"dest": "bios/quasi88/n88sub.rom",
"sha1": "bb7103a0818850a039c67ff666a31ce49a8d516f",
"sha256": "9dc63118728a171ce2ed437ad38ef280b8b7bcf4a5a95fedf18946501b010456",
"size": 2048,
"repo_path": "bios/NEC/PC-98/N88SUB.ROM",
"cores": [
"quasi88"
]
},
{
"dest": "bios/quasi88/n88knj2.rom",
"sha1": "b86b932cb4ce34472e0fe0f01a4a4350e15dcb53",
"sha256": "5c31df4b892e730d1ba7f80fd5583e838ae5583ba52408088da74b9ff422e5fe",
"size": 131072,
"repo_path": "bios/NEC/PC-98/n88knj2.rom",
"cores": [
"quasi88"
]
},
{
"dest": "bios/quasi88/n88jisho.rom",
"sha1": "deef0cc2a9734ba891a6d6c022aa70ffc66f783e",
"sha256": "25773eaee3af617db08d26602d8f4cdc7d42ed946ca510d3b1d96fe1645af63c",
"size": 524288,
"repo_path": "bios/NEC/PC-98/n88jisho.rom",
"cores": [
"quasi88"
]
},
{
"dest": "bios/quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "bios/quasi88/font2.rom",
"sha1": "0a056b6d28b4b9e29a2d255bdaa25c24f176cf1e",
@@ -66547,6 +66317,16 @@
"same_cdi"
]
},
{
"dest": "bios/sgb_boot.bin",
"sha1": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de",
"sha256": "b60d493a7944ccf74c81f1e7b6bf38c2c7029e296648ea0e74cb22b10dd1fcb8",
"size": 256,
"repo_path": "bios/Nintendo/Super Game Boy/sgb_boot.bin",
"cores": [
"sameboy"
]
},
{
"dest": "bios/agb_boot.bin",
"sha1": "4ed1d95c9c682a78698f235ce9e9bb3b62bccf9f",
@@ -67377,26 +67157,6 @@
"sdlpal"
]
},
{
"dest": "bios/SkyEmu/cgb_boot.bin",
"sha1": "80e3ee82416c1a74827a5459fde32f04bf51a1f6",
"sha256": "119470a9cf9f0d85f0c7ba5a6cadffeec07eb9862719086d1a9808189605e388",
"size": 2304,
"repo_path": "bios/Nintendo/Game Boy Color/cgb_boot.bin",
"cores": [
"skyemu"
]
},
{
"dest": "bios/SkyEmu/dmg_rom.bin",
"sha1": "4ed31ec6b0b175bb109c0eb5fd3d193da823339f",
"sha256": "cf053eccb4ccafff9e67339d4e78e98dce7d1ed59be819d2a1ba2232c6fce1c7",
"size": 256,
"repo_path": "bios/Nintendo/Game Boy/GB_dmg.bin",
"cores": [
"skyemu"
]
},
{
"dest": "bios/SkyEmu/nds7.bin",
"sha1": "24f67bdea115a2c847c8813a262502ee1607b7df",
@@ -71483,6 +71243,66 @@
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_bd.wav",
"sha1": "0a56c142ef40cec50f3ee56a6e42d0029c9e2818",
"sha256": "802fb0ce40878e98335fc56d11eff6382982e9f09ebc83055b0a469df13f7900",
"size": 19192,
"repo_path": "bios/NEC/PC-98/2608_bd.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_sd.wav",
"sha1": "3c79663ef74c0b0439d13351326eb1c52a657008",
"sha256": "e5877cd1e4821cbbc9a450491a189032c82906c05f7fc9f2e48ee0d06b3ad18c",
"size": 15558,
"repo_path": "bios/NEC/PC-98/2608_sd.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_top.wav",
"sha1": "aa4a8f766a86b830687d5083fd3b9db0652f46fc",
"sha256": "d7bcf2da99a09589096e9bcee98da43303c588cca4e89b1a263adace52038cd3",
"size": 57016,
"repo_path": "bios/NEC/PC-98/2608_top.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_hh.wav",
"sha1": "12f676cef249b82480b6f19c454e234b435ca7b6",
"sha256": "216c81ff9a84af0dfbf21dd8df6827c25fca9fdeefe9bc8426f7a00d6ff2264e",
"size": 36722,
"repo_path": "bios/NEC/PC-98/2608_hh.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_tom.wav",
"sha1": "9513fb4a3f41e75a972a273a5104cbd834c1e2c5",
"sha256": "8e2c9ccfa8caff03dbd0d945d7c1d4de15292817409fe6fabda07f008f97dbc6",
"size": 23092,
"repo_path": "bios/NEC/PC-98/2608_tom.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/np2kai/2608_rim.wav",
"sha1": "c65592330c9dd84011151daed52f9aec926b7e56",
"sha256": "6f7d1996001c74452a9ca68b41a2767925b5b947c699096a8ad15bbc658af5a3",
"size": 5288,
"repo_path": "bios/NEC/PC-98/2608_rim.wav",
"cores": [
"np2kai"
]
},
{
"dest": "bios/PPSSPP/ppge_atlas.zim",
"sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a",
@@ -71613,6 +71433,16 @@
"quasi88"
]
},
{
"dest": "bios/quasi88/n88jisho.rom",
"sha1": "deef0cc2a9734ba891a6d6c022aa70ffc66f783e",
"sha256": "25773eaee3af617db08d26602d8f4cdc7d42ed946ca510d3b1d96fe1645af63c",
"size": 524288,
"repo_path": "bios/NEC/PC-98/n88jisho.rom",
"cores": [
"quasi88"
]
},
{
"dest": "bios/same_cdi/bios/cdimono1.zip",
"sha1": "36771315484a1f57227e23278df91b84b5d7d3be",
@@ -71633,6 +71463,16 @@
"same_cdi"
]
},
{
"dest": "bios/SkyEmu/cgb_boot.bin",
"sha1": "80e3ee82416c1a74827a5459fde32f04bf51a1f6",
"sha256": "119470a9cf9f0d85f0c7ba5a6cadffeec07eb9862719086d1a9808189605e388",
"size": 2304,
"repo_path": "bios/Nintendo/Game Boy Color/cgb_boot.bin",
"cores": [
"skyemu"
]
},
{
"dest": "bios/SkyEmu/gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
+32 -120
View File
@@ -5,7 +5,7 @@
"platform": "retropie",
"display_name": "RetroPie",
"version": "1.0",
"generated": "2026-10-06T06:50:25Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "BIOS",
"detect": [
{
@@ -15,10 +15,10 @@
}
],
"standalone_copies": [],
"total_files": 9899,
"total_size": 8731269966,
"pack_files": 12542,
"pack_size": 8771803130,
"total_files": 9890,
"total_size": 8732256943,
"pack_files": 12533,
"pack_size": 8772790107,
"total_omitted": 8,
"omitted_files": [
{
@@ -1399,14 +1399,6 @@
"repo_path": "bios/NEC/PC-98/n88knj2.rom",
"cores": null
},
{
"dest": "quasi88/N88SUB.ROM",
"sha1": "bb7103a0818850a039c67ff666a31ce49a8d516f",
"sha256": "9dc63118728a171ce2ed437ad38ef280b8b7bcf4a5a95fedf18946501b010456",
"size": 2048,
"repo_path": "bios/NEC/PC-98/N88SUB.ROM",
"cores": null
},
{
"dest": "quasi88/N88EXT0.ROM",
"sha1": "d239c26ad7ac5efac6e947b0e9549b1534aa970d",
@@ -1947,10 +1939,10 @@
},
{
"dest": "BS-X.bin",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"sha1": "604556b2e62860af18db5a77f2956ebc75450020",
"sha256": "3ce321496edc5d77038de2034eb3fb354d7724afd0bc7fd0319f3eb5d57b984d",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"repo_path": "bios/Nintendo/SNES/Satellaview_BS-X.sfc",
"cores": null
},
{
@@ -4195,10 +4187,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"size": 2690355,
"repo_path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"cores": null
},
{
@@ -4579,16 +4571,6 @@
"amiberry"
]
},
{
"dest": "cd32.rom",
"sha1": "3525be8887f79b5929e017b42380a79edfee542d",
"sha256": "3586355514947b5ab48c8755dc004fab94f188b19e7bce491c8f9693499e830b",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/amiga-os-310-cd32.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cd32ext.rom",
"sha1": "5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f",
@@ -4599,16 +4581,6 @@
"amiberry"
]
},
{
"dest": "cdtv.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
"sha256": "6e48bb768539e8dae352553e5fdfa860682edaf9e26b105fadd8e26103171765",
"size": 262144,
"repo_path": "bios/Commodore/Amiga/amiga-ext-130-cdtv.rom",
"cores": [
"amiberry"
]
},
{
"dest": "cdtvext.rom",
"sha1": "7ba40ffa17e500ed9fed041f3424bd81d9c907be",
@@ -41973,16 +41945,6 @@
"storage": "release",
"release_asset": "easyrpg.soundfont"
},
{
"dest": "system.rom",
"sha1": "1f5f7013f18c08ff50d7942e76c4fbd782412414",
"sha256": "863df0975a305110a7da4591b704e8f5dc7302f03c63603aa2dcd03e84c36ff9",
"size": 65536,
"repo_path": "bios/IBM/PC/ibmpcjr/bios.rom",
"cores": [
"dosbox-staging"
]
},
{
"dest": "IBM PC Cassette BASIC ROM",
"sha1": "07449ebca18f979b9ab748582b736e402f2bf940",
@@ -42023,16 +41985,6 @@
"dosbox_pure"
]
},
{
"dest": "system/nds_bios_arm9.bin",
"sha1": "bfaac75f101c135e32e2aaf541de6b1be4c8c62d",
"sha256": "1693983a7707ae394786fa526c0552457888a51d4e410d715ef07acd5a540555",
"size": 4096,
"repo_path": "bios/Nintendo/DS/NDS_Bios9.bin",
"cores": [
"drastic"
]
},
{
"dest": "system/drastic_bios_arm9.bin",
"sha1": "4dc29a1560e1cbd7afef1998f7a6c25c5db1cc3a",
@@ -42043,16 +41995,6 @@
"drastic"
]
},
{
"dest": "system/nds_bios_arm7.bin",
"sha1": "24f67bdea115a2c847c8813a262502ee1607b7df",
"sha256": "ba65f690eb04ec92db67c0e299e21ad71de087d6d5de8a9cb17a62eaab563c17",
"size": 16384,
"repo_path": "bios/Nintendo/DS/NDS_Bios7.bin",
"cores": [
"drastic"
]
},
{
"dest": "system/drastic_bios_arm7.bin",
"sha1": "31ae916c85d012582c7cdf47de6943ac6778253b",
@@ -42063,16 +42005,6 @@
"drastic"
]
},
{
"dest": "system/nds_firmware.bin",
"sha1": "87dae2500e889737af51f4a5b5845770a62482f5",
"sha256": "330c59198faec64b40e96cc875b422d340fc9ab1a9e32e9bd9b7baa1dbe6ba7f",
"size": 262144,
"repo_path": "bios/Nintendo/DS/NDS_Lite_Firmware.bin",
"cores": [
"drastic"
]
},
{
"dest": "game_database.xml",
"sha1": "b0ae5480c61bdb701b9a444a1a81c4970db06d77",
@@ -62851,16 +62783,6 @@
"mesence"
]
},
{
"dest": "SufamiTurbo.sfc",
"sha1": "ef86ea192eed03d5c413fdbbfd46043be1d7a127",
"sha256": "edacb453da14f825f05d1134d6035f4bf034e55f7cfb97c70c4ee107eabc7342",
"size": 262144,
"repo_path": "bios/Nintendo/SuFami Turbo/STBIOS.bin",
"cores": [
"mesence"
]
},
{
"dest": "ymf288_adpcm_rom.bin",
"sha1": "50b6c3e288eaa12ad275d4f323267bb72b0445df",
@@ -62891,26 +62813,6 @@
"mesence"
]
},
{
"dest": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Games Express CD Card (Japan).pce",
"sha1": "014881a959e045e00f4db8f52955200865d40280",
"sha256": "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d",
"size": 32768,
"repo_path": "bios/NEC/PC Engine/gecard.pce",
"cores": [
"mesence"
]
},
{
"dest": "Satellaview/BSX0120-0.bin",
"sha1": "f51c52bb26f2546fb167ab1084a56457475ff0a0",
@@ -90667,16 +90569,6 @@
"qemu"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/font2.rom",
"sha1": "0a056b6d28b4b9e29a2d255bdaa25c24f176cf1e",
@@ -95815,6 +95707,26 @@
"nekop2"
]
},
{
"dest": "resources/GameIndex.yaml",
"sha1": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"sha256": "6531239315d759d0588985b31576696d2672c1a0e192633ea2d4f3d011bfdb37",
"size": 2683897,
"repo_path": "bios/Sony/PlayStation 2/resources/.variants/GameIndex.yaml.fc3c1420",
"cores": [
"pcsx2"
]
},
{
"dest": "quasi88/font.rom",
"sha1": "84b90f69671d4b72e8f219e1fe7cd667e976cf7f",
"sha256": "3227087599afd9304f260cdeb3bf1533e39e425cf444ceb3c24f55f561e71a0c",
"size": 2048,
"repo_path": "bios/NEC/PC-88/font.rom",
"cores": [
"quasi88"
]
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
+15 -35
View File
@@ -5,7 +5,7 @@
"platform": "rocknix",
"display_name": "ROCKNIX",
"version": "1.0",
"generated": "2026-10-06T06:50:25Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "bios",
"detect": [
{
@@ -17,10 +17,10 @@
}
],
"standalone_copies": [],
"total_files": 5804,
"total_size": 6570724300,
"pack_files": 5806,
"pack_size": 6570724300,
"total_files": 5802,
"total_size": 6570592972,
"pack_files": 5804,
"pack_size": 6570592972,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -15720,16 +15720,6 @@
"fceumm"
]
},
{
"dest": "dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
"sha256": "15a9bc3aa59fa998c03bfe0b1cf6cb1958c1660e8e72b3d7195b653f44038407",
"size": 131072,
"repo_path": "bios/Sega/Dreamcast/dc_nvmem.bin",
"cores": [
"flycast"
]
},
{
"dest": "dc/font.bin",
"sha1": "94c6ff701f79742ee23c0a028ac0be5aead77c73",
@@ -53058,26 +53048,6 @@
"sdlpal"
]
},
{
"dest": "SkyEmu/cgb_boot.bin",
"sha1": "80e3ee82416c1a74827a5459fde32f04bf51a1f6",
"sha256": "119470a9cf9f0d85f0c7ba5a6cadffeec07eb9862719086d1a9808189605e388",
"size": 2304,
"repo_path": "bios/Nintendo/Game Boy Color/cgb_boot.bin",
"cores": [
"skyemu"
]
},
{
"dest": "SkyEmu/dmg_rom.bin",
"sha1": "4ed31ec6b0b175bb109c0eb5fd3d193da823339f",
"sha256": "cf053eccb4ccafff9e67339d4e78e98dce7d1ed59be819d2a1ba2232c6fce1c7",
"size": 256,
"repo_path": "bios/Nintendo/Game Boy/GB_dmg.bin",
"cores": [
"skyemu"
]
},
{
"dest": "SkyEmu/nds7.bin",
"sha1": "24f67bdea115a2c847c8813a262502ee1607b7df",
@@ -56110,6 +56080,16 @@
"kronos"
]
},
{
"dest": "SkyEmu/cgb_boot.bin",
"sha1": "80e3ee82416c1a74827a5459fde32f04bf51a1f6",
"sha256": "119470a9cf9f0d85f0c7ba5a6cadffeec07eb9862719086d1a9808189605e388",
"size": 2304,
"repo_path": "bios/Nintendo/Game Boy Color/cgb_boot.bin",
"cores": [
"skyemu"
]
},
{
"dest": "SkyEmu/gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
+538 -47
View File
@@ -5,7 +5,7 @@
"platform": "romm",
"display_name": "RomM",
"version": "1.0",
"generated": "2026-10-06T10:11:17Z",
"generated": "2026-10-09T20:41:14Z",
"base_destination": "bios",
"detect": [
{
@@ -15,12 +15,543 @@
}
],
"standalone_copies": [],
"total_files": 1089,
"total_size": 1586368550,
"pack_files": 1091,
"pack_size": 1586368550,
"total_omitted": 0,
"omitted_files": [],
"total_files": 1085,
"total_size": 1582092326,
"pack_files": 1087,
"pack_size": 1582092326,
"total_omitted": 53,
"omitted_files": [
{
"dest": "CM32LN_CONTROL.ROM",
"name": "CM32LN_CONTROL.ROM",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"DOSBox Pure"
]
},
{
"dest": "CM32L_CONTROL.ROM",
"name": "CM32L_CONTROL.ROM",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"DOSBox Pure"
]
},
{
"dest": "CM32L_PCM.ROM",
"name": "CM32L_PCM.ROM",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"DOSBox Pure"
]
},
{
"dest": "DOSBOX.SF2",
"name": "DOSBOX.SF2",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"DOSBox Pure"
]
},
{
"dest": "JiffyDOS_1541-II.bin",
"name": "JiffyDOS_1541-II.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x128"
]
},
{
"dest": "JiffyDOS_1571_repl310654.bin",
"name": "JiffyDOS_1571_repl310654.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x128"
]
},
{
"dest": "JiffyDOS_1581.bin",
"name": "JiffyDOS_1581.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x128"
]
},
{
"dest": "JiffyDOS_C128.bin",
"name": "JiffyDOS_C128.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x128"
]
},
{
"dest": "JiffyDOS_C64.bin",
"name": "JiffyDOS_C64.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x128"
]
},
{
"dest": "JiffyDOS_SX-64.bin",
"name": "JiffyDOS_SX-64.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x64"
]
},
{
"dest": "MT32_CONTROL.ROM",
"name": "MT32_CONTROL.ROM",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"DOSBox Pure"
]
},
{
"dest": "MT32_PCM.ROM",
"name": "MT32_PCM.ROM",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"DOSBox Pure"
]
},
{
"dest": "[BIOS] Atari Jaguar CD (World).j64",
"name": "[BIOS] Atari Jaguar CD (World).j64",
"system": "atari-jaguar",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Virtual Jaguar"
]
},
{
"dest": "[BIOS] Atari Jaguar Developer CD (World).j64",
"name": "[BIOS] Atari Jaguar Developer CD (World).j64",
"system": "atari-jaguar",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Virtual Jaguar"
]
},
{
"dest": "acpsx.zip",
"name": "acpsx.zip",
"system": "zn1-acclaim",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "chargen-901460-02.bin",
"name": "chargen-901460-02.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE xvic"
]
},
{
"dest": "cpzn1.zip",
"name": "cpzn1.zip",
"system": "zn1-capcom",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "cpzn2.zip",
"name": "cpzn2.zip",
"system": "zn2-capcom",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "cvs.zip",
"name": "cvs.zip",
"system": "century-cvs",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "fbalpha2012/hiscore.dat",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"FB Alpha 2012 CPS-1"
]
},
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Virtual Jaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Virtual Jaguar"
]
},
{
"dest": "kernal-901227-01.bin",
"name": "kernal-901227-01.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x64"
]
},
{
"dest": "kernal-901227-02.bin",
"name": "kernal-901227-02.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE x64"
]
},
{
"dest": "kernal.901486-02.bin",
"name": "kernal.901486-02.bin",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"VICE xvic"
]
},
{
"dest": "konamigx.zip",
"name": "konamigx.zip",
"system": "konami-gx",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "kvb1.wav",
"name": "kvb1.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "kvb2.wav",
"name": "kvb2.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "kvb3.wav",
"name": "kvb3.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "kvs1.wav",
"name": "kvs1.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "kvs2.wav",
"name": "kvs2.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "kvs3.wav",
"name": "kvs3.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "kvshared.wav",
"name": "kvshared.wav",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"Stella 2014"
]
},
{
"dest": "mame2003-plus/cheat.dat",
"name": "cheat.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003-Plus"
]
},
{
"dest": "mame2003-plus/hiscore.dat",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003-Plus"
]
},
{
"dest": "mame2003-plus/history.dat",
"name": "history.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003-Plus"
]
},
{
"dest": "mame2003-plus/mameinfo.dat",
"name": "mameinfo.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003-Plus"
]
},
{
"dest": "mame2003/cheat.dat",
"name": "cheat.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "mame2003/hiscore.dat",
"name": "hiscore.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "mame2003/history.dat",
"name": "history.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "mame2003/mameinfo.dat",
"name": "mameinfo.dat",
"system": "",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "megaplay.zip",
"name": "megaplay.zip",
"system": "sega-megaplay",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "megatech.zip",
"name": "megatech.zip",
"system": "sega-megatech",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "nss.zip",
"name": "nss.zip",
"system": "nintendo-nss",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "playch10.zip",
"name": "playch10.zip",
"system": "nintendo-playch10",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "same_cdi/bios/cdi490a.zip",
"name": "cdi490a.zip",
"system": "cdi",
"required": false,
"reason": "no_platform_slug",
"cores": [
"SAME CDi"
]
},
{
"dest": "same_cdi/bios/cdi910.zip",
"name": "cdi910.zip",
"system": "cdi",
"required": false,
"reason": "no_platform_slug",
"cores": [
"SAME CDi"
]
},
{
"dest": "same_cdi/bios/cdimono1.zip",
"name": "cdimono1.zip",
"system": "cdi",
"required": true,
"reason": "no_platform_slug",
"cores": [
"SAME CDi"
]
},
{
"dest": "same_cdi/bios/cdimono2.zip",
"name": "cdimono2.zip",
"system": "cdi",
"required": false,
"reason": "no_platform_slug",
"cores": [
"SAME CDi"
]
},
{
"dest": "sncwgltd.zip",
"name": "sncwgltd.zip",
"system": "zn1-video-system",
"required": false,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "stvbios.zip",
"name": "stvbios.zip",
"system": "sega-stv",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "taitofx1.zip",
"name": "taitofx1.zip",
"system": "zn1-taito",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
},
{
"dest": "tps.zip",
"name": "tps.zip",
"system": "zn1-tecmo",
"required": true,
"reason": "no_platform_slug",
"cores": [
"MAME 2003"
]
}
],
"files": [
{
"dest": "3do/3do_arcade_saot.bin",
@@ -3740,16 +4271,6 @@
"clk"
]
},
{
"dest": "acpc/MSX/MSX.ROM",
"sha1": "409e82adac40f6bdd18eb6c84e8b2fbdc7fb5498",
"sha256": "999564a371dd2fdf7fbe8d853e82a68d557c27b7d87417639b2fa17704b83f78",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX.ROM",
"cores": [
"clk"
]
},
{
"dest": "acpc/MSX/msx-japanese.rom",
"sha1": "302afb5d8be26c758309ca3df611ae69cced2821",
@@ -3790,26 +4311,6 @@
"clk"
]
},
{
"dest": "acpc/MSX/MSX2.ROM",
"sha1": "6103b39f1e38d1aa2d84b1c3219c44f1abb5436e",
"sha256": "4bc4ae85ca5f28246cd3e7b7e017d298ddd375603657f84ef2c7954bc2d9b919",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/MSX2.ROM",
"cores": [
"clk"
]
},
{
"dest": "acpc/MSX/MSX2EXT.ROM",
"sha1": "5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02",
"sha256": "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300",
"size": 16384,
"repo_path": "bios/Microsoft/MSX/MSX2EXT.ROM",
"cores": [
"clk"
]
},
{
"dest": "acpc/MSX/fmpac.rom",
"sha1": "9d789166e3caf28e4742fe933d962e99618c633d",
@@ -5020,16 +5521,6 @@
"minivmac"
]
},
{
"dest": "64dd/Mupen64plus/IPL.n64",
"sha1": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
"sha256": "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_v12_JPN.bin",
"cores": [
"mupen64plus_next"
]
},
{
"dest": "64dd/Mupen64plus/font.ttf",
"sha1": "1d7f6fa0c34e3d50be52c16a0816bebac309fe41",
+4
View File
@@ -805,6 +805,10 @@ platforms:
target_source: null
install:
detect:
# installer.iss: DefaultDirName=C:\RetroBat, the first place to look.
- os: windows
method: path_exists
path: 'C:\RetroBat\bios'
- os: windows
method: path_exists
path: '%USERPROFILE%\RetroBat\bios'
+9 -37
View File
@@ -163,22 +163,15 @@ shared_groups:
# QUASI88 searches "quasi88/" subfolder first
# ref: quasi88-libretro/src/LIBRETRO/libretro.c:463 — snprintf(..., "%s%cquasi88%c%s", ...)
quasi88:
- name: N88.ROM
destination: quasi88/N88.ROM
required: true
md5: "4f984e04a99d56c4cfe36115415d6eb8"
- name: N88SUB.ROM
destination: quasi88/N88SUB.ROM
required: true
md5: "793f86784e5608352a5d7f03f03e0858"
- name: N88N.ROM
destination: quasi88/N88N.ROM
required: true
md5: "2ff07b8769367321128e03924af668a0"
- name: N88KNJ1.ROM
destination: quasi88/N88KNJ1.ROM
required: true
md5: "d81c6d5d7ad1a4bbbd6ae22a01257603"
# Only what System.dat does not declare for nec-pc-88: the mkII SR
# extension ROMs that match N88.ROM (MAME pc8801mk2sr: 710a63ec, c0bd2aa6,
# af2b6efa, 7713c519). The core reads N88EXTn.ROM before n88_n.rom
# (src/LIBRETRO/libretro.c:109-112), so they win over the n88_n.rom dumps
# System.dat pairs with that main ROM, which come from other models (n88_0
# 61984bab is the original PC-8801's). N88SUB.ROM, N88.ROM, N88N.ROM and
# N88KNJ1.ROM are the native disk.rom, n88.rom, n88n.rom and n88knj1.rom
# under the other spelling the core tries: aliases in the profile, not
# files of their own here, or the pack carried disk.rom twice.
- name: N88EXT0.ROM
destination: quasi88/N88EXT0.ROM
required: true
@@ -195,27 +188,6 @@ shared_groups:
destination: quasi88/N88EXT3.ROM
required: true
md5: "fc4b76a402ba501e6ba6de4b3e8b4273"
# N88EXT0-3.ROM above are the mkII SR extension ROMs that match N88.ROM
# (MAME pc8801mk2sr: 710a63ec, c0bd2aa6, af2b6efa, 7713c519). The core
# reads N88EXTn.ROM before n88_n.rom (src/LIBRETRO/libretro.c:109-112),
# so they win over the n88_n.rom dumps System.dat pairs with that main ROM,
# which come from other models (n88_0 61984bab is the original PC-8801's).
- name: n88_0.rom
destination: quasi88/n88_0.rom
required: false
md5: "e28fe3f520bea594350ea8fb00395370"
- name: n88_1.rom
destination: quasi88/n88_1.rom
required: false
md5: "a8e298da7ac947669bcb1ff25cee0a83"
- name: n88_2.rom
destination: quasi88/n88_2.rom
required: false
md5: "9d03154fd9abfc28c4e6d4dc705e6e23"
- name: n88_3.rom
destination: quasi88/n88_3.rom
required: false
md5: "e1791f8154f1cdf22b576a1a365b6e1f"
# Sinclair ZX Spectrum — Fuse core (Pentagon/Scorpion ROMs)
# VERIFIED: fuse-libretro/src/compat/paths.c compat_get_next_path()
+105 -24
View File
@@ -4,55 +4,136 @@
"title": "RetroBIOS content database",
"type": "object",
"additionalProperties": false,
"required": ["schema_version", "generated_at", "total_files", "total_size", "files", "indexes"],
"required": [
"schema_version",
"generated_at",
"total_files",
"total_size",
"files",
"indexes"
],
"properties": {
"schema_version": { "const": 1 },
"generated_at": { "type": "string", "format": "date-time" },
"total_files": { "type": "integer", "minimum": 0 },
"total_size": { "type": "integer", "minimum": 0 },
"schema_version": {
"const": 1
},
"generated_at": {
"type": "string",
"format": "date-time"
},
"total_files": {
"type": "integer",
"minimum": 0
},
"total_size": {
"type": "integer",
"minimum": 0
},
"bios_dir": {
"type": "string",
"description": "Directory the files were scanned from; _mame_clones.json sits beside it."
},
"files": {
"type": "object",
"propertyNames": { "pattern": "^[a-f0-9]{40}$" },
"propertyNames": {
"pattern": "^[a-f0-9]{40}$"
},
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": ["path", "name", "size", "sha1", "md5", "sha256", "crc32", "adler32"],
"required": [
"path",
"name",
"size",
"sha1",
"md5",
"sha256",
"crc32",
"adler32"
],
"properties": {
"path": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1 },
"size": { "type": "integer", "minimum": 0 },
"sha1": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"md5": { "type": "string", "pattern": "^[a-f0-9]{32}$" },
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"crc32": { "type": "string", "pattern": "^[a-f0-9]{8}$" },
"adler32": { "type": "string", "pattern": "^[a-f0-9]{8}$" },
"provenance": { "type": "object" }
"path": {
"type": "string",
"minLength": 1
},
"name": {
"type": "string",
"minLength": 1
},
"size": {
"type": "integer",
"minimum": 0
},
"sha1": {
"type": "string",
"pattern": "^[a-f0-9]{40}$"
},
"md5": {
"type": "string",
"pattern": "^[a-f0-9]{32}$"
},
"sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"crc32": {
"type": "string",
"pattern": "^[a-f0-9]{8}$"
},
"adler32": {
"type": "string",
"pattern": "^[a-f0-9]{8}$"
},
"provenance": {
"type": "object"
}
}
}
},
"indexes": {
"type": "object",
"additionalProperties": false,
"required": ["by_md5", "by_name", "by_crc32", "by_sha256", "by_path_suffix"],
"required": [
"by_md5",
"by_name",
"by_crc32",
"by_sha256",
"by_path_suffix"
],
"properties": {
"by_md5": { "$ref": "#/$defs/hashIndex" },
"by_crc32": { "$ref": "#/$defs/hashIndex" },
"by_sha256": { "$ref": "#/$defs/hashIndex" },
"by_name": { "$ref": "#/$defs/listIndex" },
"by_path_suffix": { "$ref": "#/$defs/listIndex" }
"by_md5": {
"$ref": "#/$defs/hashIndex"
},
"by_crc32": {
"$ref": "#/$defs/hashIndex"
},
"by_sha256": {
"$ref": "#/$defs/hashIndex"
},
"by_name": {
"$ref": "#/$defs/listIndex"
},
"by_path_suffix": {
"$ref": "#/$defs/listIndex"
}
}
}
},
"$defs": {
"hashIndex": {
"type": "object",
"additionalProperties": { "type": "string", "pattern": "^[a-f0-9]{40}$" }
"additionalProperties": {
"type": "string",
"pattern": "^[a-f0-9]{40}$"
}
},
"listIndex": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"items": {
"type": "string",
"pattern": "^[a-f0-9]{40}$"
},
"minItems": 1,
"uniqueItems": true
}
+2 -1
View File
@@ -129,7 +129,8 @@
"hash_mismatch",
"not_found",
"external",
"user_provided"
"user_provided",
"no_platform_slug"
]
},
"cores": {
+30 -6
View File
@@ -43,11 +43,18 @@ def write_if_changed(path: str, content: str, normalize=None) -> bool:
)
if _strip_timestamps(before) == _strip_timestamps(after):
return False
# Truncate-then-write leaves a half-written artifact behind an interrupt,
# and every generator in the repo funnels through here: a partial
# database.json or README.md is committed-looking and silently wrong.
# The scratch file sits beside the target so the rename stays on one
# filesystem, which is what makes it atomic.
write_text_atomic(path, content)
return True
def write_text_atomic(path: str, content: str) -> None:
"""Write a whole file or nothing.
Truncate-then-write leaves a half-written artifact behind an interrupt:
a partial database.json, manifest or README.md is committed-looking and
silently wrong. The scratch file sits beside the target so the rename
stays on one filesystem, which is what makes it atomic.
"""
directory = os.path.dirname(os.path.abspath(path))
handle, scratch = tempfile.mkstemp(
dir=directory, prefix=f".{os.path.basename(path)}.", suffix=".tmp"
@@ -60,7 +67,24 @@ def write_if_changed(path: str, content: str, normalize=None) -> bool:
with contextlib.suppress(OSError):
os.unlink(scratch)
raise
return True
def copy_file_atomic(source: str, path: str) -> None:
"""Copy a file into place whole or not at all, metadata included."""
import shutil
directory = os.path.dirname(os.path.abspath(path))
handle, scratch = tempfile.mkstemp(
dir=directory, prefix=f".{os.path.basename(path)}.", suffix=".tmp"
)
os.close(handle)
try:
shutil.copy2(source, scratch)
os.replace(scratch, path)
except BaseException:
with contextlib.suppress(OSError):
os.unlink(scratch)
raise
def _strip_timestamps(text: str) -> str:
"""Remove known timestamp patterns for content comparison."""
+12 -4
View File
@@ -152,12 +152,13 @@ def print_report(report: dict) -> None:
print(f"\nSummary: {', '.join(parts)}")
def update_changed(report: dict) -> None:
"""Refresh entries that have changed."""
def update_changed(report: dict) -> list[str]:
"""Refresh entries that have changed; the keys whose refresh failed."""
failed: list[str] = []
for e in report.get("entries", []):
if e["status"] == "UPDATED" and e.get("key"):
log.info("refreshing %s ...", e["key"])
subprocess.run(
result = subprocess.run(
[
sys.executable,
"scripts/refresh_data_dirs.py",
@@ -167,6 +168,10 @@ def update_changed(report: dict) -> None:
],
check=False,
)
if result.returncode != 0:
log.error("refresh of %s failed (exit %d)", e["key"], result.returncode)
failed.append(e["key"])
return failed
def main() -> None:
@@ -194,7 +199,10 @@ def main() -> None:
print_report(report)
if args.update:
update_changed(report)
failed = update_changed(report)
if failed:
print(f"ERROR: refresh failed for {', '.join(failed)}", file=sys.stderr)
sys.exit(1)
# Unreachable upstream means the freshness question was not answered, and
# a zero exit says it was answered "fresh".
+113 -40
View File
@@ -489,6 +489,51 @@ def _owner_tokens(file_entry: dict) -> set[str]:
return tokens
def _foreign_tree(path: str, owners: set[str]) -> bool:
"""Whether a path lives in the tree of an emulator that is not the asker.
bios/Other/<emulator>/ holds what one emulator ships. A same-named file
found there answers that emulator's entry, not another's: EmuCoreX's
unlock.wav was served from NetherSX2's tree, and upstream PCSX2's
GameIndex.yaml from EmuCoreX's, while the system directory held a copy.
"""
parts = path.split("/")
if not owners or len(parts) < 4 or parts[0] != "bios" or parts[1] != "Other":
return False
return not (owners & _affinity_tokens(parts[2]))
def _rank_tail_candidates(
candidates: list[str], start: int, tail: str, files_db: dict, owners: set[str]
) -> list[str]:
"""Order the files a destination tail designates, the likeliest first.
A shortened tail found only in another emulator's tree is that
emulator's asset, not evidence about this entry. A full tail claimed
twice goes to the file whose path carries the least on top of the
destination, then to the owner's own copy (super3's Games.xml was served
to Supermodel-Dojo by index order alone), then to any copy outside
another emulator's tree (upstream PCSX2 took EmuCoreX's GameIndex.yaml
over the system directory's).
"""
def path_of(sha1: str) -> str:
return files_db.get(sha1, {}).get("path", "")
if start:
candidates = [h for h in candidates if not _foreign_tree(path_of(h), owners)]
if len(candidates) < 2:
return candidates
depth = len(tail.split("/"))
return sorted(
candidates,
key=lambda h: (
len(path_of(h).split("/")) - depth,
not owners & _affinity_tokens(path_of(h).rsplit("/", 1)[0]),
_foreign_tree(path_of(h), owners),
),
)
def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]:
"""Same-named candidates, the likeliest first.
@@ -507,14 +552,15 @@ def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str
owners = _owner_tokens(file_entry)
sized = any(file_entry.get(k) for k in ("size", "min_size", "max_size"))
def score(path: str) -> tuple[int, bool, int]:
def score(path: str) -> tuple[int, bool, bool, int]:
fits = 0
if sized and os.path.exists(path):
fits = int(size_fits(file_entry, os.path.getsize(path)))
tokens = _affinity_tokens(path.rsplit("/", 1)[0])
# The owner's own copy ranks above any shared tree segment: NetherSX2's
# shaders/common/ beat armsx2's own fxaa.fx on two directory tokens.
return fits, bool(owners & tokens), len(wanted & tokens)
# Another emulator's own tree ranks below everything else.
return fits, bool(owners & tokens), not _foreign_tree(path, owners | wanted), len(wanted & tokens)
return sorted(paths, key=score, reverse=True)
@@ -716,9 +762,14 @@ def resolve_local_file(
# stops describing this file and starts describing whichever emulator
# happens to store the same asset tree.
hint_parts = dest_hint.split("/")
# The asking profile and the destination's own directories both name
# whose tree a copy may come from.
owners = _owner_tokens(file_entry) | _affinity_tokens("/".join(hint_parts[:-1]))
for start in range(min(2, len(hint_parts) - 1)):
tail = "/".join(hint_parts[start:])
candidates = by_path_suffix.get(tail, [])
candidates = _rank_tail_candidates(
by_path_suffix.get(tail, []), start, tail, files_db, owners
)
if start and len(candidates) > 1:
continue
# The index names the file this destination designates. Not on
@@ -729,20 +780,6 @@ def resolve_local_file(
for h in candidates
):
own_file_absent = True
if len(candidates) > 1:
depth = len(tail.split("/"))
# At equal depth the owner's own copy: super3's Games.xml was
# served to Supermodel-Dojo by index order alone.
owners = _owner_tokens(file_entry)
candidates = sorted(
candidates,
key=lambda h: (
len(files_db.get(h, {}).get("path", "").split("/")) - depth,
not owners & _affinity_tokens(
files_db.get(h, {}).get("path", "").rsplit("/", 1)[0]
),
),
)
for match_sha1 in candidates:
if match_sha1 not in files_db:
continue
@@ -875,7 +912,7 @@ def resolve_local_file(
# MAME clone fallback: if a file was deduped, resolve via canonical
if _depth < 3 and not has_strong_hash:
clone_map = get_mame_clone_map()
clone_map = get_mame_clone_map(db.get("bios_dir"))
canonical = clone_map.get(name)
if canonical and canonical != name:
canonical_entry = {"name": canonical}
@@ -923,28 +960,34 @@ def resolve_local_file(
return None, "not_found"
_mame_clone_map_cache: dict[str, str] | None = None
_mame_clone_map_cache: dict[str, dict[str, str]] = {}
def get_mame_clone_map() -> dict[str, str]:
"""Load and cache the MAME clone map (clone_name -> canonical_name)."""
global _mame_clone_map_cache
if _mame_clone_map_cache is not None:
return _mame_clone_map_cache
clone_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"_mame_clones.json",
)
def get_mame_clone_map(bios_dir: str | None = None) -> dict[str, str]:
"""The MAME clone map (clone_name -> canonical_name) of a scanned tree.
dedup.py writes _mame_clones.json beside the tree it scanned, and the
map of one tree says nothing about another: read from the repository
root whatever ``--bios-dir`` said, an alternate tree's renamed clones
fell out of its packs. Without a tree the repository's own is read.
"""
if bios_dir is None:
bios_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bios"
)
clone_path = os.path.join(os.path.dirname(os.path.abspath(bios_dir)), "_mame_clones.json")
cached = _mame_clone_map_cache.get(clone_path)
if cached is not None:
return cached
clone_map: dict[str, str] = {}
if os.path.exists(clone_path):
with open(clone_path) as f:
data = json.load(f)
_mame_clone_map_cache = {}
for canonical, info in data.items():
for clone in info.get("clones", []):
_mame_clone_map_cache[clone] = canonical
else:
_mame_clone_map_cache = {}
return _mame_clone_map_cache
clone_map[clone] = canonical
_mame_clone_map_cache[clone_path] = clone_map
return clone_map
@@ -957,6 +1000,27 @@ class ProfileSelectionError(ValueError):
"""A named profile cannot answer for itself."""
def profiles_for_systems(
profiles: dict, system_ids: list[str], standalone: bool = False
) -> list[str]:
"""Profile keys that serve any of the systems, spelled either way.
Compared on the normalized id: stella2014 writes atari_2600 where every
other profile writes atari-2600, and an exact match built two different
packs under one file name, the second replacing the first.
"""
wanted = {_norm_system_id(sid) for sid in system_ids}
matching = []
for name, profile in sorted(profiles.items()):
if profile.get("type") in ("launcher", "alias", "test"):
continue
if standalone and "standalone" not in profile.get("type", "libretro"):
continue
if wanted & {_norm_system_id(sid) for sid in profile.get("systems", [])}:
matching.append(name)
return matching
def select_emulator_profiles(
profile_names: list[str],
all_profiles: dict,
@@ -1645,17 +1709,24 @@ def list_emulator_profiles(emulators_dir: str, skip_aliases: bool = True) -> Non
def list_system_ids(emulators_dir: str) -> None:
"""Print available system IDs with emulator count."""
"""Print available system IDs with emulator count.
Counted on the normalized id, as --system selects: listed per spelling,
3do showed two emulators and panasonic-3do six for one system.
"""
profiles = load_emulator_profiles(emulators_dir)
system_emus: dict[str, list[str]] = {}
for name, p in profiles.items():
system_emus: dict[str, set[str]] = {}
spelling: dict[str, str] = {}
for name, p in sorted(profiles.items()):
if p.get("type") in ("alias", "test", "launcher"):
continue
for sys_id in p.get("systems", []):
system_emus.setdefault(sys_id, []).append(name)
for sys_id in sorted(system_emus):
count = len(system_emus[sys_id])
print(f" {sys_id:35s} ({count} emulator{'s' if count > 1 else ''})")
key = _norm_system_id(sys_id)
system_emus.setdefault(key, set()).add(name)
spelling.setdefault(key, sys_id)
for key in sorted(system_emus, key=lambda k: spelling[k]):
count = len(system_emus[key])
print(f" {spelling[key]:35s} ({count} emulator{'s' if count > 1 else ''})")
def list_platform_system_ids(platform_name: str, platforms_dir: str) -> None:
@@ -1731,6 +1802,8 @@ from ziptools import ( # noqa: E402,F401
)
from artifacts import ( # noqa: E402,F401
write_if_changed,
write_text_atomic,
copy_file_atomic,
ArtifactLockBusy,
artifact_lock,
hold_artifact_lock,
+26 -4
View File
@@ -70,6 +70,25 @@ def load_platform_files(
return declared, platform_data_dirs
def declared_names(platforms_dir: str, platforms: list[str] | None, db: dict) -> set[str]:
"""Every name the platforms declare, under every spelling the database knows.
The report judged "undeclared" per normalized system id, which does not
bring bk and elektronika-bk, or nintendo-sgb and nintendo-super-game-boy,
together: 646 entries whose exact name a platform declares were counted
undeclared, while verify and the site read one flat set.
"""
from common import expand_platform_declared_names
names: set[str] = set()
for platform_name in platforms or list_registered_platforms(
platforms_dir, include_archived=True
):
config = load_platform_config(platform_name, platforms_dir)
names.update(expand_platform_declared_names(config, db))
return names
def _build_supplemental_index(
data_root: str = "data", bios_root: str = "bios"
) -> set[str]:
@@ -135,6 +154,7 @@ def _resolve_source(
by_path_suffix: dict | None = None,
file_entry: dict | None = None,
db_files: dict | None = None,
bios_dir: str | None = None,
) -> str | None:
"""Return the source category for a file, or None if not found.
@@ -177,7 +197,7 @@ def _resolve_source(
return "bios"
# bios/ under the canonical MAME set name, as resolve_local_file does:
# a renamed archive is held once, under the name the dedup kept.
canonical = get_mame_clone_map().get(fname)
canonical = get_mame_clone_map(bios_dir).get(fname)
if canonical and canonical != fname:
if canonical in by_name and _name_hit(canonical):
return "bios"
@@ -269,6 +289,7 @@ def entry_source(f: dict, index: dict) -> str | None:
continue
source = _resolve_source(
candidate, by_name, by_name_lower, data_names, by_path_suffix, f, db_files,
index.get("bios_dir"),
)
if source is not None:
return source
@@ -513,6 +534,7 @@ def cross_reference(
"db_files": db_files,
"data_names": data_names,
"all_declared": all_declared,
"bios_dir": db.get("bios_dir"),
}
for emu_name, profile in profiles.items():
_cross_reference_profile(
@@ -611,13 +633,13 @@ def main():
print("No emulator profiles found.", file=sys.stderr)
return
declared, plat_data_dirs = load_platform_files(
args.platforms_dir, [args.platform] if args.platform else None
)
selected = [args.platform] if args.platform else None
declared, plat_data_dirs = load_platform_files(args.platforms_dir, selected)
db = load_database(args.db)
data_names = _build_supplemental_index()
report = cross_reference(
profiles, declared, db, plat_data_dirs, data_names,
all_declared=declared_names(args.platforms_dir, selected, db),
standalone_cores=standalone_cores,
)
+43 -10
View File
@@ -62,10 +62,29 @@ class Exporter(BaseExporter):
# The data block is a fraction of the file; the rest is the checker.
return True
def _bios_files(self, files: list[NativeFile]) -> str:
return ", ".join(self._bios_items(files))
def _bios_files(self, files: list[NativeFile], before: dict | None = None) -> str:
return ", ".join(self._bios_items(files, before))
def _bios_items(self, files: list[NativeFile]) -> list[str]:
@staticmethod
def _runner_keys(item: dict | None) -> list[str]:
"""The emulator and core checkBios reads through coreExists().
Not in our model, so they travel from the maintainer's own entry: a
rewritten item without them made the checker treat the BIOS as
wanted by every build (vectrex, tvc, macintosh, bk).
"""
return [
f'"{key}": "{item[key]}"'
for key in ("emulator", "core")
if item and isinstance(item.get(key), str)
]
def _bios_items(self, files: list[NativeFile], before: dict | None = None) -> list[str]:
originals = {
str(item.get("file", "")): item
for item in (before or {}).get("biosFiles", [])
if isinstance(item, dict)
}
parts: list[str] = []
for fe in files:
# The platform states an unhashed file as an empty md5 rather
@@ -80,14 +99,25 @@ class Exporter(BaseExporter):
zipped = fe.native("zipped_file", "")
if zipped:
item.append(f'"zippedFile": "{zipped}"')
item.extend(self._runner_keys(originals.get(path)))
parts.append("{ " + ", ".join(item) + " }")
return parts
def _entry_line(self, system: NativeSystem, files: list[NativeFile]) -> str:
@staticmethod
def _own_entry(system: NativeSystem, before: dict | None) -> dict:
"""The maintainer's entry for this system, out of the parsed pair."""
inner = (before or {}).get(system.native_id)
return inner if isinstance(inner, dict) else {}
def _entry_line(
self, system: NativeSystem, files: list[NativeFile], before: dict | None = None
) -> str:
own = self._own_entry(system, before)
runner = "".join(f"{key}, " for key in self._runner_keys(own))
return (
f'{_INDENT}"{system.native_id}": '
f'{{ "name": "{self.display_name(system)}", '
f'"biosFiles": [ {self._bios_files(files)} ] }},'
f'{{ "name": "{self.display_name(system)}", {runner}'
f'"biosFiles": [ {self._bios_files(files, own)} ] }},'
)
@staticmethod
@@ -127,14 +157,17 @@ class Exporter(BaseExporter):
) -> list[str]:
"""One entry, keeping the layout and the notes it was written with."""
trailing, leading = self._item_notes(original)
items = self._bios_items(files)
before = self._parse_entry(original)
own = self._own_entry(system, before)
items = self._bios_items(files, own)
one_line = len(original) == 1 and not trailing and not leading
if one_line:
return [self._entry_line(system, files)]
return [self._entry_line(system, files, before)]
runner = "".join(f"{key}, " for key in self._runner_keys(own))
head = (
f'{_INDENT}"{system.native_id}": '
f'{{ "name": "{self.display_name(system)}", "biosFiles": ['
f'{{ "name": "{self.display_name(system)}", {runner}"biosFiles": ['
)
pad = " " * (len(_INDENT) + 4)
lines = [head]
@@ -244,8 +277,8 @@ class Exporter(BaseExporter):
continue
written.add(key)
original_lines = body[start:end + 1]
replacement = self._entry_line(*pair)
before = self._parse_entry(original_lines)
replacement = self._entry_line(*pair, before)
after = self._parse_entry([replacement])
if before is not None and before == after:
# Nothing changed: keep the maintainer's own lines, comments
+9 -2
View File
@@ -82,8 +82,15 @@ class Exporter(BaseExporter):
for fe in system.files:
# A SHA1 BizHawk declares is BizHawk's own code, the authority
# for its cores: the truth's MSX.rom came from FBNeo and would
# have made MSXHawk refuse the dump it accepts.
if fe.hash("sha1") and not _hash_values(fe.platform or {}, "sha1"):
# have made MSXHawk refuse the dump it accepts. An entry the
# database never declared (can_add refuses it) names no call
# of its own either: by name it would rewrite the homonym of
# another system.
if (
fe.platform is not None
and fe.hash("sha1")
and not _hash_values(fe.platform, "sha1")
):
seen.setdefault(fe.name.casefold(), []).append(fe)
resolved: dict[str, NativeFile] = {}
for name, entries in seen.items():
+10 -8
View File
@@ -176,8 +176,8 @@ class Exporter(BaseExporter):
return nested, "bios"
return None
@staticmethod
def _merge(existing: object, ours: list[OrderedDict]) -> list[OrderedDict]:
@classmethod
def _merge(cls, existing: object, ours: list[OrderedDict]) -> list[OrderedDict]:
"""Correct the component's own list; never replace it.
Assigning our entries wholesale dropped every file RetroDECK declares
@@ -206,6 +206,9 @@ class Exporter(BaseExporter):
for system in _systems_of(entry):
key = (str(entry.get("filename", "")), system)
seen_keys[key] = seen_keys.get(key, 0) + 1
# A hash no revision shares is a revision the platform does not
# know: appended, not counted corrected against entries kept as
# they were (the 64DD IPL declared twice, both contradicted).
for entry in entries:
keys = _matching_keys(entry, by_key)
other_revision = (
@@ -213,18 +216,17 @@ class Exporter(BaseExporter):
and seen_keys.get(keys[0], 0) > 1
and not _md5_set(entry.get("md5")) & _md5_set(by_key[keys[0]].get("md5"))
)
if other_revision:
merged.append(OrderedDict(entry))
corrected.update(keys)
continue
if not keys:
if other_revision or not keys:
merged.append(OrderedDict(entry))
continue
declared = entry.get("system")
combined = OrderedDict(entry)
# Only what the format says it corrects: the maintainer's
# description and search paths are theirs (neogeo.zip lost its
# four roms directories to a path that does not exist).
combined.update(
(field, value) for field, value in by_key[keys[0]].items()
if not (field == "system" and declared)
if field in cls.carries() and not (field == "system" and declared)
)
merged.append(combined)
corrected.update(keys)
+3
View File
@@ -386,6 +386,9 @@ def main():
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"total_files": len(files),
"total_size": total_size,
# The tree these paths belong to: dedup writes _mame_clones.json
# beside it, and the resolver reads the map there, not at the repo root.
"bios_dir": os.path.normpath(args.bios_dir),
"files": files,
"indexes": indexes,
}
+55 -26
View File
@@ -31,6 +31,8 @@ sys.path.insert(0, os.path.dirname(__file__))
from common import resolve_platform_cores as _platform_cores
from common import (
apply_target_overrides,
profiles_for_systems,
write_text_atomic,
artifact_lock,
ArtifactLockBusy,
build_target_cores_cache,
@@ -723,10 +725,13 @@ 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.
# Decided among the declarations this pack keeps: a preferred entry
# that --required-only drops must not silence its required twin, or
# the destination leaves the pack without a trace (RetroDECK's four
# Atari 5200 ROMs, optional under atari-400-800 and required under
# atari-5200). No data directories: the choice is about the baseline.
preferred_entries = _preferred_entries(
pack_systems, db, bios_dir, base_dest, False,
pack_systems, db, bios_dir, base_dest, required_only,
zip_contents, None, offline,
)
@@ -1556,16 +1561,7 @@ def generate_system_pack(
) -> str | None:
"""Generate a ZIP pack for all emulators supporting given system IDs."""
profiles = load_emulator_profiles(emulators_dir)
matching = []
for name, profile in sorted(profiles.items()):
if profile.get("type") in ("launcher", "alias", "test"):
continue
emu_systems = set(profile.get("systems", []))
if emu_systems & set(system_ids):
ptype = profile.get("type", "libretro")
if standalone and "standalone" not in ptype:
continue
matching.append(name)
matching = profiles_for_systems(profiles, system_ids, standalone)
if not matching:
all_systems: set[str] = set()
@@ -1587,10 +1583,9 @@ def generate_system_pack(
)
return None
# Use system-based ZIP name
sys_display = "_".join(
_name_part("_".join(w.title() for w in sid.split("-"))) for sid in system_ids
)
# Named like the platform's --system tag: one spelling of a system is
# one file name, and one selection.
sys_display = "_".join(_system_display_name(sid) for sid in system_ids)
# Built in a scratch directory: under its emulator name in output_dir it
# overwrote, then carried away, an --emulator pack already there.
os.makedirs(output_dir, exist_ok=True)
@@ -1826,7 +1821,16 @@ def generate_md5_pack(
if emulator_name and emulators_dir:
profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
if emulator_name in profiles:
profile = profiles[emulator_name]
# The same gate as --emulator: a core with no standalone build
# has no standalone layout, and a pack named for one would be
# the libretro pack under a name that promises otherwise.
try:
(_name, profile), = select_emulator_profiles(
[emulator_name], profiles, standalone
)
except ProfileSelectionError as exc:
print(f"Error: {exc}", file=sys.stderr)
return None
emu_display = profile.get("emulator", emulator_name)
emu_pack_structure = profile.get("pack_structure")
for fe in profile.get("files", []):
@@ -1999,9 +2003,7 @@ def generate_target_manifests(targets_dir: str, output_dir: str) -> None:
result[alias] = result[target_name]
out_path = Path(output_dir) / f"{yml_file.stem}.json"
with open(out_path, "w") as f:
json.dump(result, f, indent=2, sort_keys=True)
f.write("\n")
write_text_atomic(out_path, json.dumps(result, indent=2, sort_keys=True) + "\n")
count += 1
print(f" {yml_file.stem}: {len(result)} targets")
print(f"Generated {count} target manifest(s) in {output_dir}")
@@ -2103,8 +2105,7 @@ def _write_manifest_if_changed(path: str, manifest: dict) -> None:
new_cmp = {k: v for k, v in manifest.items() if k != "generated"}
if old_cmp == new_cmp:
return # no content change, keep existing timestamp
with open(path, "w") as f:
f.write(new_json)
write_text_atomic(path, new_json)
def _run_manifest_mode(
@@ -2297,6 +2298,11 @@ def _pack_label(source: str, required_only: bool) -> str:
return f"[source={source}, required]" if required_only else f"[source={source}]"
def _produced(split: bool, zip_path, zip_paths) -> bool:
"""Whether the build wrote what the run asked for."""
return bool(zip_paths if split else zip_path)
def _run_platform_packs(
args,
groups,
@@ -2331,6 +2337,8 @@ def _run_platform_packs(
else:
print(f"\nGenerating pack for {representative} {_pack_label(source, required_only)}...")
zip_path = None
zip_paths: list = []
try:
tc = target_cores_cache.get(representative) if args.target else None
if args.split:
@@ -2399,6 +2407,12 @@ def _run_platform_packs(
except (FileNotFoundError, OSError, yaml.YAMLError) as e:
print(f" ERROR: {e}")
failed.append(representative)
else:
# A builder that declined (no system left under the target)
# produced nothing: the run must say so, not verify whatever
# the output directory already held and exit 0.
if not _produced(args.split, zip_path, zip_paths):
failed.append(representative)
print("\nVerifying packs and generating manifests...")
skip_conf = bool(system_filter or args.split)
@@ -3078,6 +3092,18 @@ def _manifest_region_drops(
)
def _record_unplaceable(unplaceable: list[dict], record_omission) -> None:
"""Held by the collection, nowhere to put it on this platform.
Said in the manifest, where RomM's DOSBox Pure ROMs were simply absent.
"""
for u in unplaceable:
record_omission(
u.get("path") or u.get("name", ""), u, u.get("system") or "",
"no_platform_slug", [u.get("emulator", "")],
)
def generate_manifest(
platform_name: str,
platforms_dir: str,
@@ -3187,10 +3213,10 @@ def generate_manifest(
# 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.
# Decided among the declarations this manifest keeps, as the pack
# does; a manifest carries no data directories.
preferred_entries = _preferred_entries(
pack_systems, db, bios_dir, base_dest, False,
pack_systems, db, bios_dir, base_dest, required_only,
zip_contents, None, offline,
)
for sys_id, system in sorted(pack_systems.items()):
@@ -3319,6 +3345,7 @@ def generate_manifest(
seen_lower.add(dedup_key.lower())
# Phase 2: core complement (emulator extras)
unplaceable: list[dict] = []
if source != "platform":
core_files = _collect_emulator_extras(
config,
@@ -3329,9 +3356,11 @@ def generate_manifest(
emu_profiles,
target_cores=target_cores,
include_all=(source == "truth"),
unplaceable=unplaceable,
)
else:
core_files = []
_record_unplaceable(unplaceable, record_omission)
total_size += _manifest_core_entries(
core_files, config, db, bios_dir, base_dest, repo_root,
zip_contents, offline, region_drops, case_insensitive,
+54 -15
View File
@@ -134,6 +134,42 @@ def _map_emulator_to_slug(
return sys_to_slug.get(target, "")
return ""
def _slug_for(
u: dict, profiles: dict, platform_systems: set, norm_map: dict, sys_to_slug: dict
) -> str:
"""The platform slug a report entry's emulator files under, or empty."""
emu_name = u.get("profile") or u.get("emulator", "")
profile = profiles.get(emu_name, {})
if not profile:
# The report names the display name where the key failed.
profile = next(
(pp for pp in profiles.values() if pp.get("emulator") == emu_name), {}
)
return _map_emulator_to_slug(profile, platform_systems, norm_map, sys_to_slug)
def unplaceable_extras(config: dict, undeclared: list[dict], profiles: dict) -> set:
"""Keys of the report entries a slug-based platform cannot place.
RomM files every BIOS under the slug of its system and knows no slug
for DOS or for the arcade sets MAME 2003 reads; the builder skipped
those entries and the report counted them in the pack.
"""
from common import _norm_system_id
is_slug_based, sys_to_slug = _detect_slug_structure(config)
if not is_slug_based:
return set()
platform_systems = set(config.get("systems", {}).keys())
norm_map = {_norm_system_id(sid): sid for sid in platform_systems}
return {
(u.get("emulator", ""), u.get("name", ""), u.get("path") or "")
for u in undeclared
if u.get("in_repo")
and not _slug_for(u, profiles, platform_systems, norm_map, sys_to_slug)
}
def _agnostic_scan_extras(
profiles: dict,
relevant: set,
@@ -412,9 +448,18 @@ def _collect_emulator_extras(
emu_profiles: dict | None = None,
target_cores: set[str] | None = None,
include_all: bool = False,
claimants: dict[tuple[str, str, str], str] | None = None,
unplaceable: list[dict] | None = None,
) -> list[dict]:
"""Collect core requirement files from emulator profiles not in the platform pack.
``claimants`` receives the destination of every report entry that
asked for one, the ones a sibling already took included: the region
report withdraws by that key, and an entry without one stayed listed
after the builder had withdrawn its destination. ``unplaceable``
receives the entries a slug-based platform cannot place, so the
manifest and the report can say so instead of counting them packed.
Uses the same system-overlap matching as verify.py cross-reference:
- Matches emulators by shared system IDs with the platform
- Filters mode: standalone, type: launcher, type: alias
@@ -472,25 +517,18 @@ def _collect_emulator_extras(
# Slug-based platforms: prefix dest with system slug
if is_slug_based:
emu_name = u.get("profile") or u.get("emulator", "")
profile = profiles.get(emu_name, {})
# Try finding profile by display name if key lookup failed
if not profile:
for pn, pp in profiles.items():
if pp.get("emulator") == emu_name:
profile = pp
break
slug = _map_emulator_to_slug(
profile,
platform_systems,
norm_map,
sys_to_slug,
)
slug = _slug_for(u, profiles, platform_systems, norm_map, sys_to_slug)
if not slug:
continue # can't place without slug
if unplaceable is not None:
unplaceable.append(u)
continue
dest = f"{slug}/{dest}"
full_dest = f"{extras_prefix}/{dest}" if extras_prefix else dest
if claimants is not None:
claimants[(u.get("emulator", ""), u.get("name", ""), u.get("path") or "")] = (
sanitize_pack_path(dest)
)
if full_dest in seen_dests:
continue
seen_dests.add(full_dest)
@@ -681,6 +719,7 @@ def platform_region_groups(
emu_profiles,
target_cores=target_cores,
include_all=include_all,
claimants=extra_dests,
)
for extra in _kept(extras, required_only):
dest = sanitize_pack_path(extra.get("destination", extra.get("name", "")))
+5 -1
View File
@@ -198,9 +198,13 @@ def check_consistency(verify_output: str, pack_output: str) -> bool:
f" {v_label}: verify {v_ok}/{v_total} == pack {p_ok}/{p_total} OK"
)
else:
# The invariant runs both ways: a builder more optimistic
# than the report ships files the report never vouched for.
print(
f" {v_label}: verify {v_ok}/{v_total}, pack {p_ok}/{p_total} OK (pack resolves more)"
f" {v_label}: MISMATCH pack {p_ok}/{p_total} OK "
f"> verify {v_ok}/{v_total}"
)
all_ok = False
else:
print(f" {v_label}: {v_ok}/{v_total} (no separate pack)")
all_ok = False
+8
View File
@@ -88,6 +88,13 @@ def build_report(db: dict, snapshots: dict, members: dict | None = None) -> dict
by_md5_size = {
(entry.get("md5", ""), entry.get("size", 0)) for entry in by_sha1.values()
}
# Redump lists some dumps by crc32 alone (ps2-0101jd-20030110, DTL-H10100):
# collected as a loose file it would stay MISSING for good without this.
by_crc32_size = {
(str(entry.get("crc32", "")).lower(), entry.get("size", 0))
for entry in by_sha1.values()
if entry.get("crc32")
}
report = {}
for source, snapshot in sorted(snapshots.items()):
@@ -98,6 +105,7 @@ def build_report(db: dict, snapshots: dict, members: dict | None = None) -> dict
if (
entry.get("sha1") in by_sha1
or (entry.get("md5"), entry.get("size")) in by_md5_size
or (str(entry.get("crc32") or "").lower(), entry.get("size")) in by_crc32_size
or _held_in_archive(entry, members)
):
matched += 1
+18 -20
View File
@@ -19,7 +19,7 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import region
from common import load_emulator_profiles, load_database, parse_md5_list
from common import load_database, load_emulator_profiles, resolve_local_file
# No-Intro filename tokens: full English territory names.
NOINTRO = {
@@ -100,24 +100,21 @@ def catalog_regions(db: dict) -> dict[str, tuple[set[str], str]]:
return out
def resolve_sha1(file_entry: dict, db: dict) -> str | None:
"""Resolve a profile file entry to a repo SHA1, or None when ambiguous."""
files = db["files"]
indexes = db["indexes"]
raw_sha1 = file_entry.get("sha1")
declared = raw_sha1 if isinstance(raw_sha1, list) else [raw_sha1]
sha1_hits = [str(value).lower() for value in declared if value]
sha1_hits = [value for value in sha1_hits if value in files]
if len(sha1_hits) == 1:
return sha1_hits[0]
for md5 in parse_md5_list(file_entry.get("md5")):
hit = indexes["by_md5"].get(md5)
if hit:
return hit
hits = indexes["by_name"].get(file_entry.get("name", ""), [])
if isinstance(hits, str):
hits = [hits]
return hits[0] if len(hits) == 1 else None
def resolve_sha1(file_entry: dict, db: dict, by_path: dict | None = None) -> str | None:
"""The repo SHA1 a profile entry resolves to, by the pack's own resolver.
A private resolver read sha1, then md5, then a lone name: entries that
identify their file by path (Dolphin's three IPL.bin), by crc32
(geargrafx) or by sha256 (mednafen) were never checked at all.
"""
if by_path is None:
by_path = {entry.get("path", ""): sha1 for sha1, entry in db["files"].items()}
path, status = resolve_local_file(
file_entry, db, dest_hint=file_entry.get("path") or ""
)
if not path or status in ("not_found", "hash_mismatch"):
return None
return by_path.get(path)
def build_report(profiles: dict, db: dict) -> dict:
@@ -130,6 +127,7 @@ def build_report(profiles: dict, db: dict) -> dict:
for slugs, _name in catalog.values():
vocabulary.update(slugs)
by_path = {entry.get("path", ""): sha1 for sha1, entry in db["files"].items()}
for emu_name, profile in sorted(profiles.items()):
if profile.get("type") in ("launcher", "alias"):
continue
@@ -137,7 +135,7 @@ def build_report(profiles: dict, db: dict) -> dict:
if not isinstance(f, dict) or not f.get("region"):
continue
declared = region.normalize_declared(f["region"])
sha1 = resolve_sha1(f, db)
sha1 = resolve_sha1(dict(f, source_profile=emu_name), db, by_path)
if not sha1 or sha1 not in catalog:
unchecked += 1
continue
+2 -1
View File
@@ -24,6 +24,7 @@ import zipfile
from pathlib import Path
import split_pack
from common import write_text_atomic
RECORD = "release.json"
@@ -124,7 +125,7 @@ def main() -> int:
print(f"Error: {line}", file=sys.stderr)
if mismatches:
return 1
args.output.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
write_text_atomic(str(args.output), json.dumps(record, indent=2) + "\n")
print(f"Wrote {args.output}")
return 0
+4 -3
View File
@@ -19,11 +19,10 @@ from __future__ import annotations
import argparse
import hashlib
import os
import shutil
import sys
sys.path.insert(0, os.path.dirname(__file__))
from common import load_database
from common import copy_file_atomic, load_database
def gitignored_paths(gitignore: str) -> set[str]:
@@ -72,7 +71,9 @@ def restore(
unsatisfied.append(path)
continue
os.makedirs(os.path.dirname(path), exist_ok=True)
shutil.copy2(source, path)
# Whole or absent: a scan running beside a plain copy hashed the
# truncated file and published that in the database.
copy_file_atomic(source, path)
print(f"Restored: {path}")
restored += 1
print(f"Total: {restored} files restored")
+59 -12
View File
@@ -28,6 +28,7 @@ from common import (
resolve_local_file,
runs_standalone,
)
from validation import outside_gap_scope
# A profile entry can prove a slot without declaring a hash: Dolphin names no
# checksum for the GameCube boot ROM because its source names none, and the
@@ -155,10 +156,10 @@ def profile_claims(
for entry in profile.get("files") or []:
if not isinstance(entry, dict):
continue
entry_mode = entry.get("mode")
if entry_mode == "standalone" and not is_standalone:
continue
if entry_mode == "libretro" and is_standalone:
# The same rule as the gap report and the builder: an entry the
# pack never carries (read from the save directory, a
# placeholder, a filename-agnostic scan) arbitrates nothing.
if outside_gap_scope(entry, is_standalone):
continue
# A member of a romset is not a file at the BIOS root: the archive
# holding it is what occupies a destination, and it is declared
@@ -242,10 +243,14 @@ def find_conflicts(
continue
by_slot.setdefault(key, []).append(claim)
# A declaration that lists several accepted hashes is satisfied by any of
# them: Recalbox names both scph1001.bin revisions, and mesence names
# four BS-X dumps among which System.dat pins one. The resolver stops on
# the first it finds, so the paths differ while both layers agree.
disputed = {
key: claims
for key, claims in by_slot.items()
if all(c.local_path != by_dest[key].local_path for c in claims)
if not any(_satisfies(c, by_dest[key], db) for c in claims)
}
return [
@@ -455,6 +460,51 @@ def find_collisions(
return collisions
def _satisfies(profile: Claim, platform: Claim, db: dict) -> bool:
"""Whether the two layers agree on what ships at this destination.
They agree when they resolve to the same file, when the platform's own
hash list accepts the profile's file, or when the profile's accepts the
platform's. A contradiction needs both lists to reject the other side.
"""
if profile.local_path == platform.local_path:
return True
if (
profile.local_path
and _lists_hashes(platform.entry)
and _accepted_by_all(profile.local_path, [platform.entry], db)
):
return True
return bool(
platform.local_path
and _lists_hashes(profile.entry)
and _accepted_by_all(platform.local_path, [profile.entry], db)
)
def _lists_hashes(entry: dict) -> bool:
"""Whether a declaration says anything about content at all."""
return any(
_declared_hashes(entry, field_name)
for field_name in ("md5", "sha1", "sha256", "crc32")
)
def _declared_hashes(entry: dict, field_name: str) -> set[str]:
"""Hashes an entry accepts for one field, declared as a string or a list."""
value = entry.get(field_name)
values = value if isinstance(value, list) else [value]
# Recalbox writes its md5 list comma separated, and profiles do the
# same for sha256 (mesence names four BS-X dumps in one string).
return {
part.strip().lower()
for v in values
if v
for part in str(v).split(",")
if part.strip()
}
def _accepted_by_all(path: str, entries: list[dict], db: dict) -> bool:
"""Whether every declaration's own hashes accept the file at path."""
record = next(
@@ -470,13 +520,10 @@ def _accepted_by_all(path: str, entries: list[dict], db: dict) -> bool:
):
return False
continue
md5s = parse_md5_list(entry.get("md5"))
if md5s and str(record.get("md5", "")).lower() not in md5s:
return False
sha1 = entry.get("sha1")
sha1s = [sha1] if isinstance(sha1, str) else list(sha1 or [])
if sha1s and str(record.get("sha1", "")).lower() not in {h.lower() for h in sha1s}:
return False
for field_name in ("md5", "sha1", "sha256", "crc32"):
declared = _declared_hashes(entry, field_name)
if declared and str(record.get(field_name, "")).lower() not in declared:
return False
return True
+59 -36
View File
@@ -228,36 +228,71 @@ def validate_file(
def get_changed_files() -> list[str]:
"""Get list of changed files in current PR/branch using git."""
try:
for base in ("main", "master", "v2"):
try:
result = subprocess.run(
["git", "diff", "--name-only", f"origin/{base}...HEAD"],
capture_output=True,
text=True,
check=True,
)
files = [
f
for f in result.stdout.strip().split("\n")
if f.startswith("bios/")
]
if files:
return files
except subprocess.CalledProcessError:
continue
except (subprocess.CalledProcessError, OSError):
pass
"""BIOS files the branch changes against origin's main line.
A base git cannot resolve is skipped; when none resolves the staged
changes are read and said so. A git that fails outright is an error,
never "nothing changed": a clone whose remote is not called origin
used to answer that no BIOS had changed while the branch committed one.
"""
compared = False
for base in ("main", "master", "v2"):
result = subprocess.run(
["git", "diff", "--name-only", f"origin/{base}...HEAD"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
continue
compared = True
files = [f for f in result.stdout.strip().split("\n") if f.startswith("bios/")]
if files:
return files
if compared:
return []
print(
"no origin/main, origin/master or origin/v2 to compare against; "
"reading the staged changes only",
file=sys.stderr,
)
result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
raise RuntimeError(f"git diff --cached failed: {result.stderr.strip()}")
return [f for f in result.stdout.strip().split("\n") if f.startswith("bios/") and f]
def _files_to_validate(args, parser) -> list:
"""The files named on the command line, or the changed ones."""
if not args.changed:
if not args.files:
parser.error("No files specified. Use --changed or provide file paths.")
return args.files
try:
files = get_changed_files()
except (RuntimeError, OSError) as exc:
print(f"Error: {exc}", file=sys.stderr)
sys.exit(2)
if not files:
print("No changed BIOS files detected")
return files
def _load_database_if_any(path):
try:
return load_database(path)
except FileNotFoundError:
return None
except json.JSONDecodeError as e:
print(f"WARNING: corrupt database.json: {e}", file=sys.stderr)
return None
def main():
parser = argparse.ArgumentParser(description="Validate BIOS file contributions")
parser.add_argument("files", nargs="*", help="Files to validate")
@@ -272,23 +307,11 @@ def main():
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
files = args.files
if args.changed:
files = get_changed_files()
if not files:
print("No changed BIOS files detected")
return
files = _files_to_validate(args, parser)
if not files:
parser.error("No files specified. Use --changed or provide file paths.")
return
try:
db = load_database(args.db)
except FileNotFoundError:
db = None
except json.JSONDecodeError as e:
print(f"WARNING: corrupt database.json: {e}", file=sys.stderr)
db = None
db = _load_database_if_any(args.db)
platform_hashes = load_platform_hashes(args.platforms_dir)
results = []
+115 -21
View File
@@ -34,6 +34,7 @@ import slots
sys.path.insert(0, os.path.dirname(__file__))
from common import (
profiles_for_systems,
PROFILE_IDENTITY_FIELDS,
list_available_targets,
list_platform_system_ids,
@@ -84,6 +85,7 @@ from validation import (
DEFAULT_DB = "database.json"
DEFAULT_PLATFORMS_DIR = "platforms"
DEFAULT_BIOS_DIR = "bios"
# The repository's platforms wherever the script runs from, for library calls
# that pass no directory.
_REPO_PLATFORMS = os.path.join(os.path.dirname(__file__), "..", "platforms")
@@ -397,8 +399,11 @@ def _candidate_verdict(
if outside_gap_scope(file_entry, is_standalone):
return "skip"
if not include_all:
archive = file_entry.get("archive")
if fname in declared_names or (archive and archive in declared_names):
# A platform declaring any name the core answers to has met the
# requirement: quasi88 reads n88sub.rom or disk.rom, and System.dat
# names disk.rom. Without the aliases the pack carried the ROM twice.
answers = {fname, file_entry.get("archive") or "", *file_entry.get("aliases", [])}
if answers & declared_names:
return "settled"
return "keep"
@@ -715,6 +720,75 @@ def find_exclusion_notes(
# Platform verification
def _mark_unplaced(config: dict, undeclared: list[dict], profiles: dict) -> None:
"""What the builder cannot place is not in the pack, held or not."""
from packextras import unplaceable_extras
unplaced = unplaceable_extras(config, undeclared, profiles)
for u in undeclared:
if (u.get("emulator", ""), u.get("name", ""), u.get("path") or "") in unplaced:
u["in_pack"] = False
u["omitted"] = "no platform slug for its system"
def _twin_index(
verify_systems: dict, db: dict, base_dest: str, zip_contents: dict,
data_dir_registry: dict | None,
) -> tuple[dict[str, int], dict[int, tuple[str, dict]]]:
"""Which declaration the pack ships at each destination, and every declaration by id."""
from generate_pack import _preferred_entries
preferred_entries = _preferred_entries(
verify_systems, db, DEFAULT_BIOS_DIR, base_dest, False,
zip_contents, data_dir_registry, True,
)
winners = {
id(fe): (sid, fe)
for sid, system in verify_systems.items()
for fe in system.get("files", [])
}
return preferred_entries, winners
def _resolve_declaration(
file_entry: dict, sys_id: str, twins: tuple, db: dict, zip_contents: dict,
data_dir_registry: dict | None, slot_overrides: dict, mode: str,
platform_profiles: dict,
) -> tuple[str | None, str, str | None]:
"""Resolve a declaration to the file the pack ships at its destination.
When another declaration holds the destination, that one's file is
returned and the evidence reset: the winner's hash match is not this
declaration's, which is hashed against the shipped file.
"""
preferred_entries, winners, base_dest = twins
bare = sanitize_pack_path(file_entry.get("destination", file_entry.get("name", "")))
preferred = preferred_entries.get(f"{base_dest}/{bare}" if base_dest else bare)
held_by, winner = (None, file_entry)
if preferred is not None and preferred != id(file_entry):
held_by, winner = winners[preferred]
local_path, resolve_status = _resolve_for_platform(
winner, held_by or sys_id, db, zip_contents, data_dir_registry,
slot_overrides, mode, platform_profiles,
)
return local_path, "" if held_by else resolve_status, held_by
def _twin_unmet(result: dict, held_by: str | None) -> bool:
"""Mark a declaration unmet at a path another declaration holds.
The path is answered by the winner's file, which the pack counts once;
this declaration's failure is reported, never folded into the
destination's status.
"""
if held_by is None or result["status"] == Status.OK:
return False
result["discrepancy"] = (
f"{result.get('name', '')} is not met at the path {held_by} holds"
)
return True
def _resolve_for_platform(
file_entry: dict,
sys_id: str,
@@ -830,7 +904,7 @@ def verify_platform(
# pass reads. Grouping the platform files here and the core extras
# there let the two answer differently on one request: the report kept
# every core extra a region run withdraws from the pack.
from generate_pack import platform_region_groups
from packextras import platform_region_groups
region_groups, region_extra_dests = platform_region_groups(
config,
@@ -845,6 +919,15 @@ def verify_platform(
region_groups, region_mod.build_region_index(profiles), regions
)
# A destination two systems declare with two hashes ships one file, the
# one the builder prefers. The other declaration is scored against that
# file, as the frontend will score it after installation: RetroDECK's
# xroar component hashes bios/disk.rom and finds the PC-88 ROM.
base_dest = config.get("base_destination", "")
preferred_entries, winners = _twin_index(
verify_systems, db, base_dest, zip_contents, data_dir_registry
)
for sys_id, system in verify_systems.items():
for file_entry in system.get("files", []):
if region_drops and (
@@ -854,9 +937,10 @@ def verify_platform(
in region_drops
):
continue
local_path, resolve_status = _resolve_for_platform(
file_entry, sys_id, db, zip_contents, data_dir_registry,
slot_overrides, mode, platform_profiles,
local_path, resolve_status, held_by = _resolve_declaration(
file_entry, sys_id, (preferred_entries, winners, base_dest),
db, zip_contents, data_dir_registry, slot_overrides, mode,
platform_profiles,
)
destination = sanitize_pack_path(
file_entry.get("destination", file_entry.get("name", ""))
@@ -893,6 +977,8 @@ def verify_platform(
validation_index,
)
details.append(result)
if _twin_unmet(result, held_by):
continue
# Aggregate by destination
dest = file_entry.get("destination", file_entry.get("name", ""))
@@ -949,6 +1035,7 @@ def verify_platform(
)
not in region_drops
]
_mark_unplaced(config, undeclared, profiles)
exclusions = find_exclusion_notes(
config, emulators_dir, emu_profiles, target_cores=target_cores
)
@@ -1048,6 +1135,10 @@ def _print_detail_entries(details: list[dict], seen: set[str], verbose: bool) ->
"""Print UNTESTED, MISSING, and DISCREPANCY entries from verification details."""
for d in details:
if d["status"] == Status.UNTESTED:
if d.get("discrepancy"):
# Reported below with the ground for it: a declaration
# unmet at a path another declaration holds.
continue
key = f"{d['system']}/{d['name']}"
if key in seen:
continue
@@ -1116,17 +1207,27 @@ def _print_undeclared_entry(u: dict, prefix: str, verbose: bool) -> None:
print(f" [{'+'.join(checks)}]")
def _split_undeclared(undeclared: list[dict]) -> tuple[list[dict], list[dict], list[dict]]:
"""Game data, firmware the pack places, firmware it holds but cannot place.
Everything that is not game data is firmware the core loads, archives
included: a bios_zip sat in neither list, so a required one that was
missing was never printed.
"""
game_data = [u for u in undeclared if u.get("category", "bios") == "game_data"]
firmware = [u for u in undeclared if u.get("category", "bios") != "game_data"]
unplaced = [u for u in firmware if u["in_repo"] and not u.get("in_pack", True)]
placed = [u for u in firmware if u.get("in_pack", True)]
return game_data, placed, unplaced
def _print_undeclared_section(result: dict, verbose: bool) -> None:
"""Print cross-reference section for undeclared files used by cores."""
undeclared = result.get("undeclared_files", [])
if not undeclared:
return
# Everything that is not game data is firmware the core loads, archives
# included: a bios_zip sat in neither list, so a required one that was
# missing was never printed.
game_data = [u for u in undeclared if u.get("category", "bios") == "game_data"]
bios_files = [u for u in undeclared if u.get("category", "bios") != "game_data"]
game_data, bios_files, unplaced = _split_undeclared(undeclared)
req_not_in_repo = [
u
@@ -1149,6 +1250,8 @@ def _print_undeclared_section(result: dict, verbose: bool) -> None:
print(
f" Core files: {core_in_pack} in pack, {core_missing_req} required missing, {core_missing_opt} optional missing"
)
if unplaced:
print(f" Core files held but not packed: {len(unplaced)} ({unplaced[0]['omitted']})")
for u in req_not_in_repo:
_print_undeclared_entry(u, "MISSING (required)", verbose)
@@ -1562,16 +1665,7 @@ def verify_system(
) -> dict:
"""Verify files for all emulators supporting given system IDs."""
profiles = load_emulator_profiles(emulators_dir)
matching = []
for name, profile in sorted(profiles.items()):
if profile.get("type") in ("launcher", "alias", "test"):
continue
emu_systems = set(profile.get("systems", []))
if emu_systems & set(system_ids):
ptype = profile.get("type", "libretro")
if standalone and "standalone" not in ptype:
continue # skip non-standalone in standalone mode
matching.append(name)
matching = profiles_for_systems(profiles, system_ids, standalone)
if not matching:
all_systems: set[str] = set()
+69
View File
@@ -0,0 +1,69 @@
"""Generated artifacts are written whole or not at all.
write_if_changed got the scratch-and-rename treatment, but the install
manifests, the target manifests and release.json were still truncated at
their final name, and restore_large_files copied an asset straight to its
path in bios/: a scan running beside the copy hashed the truncated file.
"""
from __future__ import annotations
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import artifacts # noqa: E402
class WholeOrNothing(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.dir = Path(self._tmp.name)
def tearDown(self):
self._tmp.cleanup()
def test_an_interrupted_write_keeps_the_previous_file(self):
target = self.dir / "manifest.json"
target.write_text("{}")
with mock.patch("os.replace", side_effect=OSError("cut")), self.assertRaises(OSError):
artifacts.write_text_atomic(str(target), "{\"files\": []}")
self.assertEqual(target.read_text(), "{}")
self.assertEqual(os.listdir(self.dir), ["manifest.json"])
def test_an_interrupted_copy_leaves_no_file(self):
source = self.dir / "asset.bin"
source.write_bytes(b"x" * 1000)
target = self.dir / "bios" / "asset.bin"
target.parent.mkdir()
with mock.patch("shutil.copy2", side_effect=OSError("cut")), self.assertRaises(OSError):
artifacts.copy_file_atomic(str(source), str(target))
self.assertEqual(os.listdir(target.parent), [])
class EveryWriterGoesThroughIt(unittest.TestCase):
def test_manifests_record_and_restore_use_the_atomic_helpers(self):
scripts = REPO_ROOT / "scripts"
for name, helper in (
("generate_pack.py", "write_text_atomic("),
("release_record.py", "write_text_atomic("),
("restore_large_files.py", "copy_file_atomic("),
):
source = (scripts / name).read_text(encoding="utf-8")
with self.subTest(script=name):
self.assertIn(helper, source)
pack = (scripts / "generate_pack.py").read_text(encoding="utf-8")
self.assertNotIn('with open(path, "w") as f:\n f.write(new_json)', pack)
self.assertNotIn("json.dump(result, f", pack)
self.assertNotIn("args.output.write_text(", (scripts / "release_record.py").read_text())
self.assertNotIn("shutil.copy2(source, path)", (scripts / "restore_large_files.py").read_text())
if __name__ == "__main__":
unittest.main()
+49 -12
View File
@@ -482,6 +482,20 @@ class PipelineRegressions(unittest.TestCase):
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(pipeline.check_consistency(verify, pack))
def test_a_pack_more_optimistic_than_verify_is_a_failure(self):
"""The invariant held one way only: a pack counting more OK files
than the report passed as 'pack resolves more'."""
verify = "DupTest: 0/1 OK, 1 untested [md5]"
pack = "\n".join(
[
"Generating pack for DupTest [source=full]...",
" x/DupTest_1_BIOS_Pack.zip: 1 files packed (1 baseline + 0 from cores), "
"1/1 files OK [md5]",
]
)
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(pipeline.check_consistency(verify, pack))
def test_nothing_parsed_is_not_consistent(self):
"""A changed output format must not pass with no platform compared."""
with contextlib.redirect_stdout(io.StringIO()):
@@ -925,17 +939,37 @@ class RegionRegressions(unittest.TestCase):
)
self.assertNotEqual(north_america, europe)
def test_region_audit_accepts_list_valued_md5(self):
sha1 = "a" * 40
md5 = "b" * 32
db = {
"files": {sha1: {}},
"indexes": {"by_md5": {md5: sha1}, "by_name": {}},
}
self.assertEqual(
region_audit.resolve_sha1({"name": "bios.bin", "md5": [md5]}, db),
sha1,
)
def test_region_audit_resolves_the_way_the_pack_does(self):
"""A private resolver read sha1, md5 and a lone name: an entry
identified by its path (Dolphin's three IPL.bin) was never checked."""
import hashlib # noqa: PLC0415
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
previous = os.getcwd()
os.chdir(directory)
self.addCleanup(os.chdir, previous)
files, shas = {}, {}
for rel, payload in (("bios/GC/USA/IPL.bin", b"usa"), ("bios/GC/JAP/IPL.bin", b"jap")):
Path(rel).parent.mkdir(parents=True, exist_ok=True)
Path(rel).write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
shas[rel] = sha1
files[sha1] = {"path": rel, "name": "IPL.bin", "size": len(payload), "sha1": sha1,
"md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000001"}
from scripts import generate_db # noqa: PLC0415
db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
by_md5 = {e["md5"]: s for s, e in files.items()}
self.assertEqual(
region_audit.resolve_sha1({"name": "IPL.bin", "md5": [files[shas["bios/GC/USA/IPL.bin"]]["md5"]]}, db),
shas["bios/GC/USA/IPL.bin"],
)
self.assertEqual(
region_audit.resolve_sha1({"name": "IPL.bin", "path": "GC/JAP/IPL.bin"}, db),
shas["bios/GC/JAP/IPL.bin"],
)
self.assertTrue(by_md5)
class ArchiveSecurityRegressions(unittest.TestCase):
@@ -1291,7 +1325,10 @@ class EveryManifestEntryIsFetchable(unittest.TestCase):
def test_an_unresolvable_file_is_recorded_as_omitted(self):
"""The reason must be one install.py knows how to report."""
allowed = {"hash_mismatch", "not_found", "external", "user_provided"}
allowed = {
"hash_mismatch", "not_found", "external", "user_provided",
"no_platform_slug",
}
for path in sorted((ROOT / "install").glob("*.json")):
with self.subTest(manifest=path.name):
data = json.loads(path.read_text())
+49
View File
@@ -0,0 +1,49 @@
"""The command line report judges "undeclared" on the flat declared set.
Per normalized system id, bk and elektronika-bk never met, nor nintendo-sgb
and nintendo-super-game-boy: 646 entries whose exact name a platform
declares were counted undeclared, while verify and the site read one flat
set, enriched with every spelling the database knows.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import load_database, load_emulator_profiles # noqa: E402
from cross_reference import ( # noqa: E402
_build_supplemental_index,
cross_reference,
declared_names,
load_platform_files,
)
class FlatDeclaredSet(unittest.TestCase):
def test_a_name_declared_under_another_system_id_is_not_a_gap(self):
if not (REPO_ROOT / "database.json").is_file():
self.skipTest("no database.json")
db = load_database(str(REPO_ROOT / "database.json"))
platforms = str(REPO_ROOT / "platforms")
profiles = {k: v for k, v in load_emulator_profiles(str(REPO_ROOT / "emulators")).items() if k == "bk"}
if not profiles:
self.skipTest("no bk profile")
declared, data_dirs = load_platform_files(platforms, ["batocera"])
names = _build_supplemental_index()
per_system = cross_reference(profiles, declared, db, data_dirs, names)
flat = cross_reference(
profiles, declared, db, data_dirs, names,
all_declared=declared_names(platforms, ["batocera"], db),
)
gaps = {g["name"] for g in flat["bk"]["gap_details"]}
self.assertNotIn("MONIT10.ROM", gaps)
self.assertLess(flat["bk"]["gaps"], per_system["bk"]["gaps"])
if __name__ == "__main__":
unittest.main()
+16
View File
@@ -271,5 +271,21 @@ class PathPriority(unittest.TestCase):
self.assertFalse(dedup._in_nodedup_dir("bios/Sony/PlayStation/scph.bin"))
class TheNoDedupListIsPinned(unittest.TestCase):
"""Every directory the dedup must leave alone, with the reason written
beside it in dedup.py. Only RPG Maker and ScummVM were tested; a merge
that dropped c64-emu would have the name step serve the C128 keymap to
three machines again, with the suite green."""
def test_every_tree_read_by_path_stays_undeduplicated(self):
from scripts.dedup import NODEDUP_DIRS # noqa: PLC0415
expected = {
"RPG Maker", "ScummVM", "tzdata", "c64-emu", "PinMAME", "BBK",
"C-Dogs SDL", "Ikemen GO", "Theme Hospital", "TheXTech", "Softdisk",
}
self.assertEqual(expected - set(NODEDUP_DIRS), set())
if __name__ == "__main__":
unittest.main()
+2 -2
View File
@@ -3833,7 +3833,7 @@ class TestE2E(unittest.TestCase):
}
],
}
copied, skipped = do_standalone_copies(manifest, bios_dir, "linux")
copied, skipped, _ = do_standalone_copies(manifest, bios_dir, "linux")
self.assertEqual(copied, 1)
self.assertEqual(skipped, 1)
self.assertTrue((yuzu_dir / "prod.keys").exists())
@@ -6356,7 +6356,7 @@ struct BurnDriver BurnDrvneogeo = {
db = self._gap_db("v4bios.zip", "a" * 40, "465c4e1c")
original = cross_reference.get_mame_clone_map
cross_reference.get_mame_clone_map = lambda: {"bctvidbs.zip": "v4bios.zip"}
cross_reference.get_mame_clone_map = lambda *_a: {"bctvidbs.zip": "v4bios.zip"}
try:
entry = {"name": "bctvidbs.zip", "required": True}
self.assertEqual(self._gap_source(entry, db), "bios")
+71
View File
@@ -355,5 +355,76 @@ class RetroPieProposals(unittest.TestCase):
self.assertIsNone(RetroPie._insertion_point(alternatives))
self.assertIsNotNone(RetroPie._insertion_point(enumeration))
class BizHawkRewritesOnlyWhatItDeclares(unittest.TestCase):
def test_a_truth_only_homonym_does_not_rewrite_a_declared_call(self):
"""opera's panafz10-norsa.bin, absent from BizHawk's list, would have
rewritten the 3DO call of the same name by index on the name alone."""
sha_bizhawk, sha_other = "4" * 40, "e" * 40
declared = NativeFile("bios.bin", "bios.bin", "SYSB", platform={"sha1": sha_bizhawk})
truth_only = NativeFile("bios.bin", "bios.bin", "sys-a", truth={"sha1": sha_other})
exporter = BizHawk()
source = f'File("{sha_bizhawk.upper()}", 10, "bios.bin")\n'
produced = exporter.render(
{"SYSB": NativeSystem("SYSB", files=[declared]),
"sys-a": NativeSystem("sys-a", files=[truth_only])},
None, {exporter.native_filename(): source},
)
self.assertIn(sha_bizhawk.upper(), produced[exporter.native_filename()])
self.assertNotIn(sha_other.upper(), produced[exporter.native_filename()])
class RetroDeckCorrectsOnlyWhatItCarries(unittest.TestCase):
def test_paths_and_description_stay_the_maintainers(self):
"""neogeo.zip lost its four search directories to a path that does
not exist, and 26 descriptions were rewritten."""
from collections import OrderedDict # noqa: PLC0415
existing = [{"filename": "neogeo.zip", "system": "neogeo", "md5": A,
"paths": ["$roms_path/neogeo", "$roms_path/fbneo", "$bios_path"],
"description": "Neo Geo BIOS"}]
ours = [OrderedDict([("filename", "neogeo.zip"), ("md5", B), ("system", "neogeo"),
("description", "generated"), ("paths", "$bios_path/roms/neogeo")])]
merged = RetroDeck._merge(existing, ours)
self.assertEqual(merged[0]["md5"], B)
self.assertEqual(merged[0]["paths"], ["$roms_path/neogeo", "$roms_path/fbneo", "$bios_path"])
self.assertEqual(merged[0]["description"], "Neo Geo BIOS")
def test_a_hash_no_revision_shares_is_appended(self):
"""IPL.n64 declared twice, both contradicted: counted corrected,
written nowhere."""
from collections import OrderedDict # noqa: PLC0415
existing = [{"filename": "IPL.n64", "system": "n64dd", "md5": A},
{"filename": "IPL.n64", "system": "n64dd", "md5": B}]
ours = [OrderedDict([("filename", "IPL.n64"), ("md5", C), ("system", "n64dd")])]
merged = RetroDeck._merge(existing, ours)
self.assertEqual([e["md5"] for e in merged], [A, B, C])
class BatoceraKeepsTheRunnerKeys(unittest.TestCase):
def test_emulator_and_core_travel_with_a_rewritten_entry(self):
"""checkBios skips a BIOS whose emulator or core is not installed;
rewritten without the keys, vectrex's MAME BIOS was wanted by every
build."""
from exporter.batocera_exporter import Exporter as Batocera # noqa: PLC0415
fe = NativeFile("bios.bin", "bios.bin", "vectrex", platform={"md5": A},
truth={"md5": B}, corrections=["md5"])
fe.native_data = {"native_path": "bios/bios.bin"}
system = NativeSystem("vectrex", files=[fe])
original = (
' "vectrex": { "name": "Vectrex", "emulator": "libretro", "core": "mame", '
'"biosFiles": [ { "md5": "' + A + '", "file": "bios/bios.bin", '
'"emulator": "libretro", "core": "mame" } ] },'
)
exporter = Batocera()
line = exporter._entry_line(system, [fe], exporter._parse_entry([original]))
parsed = exporter._parse_entry([line])["vectrex"]
self.assertEqual((parsed["emulator"], parsed["core"]), ("libretro", "mame"))
self.assertEqual(parsed["biosFiles"][0]["core"], "mame")
self.assertEqual(parsed["biosFiles"][0]["md5"], B)
if __name__ == "__main__":
unittest.main()
+16
View File
@@ -56,5 +56,21 @@ class ExtractTargets(unittest.TestCase):
self.assertNotIn('" | RetroDECK | `~/retrodeck/` |"', site)
class ThePackGuideNamesTheInstallerDefault(unittest.TestCase):
"""The pack README, the registry and install.py each wrote RetroBat's
default folder by hand, and two of the three said the home directory
where the installer (installer.iss) says C:\\RetroBat."""
def test_retrobat_guide_matches_the_registry(self):
registry = load_platform_registry(PLATFORMS)
first = next(
rule["path"] for rule in registry["retrobat"]["install"]["detect"]
if rule["os"] == "windows" and rule["method"] == "path_exists"
)
guide = (REPO_ROOT / "scripts" / "packreadme.py").read_text(encoding="utf-8")
self.assertEqual(first, "C:\\RetroBat\\bios")
self.assertIn("(default: C:\\\\RetroBat\\\\bios\\\\)", guide)
if __name__ == "__main__":
unittest.main()
+72
View File
@@ -0,0 +1,72 @@
"""A file in another emulator's tree does not answer for this one.
bios/Other/<emulator>/ holds what that emulator ships. The path step took a
shortened tail that only NetherSX2's tree answered as proof for EmuCoreX's
unlock.wav, and broke a full-tail tie between EmuCoreX's GameIndex.yaml and
the system directory's copy by index order, serving a fork's game database
to upstream PCSX2.
"""
from __future__ import annotations
import hashlib
import os
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import generate_db # noqa: E402
from common import resolve_local_file # noqa: E402
class ForeignTree(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self._cwd = os.getcwd()
os.chdir(self._tmp.name)
files = {}
for rel, payload in (
("bios/Other/forkemu/resources/GameIndex.yaml", b"fork db"),
("bios/Sony/PlayStation 2/resources/GameIndex.yaml", b"system copy"),
("bios/Other/forkemu/resources/sounds/unlock.wav", b"fork sound"),
):
path = Path(rel)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
files[sha1] = {
"path": rel, "name": path.name, "size": len(payload), "sha1": sha1,
"md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000001",
}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
def tearDown(self):
os.chdir(self._cwd)
self._tmp.cleanup()
def _resolve(self, owner: str, path: str):
entry = {"name": path.rsplit("/", 1)[-1], "path": path, "source_profile": owner}
return resolve_local_file(entry, self.db, {}, dest_hint=path)
def test_a_full_tail_tie_goes_to_the_system_directory(self):
local, status = self._resolve("pcsx2", "resources/GameIndex.yaml")
self.assertEqual(status, "path_exact")
self.assertTrue(local.startswith("bios/Sony/"), local)
def test_the_fork_still_gets_its_own_copy(self):
local, status = self._resolve("forkemu", "resources/GameIndex.yaml")
self.assertEqual(status, "path_exact")
self.assertTrue(local.startswith("bios/Other/forkemu/"), local)
def test_a_shortened_tail_in_a_foreign_tree_is_not_path_evidence(self):
_local, status = self._resolve("otheremu", "assets/sounds/unlock.wav")
self.assertNotEqual(status, "path_exact")
if __name__ == "__main__":
unittest.main()
+71 -7
View File
@@ -218,6 +218,15 @@ class TestDefaultsFollowTheRegistry(unittest.TestCase):
with self.subTest(platform=name, os=os_type):
self.assertIn(install._default_dest(os_type, name), candidates)
checked += 1
# The installer's own default comes first in the registry and
# is the one the pack guide and the installer name.
first = next(
(self._expand(rule["path"]) for rule in (entry.get("install") or {}).get("detect", [])
if rule.get("os") == "windows" and rule.get("method") == "path_exists"),
None,
)
if name == "retrobat":
self.assertEqual(install._default_dest("windows", name), first)
self.assertGreater(checked, 10)
@@ -789,7 +798,7 @@ class TestStandaloneCopiesExtraDirs(unittest.TestCase):
(bios / "ps2-0230a.bin").write_text("rom")
extra = Path(tempfile.mkdtemp()) / "bios"
extra.mkdir()
copied, _ = install.do_standalone_copies(
copied, _, _ = install.do_standalone_copies(
self._manifest(), bios, "windows", {"pcsx2": extra}
)
self.assertEqual(copied, 1)
@@ -801,7 +810,7 @@ class TestStandaloneCopiesExtraDirs(unittest.TestCase):
(bios / "GC" / "USA" / "IPL.bin").write_text("ipl")
user = Path(tempfile.mkdtemp()) / "User"
(user / "GC" / "USA").mkdir(parents=True)
copied, _ = install.do_standalone_copies(
copied, _, _ = install.do_standalone_copies(
self._manifest(), bios, "windows", {"dolphin": user}
)
self.assertEqual(copied, 1)
@@ -811,10 +820,64 @@ class TestStandaloneCopiesExtraDirs(unittest.TestCase):
bios = Path(tempfile.mkdtemp())
(bios / "ps2-0230a.bin").write_text("rom")
self.assertEqual(
install.do_standalone_copies(self._manifest(), bios, "windows"), (0, 0)
install.do_standalone_copies(self._manifest(), bios, "windows"), (0, 0, 0)
)
class InstallerStaysOnPython38AndTheStdlib(unittest.TestCase):
"""install.py runs where the bootstraps let it run: Python 3.8, stdlib only.
The suite runs on 3.12 with pyyaml installed, so a removeprefix() or an
import of yaml kept every test green and died at the user's first run,
after the bootstrap had told them 3.8 was enough.
"""
SOURCE = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
def test_the_grammar_is_python_38(self):
import ast # noqa: PLC0415
ast.parse(self.SOURCE, filename="install.py", feature_version=(3, 8))
def test_every_import_is_standard_library(self):
import ast # noqa: PLC0415
import sys # noqa: PLC0415
tree = ast.parse(self.SOURCE)
imported = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
imported.update(alias.name.split(".")[0] for alias in node.names)
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
imported.add(node.module.split(".")[0])
self.assertEqual(sorted(imported - set(sys.stdlib_module_names)), [])
def test_no_api_newer_than_38(self):
newer = (".removeprefix(", ".removesuffix(", "zoneinfo", "graphlib",
"functools.cache(", ".bit_count(", "math.lcm(", "math.nextafter(")
used = [api for api in newer if api in self.SOURCE]
self.assertEqual(used, [])
class ARefusedCopyIsAnError(unittest.TestCase):
"""A PermissionError on a standalone copy was counted as "dir not found"
and the run ended on Done with exit code 0."""
def test_a_refused_copy_is_counted_failed_not_skipped(self):
with tempfile.TemporaryDirectory() as tmp:
bios = Path(tmp, "bios")
bios.mkdir()
(bios / "prod.keys").write_bytes(b"k")
target = Path(tmp, "keys")
target.mkdir()
manifest = {"standalone_copies": [
{"file": "prod.keys", "targets": {"linux": [str(target)]}}
]}
with unittest.mock.patch("shutil.copy2", side_effect=PermissionError(13, "denied")):
result = install.do_standalone_copies(manifest, bios, "linux")
self.assertEqual(result, (0, 0, 1))
class TargetFlagIsAppliedOrRefused(unittest.TestCase):
"""A filter the installer cannot apply must stop it, not be ignored.
@@ -844,11 +907,12 @@ class TargetFlagIsAppliedOrRefused(unittest.TestCase):
for name in ("install/targets/retroarch.json",):
path = REPO_ROOT / name
if not path.is_file():
continue
self.skipTest(f"{name} not generated yet")
targets = json.loads(path.read_text())
for alias in ("switch", "ps2", "psp"):
if alias not in targets:
continue
# An alias the builder accepts and the manifest lacks is a
# documented word that fails on the installer's side.
self.assertIn(alias, targets, f"builder accepts --target {alias}")
with self.subTest(alias=alias):
resolved = load_target_config(
"retroarch", alias, str(REPO_ROOT / "platforms")
@@ -1007,7 +1071,7 @@ class TestStandaloneCopyTargetsAreUntrusted(unittest.TestCase):
outside.write_text("untouched")
(target / "boot.bin").symlink_to(outside)
copied, skipped = install.do_standalone_copies(
copied, skipped, _ = install.do_standalone_copies(
self._manifest(str(target)), bios, "linux"
)
self.assertEqual(copied, 0)
+16 -4
View File
@@ -48,13 +48,25 @@ def _registered_platforms() -> list[str]:
return list_registered_platforms(PLATFORMS_DIR, include_archived=True)
def _profiles_newer_than(path: str) -> list[str]:
"""Profiles modified after the pack was built."""
def _profiles_newer_than(path: str, platform_name: str) -> list[str]:
"""Profiles of this platform's emulators modified after the pack was built.
A profile the platform never resolves cannot have changed its pack: a
comment edited in any of 500 profiles used to disarm the integrity
check of every pack on disk.
"""
sys.path.insert(0, os.path.join(REPO_ROOT, "scripts"))
from common import load_emulator_profiles, load_platform_config, resolve_platform_cores
built = os.path.getmtime(path)
config = load_platform_config(platform_name, PLATFORMS_DIR)
profiles = load_emulator_profiles(EMULATORS_DIR)
relevant = resolve_platform_cores(config, profiles)
return [
os.path.basename(p)
for p in glob.glob(os.path.join(EMULATORS_DIR, "*.yml"))
if os.path.getmtime(p) > built
if os.path.splitext(os.path.basename(p))[0] in relevant
and os.path.getmtime(p) > built
]
@@ -87,7 +99,7 @@ class PackIntegrityTest(unittest.TestCase):
# goes red depends on how far along that build is.
if "is in use by another run" in (result.stdout + result.stderr):
self.skipTest(f"dist/ is being written; {platform_name} not verifiable")
newer = _profiles_newer_than(pack)
newer = _profiles_newer_than(pack, platform_name)
if newer:
self.skipTest(
f"{platform_name} pack predates {len(newer)} profile(s) "
+14
View File
@@ -353,6 +353,20 @@ class TestProvenanceReport(unittest.TestCase):
report["redump"]["covered_dats"], ["Sony - PlayStation - BIOS Images"]
)
def test_a_loose_file_matches_by_crc32_and_size(self):
"""Redump lists DTL-H10100's image by crc32 alone; collected as a
loose file it stayed MISSING for good."""
db = {"files": {"f" * 40: {"name": "ps2-0101jd.bin", "size": 4194304,
"md5": "e" * 32, "crc32": "E9836A0C"}}}
snapshots = {"redump": {"source": "redump", "imported_at": "2026-10-09",
"dats": {"PS2 BIOS": "2026-10-01"},
"entries": [{"name": "ps2-0101jd.bin", "size": 4194304,
"crc32": "e9836a0c", "md5": "", "sha1": "",
"dat": "PS2 BIOS"}]}}
report = build_report(db, snapshots)
self.assertEqual(report["redump"]["matched"], 1)
self.assertEqual(report["redump"]["missing"], [])
def test_uncovered_dat_entries_are_out_of_scope(self):
"""Entries from a DAT the collection never matches are not targets.
+67
View File
@@ -0,0 +1,67 @@
"""A step that could not do its job says so with its exit code.
validate_pr --changed answered "No changed BIOS files detected" and exited
0 when git itself had failed (no origin, or no repository), and
check_buildbot_system --update exited 0 after a refresh it launched had
failed, the cache left at the old version.
"""
from __future__ import annotations
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import check_buildbot_system # noqa: E402
class GitThatCannotAnswer(unittest.TestCase):
def test_outside_a_repository_is_an_error_not_an_empty_change(self):
with tempfile.TemporaryDirectory() as tmp:
proc = subprocess.run(
[sys.executable, str(REPO_ROOT / "scripts" / "validate_pr.py"), "--changed"],
cwd=tmp, capture_output=True, text=True, timeout=60, check=False,
)
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
self.assertNotIn("No changed BIOS files detected", proc.stdout)
def test_a_clone_without_origin_reads_the_index_and_says_so(self):
with tempfile.TemporaryDirectory() as tmp:
subprocess.run(["git", "init", "-q", tmp], check=True)
Path(tmp, "bios").mkdir()
Path(tmp, "bios", "x.bin").write_bytes(b"x")
subprocess.run(["git", "-C", tmp, "add", "bios/x.bin"], check=True)
proc = subprocess.run(
[sys.executable, "-c", (
f"import sys; sys.path.insert(0, {str(REPO_ROOT / 'scripts')!r}); "
"import validate_pr; print(validate_pr.get_changed_files())"
)],
cwd=tmp, capture_output=True, text=True, timeout=60, check=False,
)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("bios/x.bin", proc.stdout)
self.assertIn("staged changes only", proc.stderr)
class RefreshFailuresAreFailures(unittest.TestCase):
def test_a_failed_refresh_is_returned_and_exits_nonzero(self):
report = {"entries": [{"status": "UPDATED", "key": "dolphin-sys"},
{"status": "OK", "key": "ppsspp-assets"}]}
with mock.patch.object(
check_buildbot_system.subprocess, "run",
return_value=subprocess.CompletedProcess([], 1),
):
self.assertEqual(check_buildbot_system.update_changed(report), ["dolphin-sys"])
source = (REPO_ROOT / "scripts" / "check_buildbot_system.py").read_text(encoding="utf-8")
self.assertIn("failed = update_changed(report)", source)
self.assertIn("sys.exit(1)", source[source.index("failed = update_changed(report)"):])
if __name__ == "__main__":
unittest.main()
+22 -6
View File
@@ -436,26 +436,32 @@ class TestVerifyModesHonourRegion(unittest.TestCase):
"""
def _run(self, *args: str) -> str:
"""The report, from a run that succeeded.
A run that crashed produced an empty stdout, different from the
plain report, and the test read the difference as narrowing.
"""
import subprocess
repo = os.path.join(os.path.dirname(__file__), "..")
return subprocess.run(
completed = subprocess.run(
[sys.executable, "scripts/verify.py", *args],
capture_output=True, text=True, cwd=repo, timeout=900,
).stdout
capture_output=True, text=True, cwd=repo, timeout=900, check=False,
)
self.assertEqual(completed.returncode, 0, completed.stderr[-2000:])
self.assertTrue(completed.stdout.strip(), "an empty report is not a report")
return completed.stdout
def test_emulator_mode_narrows(self):
plain = self._run("--emulator", "duckstation")
filtered = self._run("--emulator", "duckstation", "--region", "us")
if "duckstation" not in plain:
self.skipTest("duckstation profile not present")
filtered = self._run("--emulator", "duckstation", "--region", "us")
self.assertNotEqual(plain, filtered)
def test_system_mode_narrows(self):
plain = self._run("--system", "sony-playstation")
filtered = self._run("--system", "sony-playstation", "--region", "us")
if not plain.strip():
self.skipTest("system not present")
self.assertNotEqual(plain, filtered)
@@ -576,6 +582,16 @@ class TestReportAndBuilderNarrowTogether(unittest.TestCase):
"or emulator_region_drops",
)
def test_the_verifier_reads_the_grouping_where_it_lives(self):
"""Imported through the builder's CLI module, the verdict of a
--region run depended on that module compiling, and seven scripts
entered the site workflow's trigger paths for one function."""
source = (Path(__file__).resolve().parent.parent / "scripts" / "verify.py").read_text(
encoding="utf-8"
)
self.assertNotIn("from generate_pack import platform_region_groups", source)
self.assertIn("from packextras import platform_region_groups", source)
def test_emulator_report_keeps_what_the_emulator_pack_keeps(self):
"""O2EM: --region us left the Videopac BIOSes in the pack, not in the report."""
repo = Path(__file__).resolve().parent.parent
+103
View File
@@ -0,0 +1,103 @@
"""A required declaration is never silenced by its optional twin.
RetroDECK declares the four Atari 5200 ROMs optional under atari-400-800 and
required under atari-5200, same md5. The preferred declaration for a shared
destination was chosen over the full set, then applied to a loop that had
already dropped the optional entries: under --required-only the preferred
(optional) entry was skipped for being optional and the required one for not
being preferred, and the destination left the pack, the status counts and
the manifest without a trace.
"""
from __future__ import annotations
import hashlib
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
import generate_db # noqa: E402
import generate_pack as builder # noqa: E402
class RequiredTwins(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
self.bios = self.root / "bios" / "Atari"
self.platforms = self.root / "platforms"
self.emulators = self.root / "emulators"
for directory in (self.bios, self.platforms, self.emulators):
directory.mkdir(parents=True)
payload = b"atari os"
(self.bios / "ATARIOSB.ROM").write_bytes(payload)
md5 = hashlib.md5(payload).hexdigest()
sha1 = hashlib.sha1(payload).hexdigest()
files = {
sha1: {
"path": str(self.bios / "ATARIOSB.ROM"), "name": "ATARIOSB.ROM",
"size": len(payload), "sha1": sha1, "md5": md5,
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000008",
}
}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
platform = {
"platform": "Twins",
"verification_mode": "md5",
"base_destination": "bios",
"cores": [],
"systems": {
# Sorted first: the optional declaration is the preferred one.
"atari-400-800": {"files": [
{"name": "ATARIOSB.ROM", "destination": "ATARIOSB.ROM",
"md5": md5, "required": False},
]},
"atari-5200": {"files": [
{"name": "ATARIOSB.ROM", "destination": "ATARIOSB.ROM",
"md5": md5, "required": True},
]},
},
}
(self.platforms / "twins.yml").write_text(yaml.dump(platform))
(self.platforms / "_registry.yml").write_text(
yaml.dump({"platforms": {"twins": {"status": "active"}}})
)
common._platform_config_cache.clear()
common._emulator_profiles_cache.clear()
def tearDown(self):
common._platform_config_cache.clear()
self._tmp.cleanup()
def test_the_required_only_pack_carries_the_required_file(self):
out = self.root / "dist"
out.mkdir()
zip_path = builder.generate_pack(
"twins", str(self.platforms), self.db, str(self.root / "bios"), str(out),
emulators_dir=str(self.emulators), emu_profiles={},
required_only=True, offline=True,
)
with zipfile.ZipFile(zip_path) as archive:
self.assertIn("ATARIOSB.ROM", archive.namelist())
def test_the_required_only_manifest_lists_it(self):
manifest = builder.generate_manifest(
"twins", str(self.platforms), self.db, str(self.root / "bios"),
str(self.platforms / "_registry.yml"),
emulators_dir=str(self.emulators), emu_profiles={},
required_only=True, offline=True,
)
listed = {entry["dest"] for entry in manifest["files"]}
self.assertIn("ATARIOSB.ROM", listed)
if __name__ == "__main__":
unittest.main()
+96
View File
@@ -0,0 +1,96 @@
"""A shared group adds files, never a second name for a native one.
The quasi88 group declared N88SUB.ROM beside the disk.rom System.dat
already names for nec-pc-88, same bytes: the RetroArch pack carried the
2 KB ROM twice under quasi88/, and the group read as twelve required files
where four did the work. Alternative names are aliases in the profile.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import list_registered_platforms, load_platform_config # noqa: E402
PLATFORMS = str(REPO_ROOT / "platforms")
def _md5s(entry: dict) -> set[str]:
raw = entry.get("md5") or ""
values = raw if isinstance(raw, list) else str(raw).split(",")
return {v.strip().lower() for v in values if v and v.strip()}
class SharedGroupsAddOnly(unittest.TestCase):
def test_no_shared_file_repeats_a_native_file_of_the_same_directory(self):
shared = yaml.safe_load((REPO_ROOT / "platforms" / "_shared.yml").read_text())
groups = shared.get("shared_groups") or {}
repeated = []
for platform in list_registered_platforms(PLATFORMS, include_archived=True):
config = load_platform_config(platform, PLATFORMS)
for system in config.get("systems", {}).values():
included = [
fe for group in system.get("includes", []) for fe in groups.get(group, [])
]
if not included:
continue
group_keys = {
(fe.get("name"), fe.get("destination", fe.get("name"))) for fe in included
}
native: dict[tuple[str, str], str] = {}
for fe in system.get("files", []):
if (fe.get("name"), fe.get("destination", fe.get("name"))) in group_keys:
continue
dest = fe.get("destination") or fe.get("name", "")
directory = dest.rsplit("/", 1)[0] if "/" in dest else ""
for md5 in _md5s(fe):
native.setdefault((directory, md5), fe.get("name", ""))
for fe in included:
dest = fe.get("destination") or fe.get("name", "")
directory = dest.rsplit("/", 1)[0] if "/" in dest else ""
for md5 in _md5s(fe):
other = native.get((directory, md5))
if other and other.lower() != fe.get("name", "").lower():
repeated.append(f"{platform}:{fe['name']} = {other}")
self.assertEqual(repeated, [])
class ADeclaredAliasSettlesTheRequirement(unittest.TestCase):
"""quasi88 reads n88sub.rom or disk.rom; System.dat names disk.rom. The
gap pass compared the entry's name alone and the pack carried the same
2 KB ROM a second time as quasi88/n88sub.rom."""
def test_a_platform_naming_an_alias_has_met_the_entry(self):
import os # noqa: PLC0415
import tempfile # noqa: PLC0415
from common import _emulator_profiles_cache # noqa: PLC0415
from verify import find_undeclared_files # noqa: PLC0415
with tempfile.TemporaryDirectory() as tmp:
Path(tmp, "emulators").mkdir()
Path(tmp, "emulators", "quasi88.yml").write_text(
"emulator: QUASI88\ntype: libretro\nsystems: [nec-pc-88]\ncores: [quasi88]\n"
"files:\n - name: n88sub.rom\n aliases: [N88SUB.ROM, disk.rom]\n"
" required: true\n"
)
_emulator_profiles_cache.clear()
self.addCleanup(_emulator_profiles_cache.clear)
config = {"platform": "P", "verification_mode": "existence", "cores": "all_libretro",
"systems": {"nec-pc-88": {"files": [{"name": "disk.rom", "destination": "quasi88/disk.rom"}]}}}
db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}, "by_crc32": {}, "by_path_suffix": {}}}
previous = os.getcwd()
os.chdir(tmp)
self.addCleanup(os.chdir, previous)
undeclared = find_undeclared_files(config, "emulators", db, data_names=set())
self.assertEqual([u["name"] for u in undeclared], [])
if __name__ == "__main__":
unittest.main()
+40
View File
@@ -157,6 +157,46 @@ class TestConflicts(unittest.TestCase):
conflicts = slots.find_conflicts(self._config("m" * 32), profile, REGIONS_DB)
self.assertEqual(conflicts, [])
def test_a_platform_list_naming_the_profile_file_is_agreement(self):
"""Recalbox names both scph1001.bin revisions; the resolver stops on
the first, the profile pins the other, and --strict reported a
contradiction nobody has to correct."""
conflicts = slots.find_conflicts(
self._config(f"{'m' * 32},{'n' * 32}"),
self._profile("GC/JAP/IPL.bin"),
REGIONS_DB,
)
self.assertEqual(conflicts, [])
def test_a_profile_list_naming_the_shipped_file_is_agreement(self):
"""mesence accepts four BS-X dumps, one of which System.dat pins;
the slot read as contested and the pack swapped the catalogued
dump for an uncatalogued one."""
profile = {
"mesence": {
"files": [
{"name": "IPL.bin", "path": "GC/JAP/IPL.bin",
"md5": [ "n" * 32, "m" * 32 ]},
]
}
}
conflicts = slots.find_conflicts(self._config("m" * 32), profile, REGIONS_DB)
self.assertEqual(conflicts, [])
def test_an_entry_the_pack_never_carries_claims_nothing(self):
"""A file read from the save directory, declared with the sha1 of
another dump, arbitrated the platform's file off its own path."""
profile = {
"core": {
"files": [
{"name": "IPL.bin", "path": "GC/JAP/IPL.bin", "md5": "n" * 32,
"load_from": "save_dir"},
]
}
}
conflicts = slots.find_conflicts(self._config("m" * 32), profile, REGIONS_DB)
self.assertEqual(conflicts, [])
def test_a_name_only_resolution_asserts_nothing(self):
# No hash and no path that the repository carries: the entry is
# answered by filename alone and cannot contradict anything.
+93
View File
@@ -0,0 +1,93 @@
"""One system, two spellings, one pack.
stella2014 and stella2023 write atari_2600 where every other profile writes
atari-2600. --system matched the spelling exactly and named the ZIP from
it, so the two spellings built two different packs under one file name,
the second replacing the first; --from-md5 --emulator X --standalone
accepted a libretro-only core and named a standalone pack that was the
libretro pack.
"""
from __future__ import annotations
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
import generate_pack as builder # noqa: E402
class SpellingsAgree(unittest.TestCase):
def test_both_spellings_select_the_same_profiles(self):
profiles = common.load_emulator_profiles(str(REPO_ROOT / "emulators"))
dashed = common.profiles_for_systems(profiles, ["atari-2600"])
underscored = common.profiles_for_systems(profiles, ["atari_2600"])
self.assertEqual(dashed, underscored)
self.assertIn("stella2014", dashed)
self.assertIn("gopher2600", dashed)
def test_the_pack_name_normalizes_the_spelling(self):
self.assertEqual(
builder._system_display_name("atari_2600"), builder._system_display_name("atari-2600")
)
self.assertEqual(builder._system_display_name("msxturboR"), builder._system_display_name("msxturbor"))
class TheListingCountsLikeTheSelection(unittest.TestCase):
def test_one_system_is_listed_once_with_every_profile(self):
import contextlib # noqa: PLC0415
import io # noqa: PLC0415
out = io.StringIO()
with contextlib.redirect_stdout(out):
common.list_system_ids(str(REPO_ROOT / "emulators"))
lines = {line.split()[0]: line for line in out.getvalue().splitlines() if line.strip()}
profiles = common.load_emulator_profiles(str(REPO_ROOT / "emulators"))
for spellings in (("3do", "panasonic-3do"), ("atari-2600", "atari_2600")):
listed = [s for s in spellings if s in lines]
self.assertEqual(len(listed), 1, f"{spellings}: one line per system")
count = len(common.profiles_for_systems(profiles, [spellings[0]]))
self.assertIn(f"({count} emulator", lines[listed[0]])
class StandaloneNeedsAStandaloneBuild(unittest.TestCase):
def test_a_custom_pack_refuses_it_for_a_libretro_core(self):
with tempfile.TemporaryDirectory() as tmp:
Path(tmp, "emulators").mkdir()
Path(tmp, "emulators", "handy.yml").write_text(
"emulator: Handy\ntype: libretro\nsystems: [atari-lynx]\nfiles:\n - name: lynxboot.img\n"
)
common._emulator_profiles_cache.clear()
self.addCleanup(common._emulator_profiles_cache.clear)
db = {"files": {}, "indexes": {"by_md5": {}, "by_crc32": {}, "by_name": {}}}
result = builder.generate_md5_pack(
[("md5", "d8f1206299c48946e6ec5ef96d014eaa")], db, tmp, tmp,
emulator_name="handy", emulators_dir=str(Path(tmp, "emulators")), standalone=True,
)
self.assertIsNone(result)
self.assertEqual([p for p in os.listdir(tmp) if p.endswith(".zip")], [])
class ADeclinedBuildIsAFailure(unittest.TestCase):
def test_a_target_that_removes_every_requested_system_exits_nonzero(self):
with tempfile.TemporaryDirectory() as tmp:
proc = subprocess.run(
[sys.executable, "scripts/generate_pack.py", "--platform", "retroarch",
"--system", "sony-playstation-2", "--target", "switch", "--offline",
"--output-dir", tmp],
capture_output=True, text=True, cwd=str(REPO_ROOT), timeout=600, check=False,
)
self.assertNotEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertNotIn("Traceback", proc.stderr, proc.stderr)
self.assertEqual([p for p in os.listdir(tmp) if p.endswith(".zip")], [])
if __name__ == "__main__":
unittest.main()
+83
View File
@@ -0,0 +1,83 @@
"""A destination two systems declare with two hashes ships one file.
RetroDECK declares bios/disk.rom for pc88 (the N88SUB ROM) and for coco (the
CoCo disk ROM), and bios/cdibios.zip for two components with two md5. The
pack holds one file per path; verify resolved each declaration to its own
file and counted both OK, so three green tools described a pack whose
frontend hashes bios/disk.rom for xroar and finds the PC-88 ROM.
"""
from __future__ import annotations
import hashlib
import os
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
import generate_db # noqa: E402
from verify import verify_platform # noqa: E402
class TwinDeclarations(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self._cwd = os.getcwd()
os.chdir(self._tmp.name)
files, self.md5 = {}, {}
for rel, payload in (
("bios/NEC/PC-98/N88SUB.ROM", b"pc88 disk rom"),
("bios/Tandy/CoCo/disk.rom", b"coco disk rom"),
):
path = Path(rel)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
self.md5[rel] = hashlib.md5(payload).hexdigest()
files[sha1] = {
"path": rel, "name": path.name, "size": len(payload), "sha1": sha1,
"md5": self.md5[rel], "sha256": hashlib.sha256(payload).hexdigest(),
"crc32": "00000001",
}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
self.emulators = Path(self._tmp.name) / "emulators"
self.emulators.mkdir()
common._emulator_profiles_cache.clear()
def tearDown(self):
common._emulator_profiles_cache.clear()
os.chdir(self._cwd)
self._tmp.cleanup()
def test_the_losing_declaration_is_scored_against_the_shipped_file(self):
config = {
"platform": "Twins",
"verification_mode": "md5",
"base_destination": "bios",
"cores": [],
"systems": {
"pc88": {"files": [{"name": "N88SUB.ROM", "destination": "disk.rom",
"md5": self.md5["bios/NEC/PC-98/N88SUB.ROM"]}]},
"coco": {"files": [{"name": "disk.rom", "destination": "disk.rom",
"md5": self.md5["bios/Tandy/CoCo/disk.rom"]}]},
},
}
report = verify_platform(
config, self.db, str(self.emulators), {}, supplemental_names=set()
)
by_system = {d["system"]: d for d in report["details"] if d["name"] in ("N88SUB.ROM", "disk.rom")}
statuses = sorted(d["status"] for d in by_system.values())
self.assertEqual(statuses.count("ok"), 1, by_system)
loser = next(d for d in by_system.values() if d["status"] != "ok")
self.assertIn("is not met at the path", loser.get("discrepancy", ""))
# The path counts once, for the file it ships; the loser is a detail.
self.assertEqual(report["status_counts"], {"ok": 1})
if __name__ == "__main__":
unittest.main()
+141
View File
@@ -0,0 +1,141 @@
"""A core file the platform cannot place is not in the pack.
RomM files every BIOS under the slug of its system and knows no slug for
DOS or for the arcade sets MAME 2003 reads. The builder skipped those
entries without a word; the report counted them "in pack" and the manifest
listed them neither in files nor in omitted_files.
"""
from __future__ import annotations
import hashlib
import os
import sys
import tempfile
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
import generate_db # noqa: E402
import generate_pack as builder # noqa: E402
from packextras import platform_region_groups # noqa: E402
from verify import verify_platform # noqa: E402
PROFILE = """\
emulator: Dosbox
type: libretro
systems: [dos]
cores: [dosbox]
files:
- name: MT32_CONTROL.ROM
required: false
"""
class SlugPlatform(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self._cwd = os.getcwd()
os.chdir(self._tmp.name)
self.emulators = Path("emulators")
self.platforms = Path("platforms")
for d in (self.emulators, self.platforms, Path("bios/Roland"), Path("bios/Sony")):
d.mkdir(parents=True)
files = {}
for rel, payload in (("bios/Roland/MT32_CONTROL.ROM", b"mt32"), ("bios/Sony/scph5501.bin", b"psx")):
Path(rel).write_bytes(payload)
sha1 = hashlib.sha1(payload).hexdigest()
files[sha1] = {
"path": rel, "name": Path(rel).name, "size": len(payload), "sha1": sha1,
"md5": hashlib.md5(payload).hexdigest(),
"sha256": hashlib.sha256(payload).hexdigest(), "crc32": "00000001",
}
self.db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
self.config = {
"platform": "Slugs",
"verification_mode": "md5",
"base_destination": "bios",
"cores": ["dosbox"],
"systems": {
"psx": {"files": [{"name": "scph5501.bin", "destination": "psx/scph5501.bin",
"md5": hashlib.md5(b"psx").hexdigest()}]},
"ps2": {"files": [{"name": "scph.bin", "destination": "ps2/scph.bin",
"md5": "0" * 32}]},
"snes": {"files": [{"name": "s.bin", "destination": "snes/s.bin", "md5": "1" * 32}]},
},
}
(self.platforms / "slugs.yml").write_text(yaml.dump(self.config))
(self.platforms / "_registry.yml").write_text(
yaml.dump({"platforms": {"slugs": {"status": "active"}}})
)
(self.emulators / "dosbox.yml").write_text(PROFILE)
common._platform_config_cache.clear()
common._emulator_profiles_cache.clear()
self.profiles = common.load_emulator_profiles("emulators")
def tearDown(self):
common._platform_config_cache.clear()
common._emulator_profiles_cache.clear()
os.chdir(self._cwd)
self._tmp.cleanup()
def test_the_report_does_not_count_it_packed(self):
report = verify_platform(
self.config, self.db, "emulators", self.profiles, supplemental_names=set()
)
entry = next(u for u in report["undeclared_files"] if u["name"] == "MT32_CONTROL.ROM")
self.assertTrue(entry["in_repo"])
self.assertFalse(entry.get("in_pack", True))
def test_the_manifest_says_why_it_is_absent(self):
manifest = builder.generate_manifest(
"slugs", "platforms", self.db, "bios", "platforms/_registry.yml",
emulators_dir="emulators", emu_profiles=self.profiles, offline=True,
)
names = {entry["dest"] for entry in manifest["files"]}
self.assertFalse(any("MT32_CONTROL" in n for n in names))
omitted = [o for o in manifest["omitted_files"] if o["name"] == "MT32_CONTROL.ROM"]
self.assertEqual([o["reason"] for o in omitted], ["no_platform_slug"])
class RegionKeysForEveryClaimant(unittest.TestCase):
"""Two profiles claiming one destination: the second, deduplicated by
the collector, had no key in the region map, and the report kept it
after the builder withdrew the destination."""
def test_both_claimants_are_keyed(self):
with tempfile.TemporaryDirectory() as tmp:
previous = os.getcwd()
os.chdir(tmp)
self.addCleanup(os.chdir, previous)
Path("emulators").mkdir()
Path("bios").mkdir()
Path("bios/jp.bin").write_bytes(b"jp")
sha1 = hashlib.sha1(b"jp").hexdigest()
files = {sha1: {"path": "bios/jp.bin", "name": "jp.bin", "size": 2, "sha1": sha1,
"md5": hashlib.md5(b"jp").hexdigest(),
"sha256": hashlib.sha256(b"jp").hexdigest(), "crc32": "00000001"}}
db = {"files": files, "indexes": generate_db.build_indexes(files, {})}
for name, extra in (("one", " system: sys\n"), ("two", "")):
Path(f"emulators/{name}.yml").write_text(
f"emulator: {name}\ntype: libretro\nsystems: [sys]\ncores: [{name}]\n"
f"files:\n - name: jp.bin\n region: [japan]\n{extra}"
)
common._emulator_profiles_cache.clear()
profiles = common.load_emulator_profiles("emulators")
config = {"platform": "P", "verification_mode": "existence", "cores": ["one", "two"],
"systems": {"sys": {"files": []}}}
_groups, extra_dests = platform_region_groups(
config, config["systems"], "emulators", db, "", profiles
)
claimants = {key[0] for key, dest in extra_dests.items() if dest == "jp.bin"}
self.assertEqual(claimants, {"one", "two"}, extra_dests)
if __name__ == "__main__":
unittest.main()