Author SHA1 Message Date
Abdessamad Derraz c4599cd69e fix: cite pcsx2 bundled resources at the pin 2026-10-09 17:56:02 +02:00
Abdessamad Derraz 913ba09600 chore: hoist test imports to module level 2026-10-06 13:59:50 +02:00
Abdessamad Derraz 38655d1d23 refactor: split helpers out of grown functions 2026-10-06 13:59:50 +02:00
Abdessamad Derraz 9af89cb120 refactor: share the gap scope rule 2026-10-06 13:59:50 +02:00
Abdessamad Derraz fbc92c99cf chore: regenerate database, manifests and readme 2026-10-06 13:06:27 +02:00
Abdessamad Derraz 49b3cffa58 fix: cite lrps2 bios loaders at the pin 2026-10-06 13:01:09 +02:00
Abdessamad Derraz af4f980bab fix: cite pokemini's bios reload at the pin 2026-10-06 13:00:09 +02:00
Abdessamad Derraz 3582101af4 fix: cite the msx-emu fdc mapper at the pin 2026-10-06 13:00:00 +02:00
Abdessamad Derraz a4f3f016f8 fix: cite the casloopy rom block at the pin 2026-10-06 12:59:43 +02:00
Abdessamad Derraz a252e2d158 fix: a cited range past the file end is gone 2026-10-06 12:59:23 +02:00
Abdessamad Derraz 067d962210 fix: cite dolphin's NAND import where it is 2026-10-06 12:59:23 +02:00
Abdessamad Derraz 0d81f19831 fix: pin kronos on the branch the buildbot builds 2026-10-06 12:56:44 +02:00
Abdessamad Derraz 52d128bcf5 fix: name a file's readers by resolution 2026-10-06 12:55:41 +02:00
Abdessamad Derraz 0b46880604 fix: show unsourceable entries apart on the site 2026-10-06 12:53:10 +02:00
Abdessamad Derraz 0f293b6c14 fix: slot emulator entries where the pack puts them 2026-10-06 12:52:24 +02:00
Abdessamad Derraz 9ca14bb77e fix: keep save and content files out of the truth 2026-10-06 12:48:58 +02:00
Abdessamad Derraz 443639c33d fix: cross-reference the build a platform runs 2026-10-06 12:47:50 +02:00
Abdessamad Derraz 0ea8840f14 fix: default each platform to the folder it reads 2026-10-06 12:46:35 +02:00
Abdessamad Derraz 53c5504330 fix: read extract folders from the registry 2026-10-06 12:44:32 +02:00
Abdessamad Derraz 7164e8a125 fix: an untagged bios serves every region 2026-10-06 12:43:09 +02:00
Abdessamad Derraz cea6e516a4 fix: unmark files of standalone-only profiles 2026-10-06 12:39:34 +02:00
Abdessamad Derraz 8d2df0b651 fix: lock the pack directory while splitting 2026-10-06 12:34:31 +02:00
Abdessamad Derraz b20c38d1c1 fix: hold dist/ for the whole pipeline run 2026-10-06 12:33:59 +02:00
Abdessamad Derraz 45ede873b3 fix: refetch a large file the release replaced 2026-10-06 12:30:51 +02:00
Abdessamad Derraz 14e4dd919c fix: check targeted packs the way --all builds 2026-10-06 12:29:40 +02:00
Abdessamad Derraz dc5cdb8d70 docs: split parts are named, not renamed 2026-10-06 12:29:10 +02:00
Abdessamad Derraz 87ef905c44 fix: name split parts directly, version their dir 2026-10-06 12:29:03 +02:00
Abdessamad Derraz e3173b1fc5 fix: drop the --include-extras flag nothing read 2026-10-06 12:27:21 +02:00
Abdessamad Derraz 514becd784 fix: run every scraper contract class directly 2026-10-06 12:27:11 +02:00
Abdessamad Derraz 0cc6ab5990 fix: refuse --include-archived without --all 2026-10-06 12:26:47 +02:00
Abdessamad Derraz 4609102772 fix: fail the fileless check on real errors 2026-10-06 12:25:18 +02:00
Abdessamad Derraz 92594b1751 fix: read profile tree status from its own repo 2026-10-06 12:24:31 +02:00
Abdessamad Derraz 2499a0eabc fix: refuse a libretro scrape without core info 2026-10-06 12:23:48 +02:00
Abdessamad Derraz 91e113a7d3 fix: refuse an emudeck scrape with no cores 2026-10-06 12:23:48 +02:00
Abdessamad Derraz e723f346f9 fix: refuse batocera targets with unread boards 2026-10-06 12:22:51 +02:00
Abdessamad Derraz 139bb99088 fix: stop a scrape whose release tag is unread 2026-10-06 12:22:51 +02:00
Abdessamad Derraz e35aa9a323 fix: quote system.dat names as the original does 2026-10-06 12:16:23 +02:00
Abdessamad Derraz be2c325b53 fix: leave bizhawk's declared sha1 to bizhawk 2026-10-06 12:15:50 +02:00
Abdessamad Derraz b39b187243 fix: refuse a one-md5 addition of several accepted 2026-10-06 12:15:49 +02:00
Abdessamad Derraz 6380a92e6c fix: never pair files whose contents or dirs differ 2026-10-06 12:15:40 +02:00
Abdessamad Derraz 22f018995e feat: collect emucorex and dojo bundled resources 2026-10-06 12:13:27 +02:00
Abdessamad Derraz 5d5170dc12 fix: prefer the owner's copy among equal path tails 2026-10-06 12:13:27 +02:00
Abdessamad Derraz 64ed737ec6 fix: rank the owner's copy above shared tree names 2026-10-06 12:09:51 +02:00
Abdessamad Derraz 87addda6e1 fix: look inside every zipped_file archive 2026-10-06 12:07:34 +02:00
Abdessamad Derraz b8a7aa74ff fix: report contradicted hashes in existence verify 2026-10-06 12:05:56 +02:00
Abdessamad Derraz d4ae1a76e8 fix: state no size for several accepted sizes 2026-10-06 12:04:24 +02:00
Abdessamad Derraz eb8c7e5267 refactor: split this round's fixes into small helpers 2026-10-06 11:16:06 +02:00
Abdessamad Derraz 6748982d94 fix: count variant choices made through validated_choice 2026-10-06 10:23:20 +02:00
Abdessamad Derraz e335511b72 chore: regenerate database, manifests and readme 2026-10-06 09:59:51 +02:00
Abdessamad Derraz 3088db7088 fix: count core archives among the core files 2026-10-06 09:54:33 +02:00
Abdessamad Derraz 204637cfeb fix: pack undistributed core extras in a split part 2026-10-06 09:53:09 +02:00
Abdessamad Derraz 519c02646a fix: compute coverage from the profiles the site reads 2026-10-06 09:48:15 +02:00
Abdessamad Derraz 72e47becb4 refactor: write one contributing text for both pages 2026-10-06 09:47:09 +02:00
Abdessamad Derraz 4a1a9dd861 docs: name jsonschema where the build needs it 2026-10-06 09:46:10 +02:00
Abdessamad Derraz d610aad990 fix: require the jsonschema format checkers 2026-10-06 09:45:25 +02:00
Abdessamad Derraz a0af1b141e fix: diff every declaration of a repeated destination 2026-10-06 09:43:58 +02:00
Abdessamad Derraz 4d5340c5f4 fix: count site cores with the builder's resolution 2026-10-06 09:43:38 +02:00
Abdessamad Derraz 4923873a7f fix: refuse an unknown mame or fbneo version 2026-10-06 09:42:33 +02:00
Abdessamad Derraz ced7588d17 fix: count mister corrections only where it writes 2026-10-06 09:41:53 +02:00
Abdessamad Derraz 44cf638e06 fix: count bizhawk corrections only where it writes 2026-10-06 09:41:28 +02:00
Abdessamad Derraz a9b33484ed fix: write emudeck corrections, count only what lands 2026-10-06 09:41:04 +02:00
Abdessamad Derraz 39d02b3b18 fix: never let a romm addition replace their entry 2026-10-06 09:40:09 +02:00
Abdessamad Derraz e5420a9dba fix: keep platform hashes the truth does not contradict 2026-10-06 09:39:07 +02:00
Abdessamad Derraz 9604e933ed fix: recale prose onto the code the pins carry 2026-10-06 09:38:27 +02:00
Abdessamad Derraz dcc12e62b7 fix: tag regions only where the code picks by area 2026-10-06 09:37:44 +02:00
Abdessamad Derraz 25e8eeb37b fix: read gaps by normalized system, sha256, file names 2026-10-06 09:32:32 +02:00
Abdessamad Derraz 5d5befc002 fix: keep refs for another version out of the pin check 2026-10-06 09:28:51 +02:00
Abdessamad Derraz c4e16e583e fix: realign stale prose before a pin moves 2026-10-06 09:27:33 +02:00
Abdessamad Derraz b0f2c3bb88 fix: leave mode-keyed pins to a hand edit 2026-10-06 09:25:23 +02:00
Abdessamad Derraz d89b193780 fix: move a ref only on a hash found elsewhere 2026-10-06 09:24:49 +02:00
Abdessamad Derraz afdd38826c fix: refuse --refresh-data where nothing refreshes 2026-10-06 09:24:24 +02:00
Abdessamad Derraz 7ddb849c89 fix: hold the data dir lock in emulator packs 2026-10-06 09:20:42 +02:00
Abdessamad Derraz 8e3f3fe057 fix: build system packs beside, not over, emulator packs 2026-10-06 09:19:18 +02:00
Abdessamad Derraz af030fdd6d fix: reduce emulator packs by region after required-only 2026-10-06 09:13:15 +02:00
Abdessamad Derraz f159d35f18 fix: read the data registry from --platforms-dir 2026-10-06 09:10:57 +02:00
Abdessamad Derraz dce43a1281 fix: keep display names out of pack file paths 2026-10-06 09:09:02 +02:00
Abdessamad Derraz d7e7ec13ae fix: install the variant the pack ships 2026-10-06 09:08:33 +02:00
Abdessamad Derraz 098b2f0b70 fix: restore release assets through a scratch file 2026-10-06 08:58:40 +02:00
Abdessamad Derraz 6c9a41327b fix: refuse unread verify flags, read platforms dir 2026-10-06 08:58:21 +02:00
Abdessamad Derraz fb3c577521 fix: anchor the slot mode test on the override lookup 2026-10-06 08:53:58 +02:00
Abdessamad Derraz 821cbf4717 fix: count exclusions from the full pack only 2026-10-06 08:52:51 +02:00
Abdessamad Derraz 9c84b7990a fix: give np2kai scsi.rom a read ceiling, not a size 2026-10-06 08:52:26 +02:00
Abdessamad Derraz 4f8fbdfabb fix: read numbered master keys in the switch test 2026-10-06 08:51:16 +02:00
Abdessamad Derraz eccd0b0a28 fix: file the older switch keys as variants 2026-10-06 08:50:38 +02:00
Abdessamad Derraz 8fe961a1ea fix: read the hle index scope from its comprehension 2026-10-06 07:37:41 +02:00
Abdessamad Derraz ce239d32c1 refactor: name unused mock arguments, flatten withs 2026-10-06 07:06:53 +02:00
Abdessamad Derraz 13995047fc refactor: simplify pack label, hash merge, diff exit 2026-10-06 07:06:22 +02:00
Abdessamad Derraz 31e86b6db5 refactor: let the retropie core check take none 2026-10-06 07:05:05 +02:00
Abdessamad Derraz da5e95cd0f refactor: extract the alias scope helpers 2026-10-06 07:04:54 +02:00
Abdessamad Derraz 3dce7b5f09 refactor: extract companion extras and the kept filter 2026-10-06 07:03:59 +02:00
Abdessamad Derraz 97ccf9027b refactor: extract emulator archive placement 2026-10-06 07:00:22 +02:00
Abdessamad Derraz 1caf5c4b5e refactor: one mismatch rule for builder and verifier 2026-10-06 06:57:01 +02:00
Abdessamad Derraz acfd80c928 refactor: extract verify listing and resolution steps 2026-10-06 06:50:59 +02:00
Abdessamad Derraz 4d8ea05494 refactor: move the truth steps out of pipeline main 2026-10-06 06:47:56 +02:00
Abdessamad Derraz 3df423e84f refactor: pull mismatch and region steps from manifest 2026-10-06 06:45:45 +02:00
Abdessamad Derraz 8a9924f1d8 refactor: extract retrodeck key matching helpers 2026-10-06 06:40:41 +02:00
Abdessamad Derraz 52fbea5faa refactor: split truth merge into match, fold, create 2026-10-06 06:40:07 +02:00
Abdessamad Derraz 421a7afb3f refactor: split the pin gate from bump_commit 2026-10-06 06:39:02 +02:00
Abdessamad Derraz 0b7acf1e7f refactor: hoist test imports, rank pairs at module level 2026-10-06 06:38:35 +02:00
Abdessamad Derraz c4dab556e7 fix: name only pinned claims as left unserved 2026-10-06 06:33:36 +02:00
Abdessamad Derraz 0390f2d19b fix: accept verified_members in pack manifests 2026-10-06 06:31:10 +02:00
Abdessamad Derraz e6afd06191 chore: regenerate database, manifests and readme 2026-10-06 05:46:44 +02:00
Abdessamad Derraz 03929ddaf0 fix: name each retropie package once per target 2026-10-06 05:33:18 +02:00
Abdessamad Derraz 2b71e47510 fix: give solarus and eka2l1 their own pad mappings 2026-10-06 05:26:54 +02:00
Abdessamad Derraz cd90f46937 fix: redeploy the site on any script it imports 2026-10-06 05:23:27 +02:00
Abdessamad Derraz d02672c17b fix: read retropie targets the way retropie does 2026-10-06 05:22:34 +02:00
Abdessamad Derraz c6ba139493 fix: stop target scrapes on a failed request 2026-10-06 05:19:05 +02:00
Abdessamad Derraz f0bab9766c fix: tell same-name files apart by path in truth 2026-10-06 05:18:07 +02:00
Abdessamad Derraz c55e3af5ac fix: preserve release assets by registered path 2026-10-06 05:13:34 +02:00
Abdessamad Derraz 1c34503f4e fix: one game data list for notes and composition 2026-10-06 05:11:41 +02:00
Abdessamad Derraz 72834837a5 fix: give a targeted export the full truth model 2026-10-06 05:09:45 +02:00
Abdessamad Derraz ae0bbb9d3c fix: fail a truth diff that compared nothing 2026-10-06 05:09:11 +02:00
Abdessamad Derraz e4cc978614 fix: skip schema tests when jsonschema is absent 2026-10-06 05:08:40 +02:00
Abdessamad Derraz f51c3a3fc7 fix: point the retrobat guide at install.ps1 2026-10-06 05:06:15 +02:00
Abdessamad Derraz c62a34b1b2 fix: read hle fallbacks from the platform cores only 2026-10-06 05:06:04 +02:00
Abdessamad Derraz ed18e61580 fix: fail a pack member the collection cannot name 2026-10-06 05:05:14 +02:00
Abdessamad Derraz 2cdb98239e fix: propose retropie names per core, never into or-lists 2026-10-06 05:01:10 +02:00
Abdessamad Derraz 623b0a6d47 fix: keep recalbox notes its own 2026-10-06 05:00:36 +02:00
Abdessamad Derraz 82583fe17a fix: correct one retrodeck revision, not all of them 2026-10-06 05:00:24 +02:00
Abdessamad Derraz 85501c41e8 fix: export one file's identity, never two mixed 2026-10-06 05:00:02 +02:00
Abdessamad Derraz 7273f5c8b9 chore: point seven profiles' refs at their values 2026-10-06 04:59:20 +02:00
Abdessamad Derraz e723c8c1b9 chore: point five profiles' refs at their values 2026-10-06 04:59:20 +02:00
Abdessamad Derraz 4db5866bbe chore: recale wqxemu and emucorex onto head 2026-10-06 04:59:20 +02:00
Abdessamad Derraz dc3a35bdc8 feat: profile visual-pinball hrtf, collect the sofa 2026-10-06 04:59:20 +02:00
Abdessamad Derraz 0c881820c7 chore: recale jazz2-native and linapple onto head 2026-10-06 04:59:20 +02:00
Abdessamad Derraz d839ce0a1c fix: drop cpc firmware regions, a keyboard picks 2026-10-06 04:58:59 +02:00
Abdessamad Derraz 1b885632a6 fix: drop saturn bios regions, the list is fixed 2026-10-06 04:58:59 +02:00
Abdessamad Derraz ad9f834f55 fix: drop pce system card regions, an option picks 2026-10-06 04:58:59 +02:00
Abdessamad Derraz 49e0f83ce9 fix: hold the data dir lock while packing it 2026-10-06 04:56:16 +02:00
Abdessamad Derraz 2b6dbb5e92 fix: choose regions among the files required-only keeps 2026-10-06 04:54:05 +02:00
Abdessamad Derraz 3d31a887cc fix: compare only the full pack to verify 2026-10-06 04:49:21 +02:00
Abdessamad Derraz e3f773dd82 fix: finalize only the packs this run names 2026-10-06 04:48:32 +02:00
Abdessamad Derraz c1b3e1fc33 fix: keep large-file revisions and clean failed fetches 2026-10-06 04:47:10 +02:00
Abdessamad Derraz b3c7e76adf fix: apply required-only to emulator pack archives 2026-10-06 04:46:03 +02:00
Abdessamad Derraz b2208d889e fix: count undistributed split extras with the helper 2026-10-06 04:43:49 +02:00
Abdessamad Derraz af357a2931 fix: give system packs their core extras 2026-10-06 04:42:35 +02:00
Abdessamad Derraz 1b40211366 fix: refuse every flag a quick mode does not read 2026-10-06 04:39:08 +02:00
Abdessamad Derraz a484754c76 chore: recale geolith jg.c refs onto upstream head 2026-10-06 04:37:48 +02:00
Abdessamad Derraz 46268bea47 fix: judge each profile ref by its own repository 2026-10-06 04:37:48 +02:00
Abdessamad Derraz 0fbe99a14a fix: keep cross-named aliases inside their profile 2026-10-06 04:35:35 +02:00
Abdessamad Derraz 16f4c071fe fix: arbitrate slots on the strongest profile proof 2026-10-06 04:32:19 +02:00
Abdessamad Derraz a8415c33c3 fix: key the casefold index on its source dict 2026-10-06 04:30:11 +02:00
Abdessamad Derraz 7e054fbcaf fix: pack windows platforms case-insensitively 2026-10-06 04:29:49 +02:00
Abdessamad Derraz 5784d45531 fix: carry pcsx2 companions under the names it opens 2026-10-06 04:29:16 +02:00
Abdessamad Derraz c526180351 fix: withhold a file only on the frontend digest 2026-10-06 04:21:47 +02:00
Abdessamad Derraz 36f7ee0b2b fix: one agnostic substitute rule for pack and verify 2026-10-06 04:19:40 +02:00
Abdessamad Derraz cf7d9ecb89 fix: carry identity in extras, refile rom b 2026-10-06 04:12:08 +02:00
Abdessamad Derraz ccc51b861b refactor: split the all-variants flag refusals 2026-10-06 03:11:07 +02:00
Abdessamad Derraz 3ae9729143 refactor: move flag refusals out of the main functions 2026-10-06 03:08:35 +02:00
Abdessamad Derraz 26ce416163 chore: regenerate database, manifests and readme 2026-10-06 02:47:55 +02:00
Abdessamad Derraz bd3bcc36ea fix: read accepted revision lists in profile aliases 2026-10-06 02:06:01 +02:00
Abdessamad Derraz f631174c85 test: check every registered platform pack 2026-10-06 01:54:11 +02:00
Abdessamad Derraz 22648e8a5f test: keep validation index on platform cores 2026-10-06 01:49:24 +02:00
Abdessamad Derraz 0de03b5e4c test: pin exact sets in the shipped content ratchets 2026-10-06 01:49:09 +02:00
Abdessamad Derraz 3bd19b9e97 test: fail, not skip, when slot conflicts vanish 2026-10-06 01:48:17 +02:00
Abdessamad Derraz ccf1a1573a fix: read platform hashes as verify does in PR checks 2026-10-06 01:47:48 +02:00
Abdessamad Derraz ca86236c12 refactor: one upstream name index for core lookups 2026-10-06 01:47:16 +02:00
Abdessamad Derraz 994836938c fix: lay out truth with the pack mode rule 2026-10-06 01:43:30 +02:00
Abdessamad Derraz 71cbbd9b21 feat: list retrodeck standalone emulators 2026-10-06 01:43:30 +02:00
Abdessamad Derraz 65c0f5ddc1 fix: apply changed-only to every profile_sync output 2026-10-06 01:42:00 +02:00
Abdessamad Derraz ef16ed2af2 fix: decrypt otp.bin with the standard library 2026-10-06 01:41:27 +02:00
Abdessamad Derraz da0c843bdd fix: default profile report containers to empty 2026-10-06 01:39:55 +02:00
Abdessamad Derraz e32df743d5 fix: refuse targets when their list cannot be read 2026-10-06 01:39:31 +02:00
Abdessamad Derraz 72d3654245 fix: register yaps2 resources, check data dir refs 2026-10-06 01:38:39 +02:00
Abdessamad Derraz bdcf134877 fix: build the database without network calls 2026-10-06 01:37:40 +02:00
Abdessamad Derraz aec1755c61 fix: gate on slot crashes and empty consistency 2026-10-06 01:34:59 +02:00
Abdessamad Derraz 53ee1bfd0e fix: fail the run when a platform pack errors 2026-10-06 01:34:30 +02:00
Abdessamad Derraz d2d88cf6b8 fix: send retropie users to the retropie pack 2026-10-06 01:33:58 +02:00
Abdessamad Derraz fbdee43e6d fix: point pack guides at the path each platform reads 2026-10-06 01:33:33 +02:00
Abdessamad Derraz 284f1aa6b7 fix: dedup core extras on their real destination 2026-10-06 01:32:56 +02:00
Abdessamad Derraz 10f3379751 fix: skip archive members by their yaml key 2026-10-06 01:32:33 +02:00
Abdessamad Derraz 76387d1962 fix: count romset members as held dumps 2026-10-06 01:32:09 +02:00
Abdessamad Derraz 9465a250cc fix: recale pcsx_rearmed bios refs, pal to oceania 2026-10-06 01:31:31 +02:00
Abdessamad Derraz 3bc80b2e06 fix: serve pal psx bios to oceania too 2026-10-06 01:31:27 +02:00
Abdessamad Derraz dcdfe5936e fix: drop fmsx font regions, fonts are options 2026-10-06 01:31:27 +02:00
Abdessamad Derraz ed47f437fc fix: drop prosystem regions, pal is a signal 2026-10-06 01:31:27 +02:00
Abdessamad Derraz 15abbec663 fix: drop o2em regions, the bios is a model 2026-10-06 01:31:27 +02:00
Abdessamad Derraz 077dcda775 fix: drop opera regions, the bios is an option 2026-10-06 01:31:27 +02:00
Abdessamad Derraz 27610c1d33 chore: regenerate database, manifests and readme 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 25034cd958 fix: count what each export format writes 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 480c02882d feat: collect the pc-88 font rom for quasi88 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 056953d833 fix: attach profile aliases to a unique name only 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 49d425aa8a fix: map pc-8801, dsi and cd-i system slugs 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 58cb61a08f fix: refresh data dirs under a lock, by rename 2026-10-06 01:22:56 +02:00
Abdessamad Derraz 485e5bc2b3 feat: profile aps3e 2.43 on rpcs3 0.0.42 2026-10-06 00:26:36 +02:00
Abdessamad Derraz c5165ffb96 feat: profile cap32 at c238550 with the 664 rom 2026-10-06 00:26:36 +02:00
Abdessamad Derraz 183793481c fix: keep prose citations within reach and pinned 2026-10-06 00:26:36 +02:00
Abdessamad Derraz a762152c12 chore: recale gpsp refs onto their pin 2026-10-06 00:26:36 +02:00
Abdessamad Derraz b650f9eb8a chore: point desmume refs at the bios loads 2026-10-06 00:26:36 +02:00
Abdessamad Derraz 4ca4592f65 fix: group variant slots across their systems 2026-10-06 00:26:36 +02:00
Abdessamad Derraz 0fb09c6c94 fix: decide slots on the region pass grouping 2026-10-06 00:26:36 +02:00
Abdessamad Derraz 34e382eae8 fix: group regions by the target in pack integrity 2026-10-06 00:13:38 +02:00
Abdessamad Derraz 9e327a05ed fix: verify the pack the flags name, by exact name 2026-10-06 00:13:38 +02:00
Abdessamad Derraz f010e737c5 chore: regenerate database, manifests and readme 2026-10-05 22:55:32 +02:00
Abdessamad Derraz edce60bae1 feat: collect nethersx2-turnip assets from v0.7 2026-10-05 22:55:31 +02:00
Abdessamad Derraz 44663db144 fix: scan free bios names only in the bios folder 2026-10-05 22:55:31 +02:00
Abdessamad Derraz 757fad69bc feat: store pcsx2 achievement sound under its tail 2026-10-05 22:22:22 +02:00
Abdessamad Derraz ca6462c510 fix: order same-named files by their owner's copy 2026-10-05 22:22:22 +02:00
Abdessamad Derraz 3f5d825ac6 fix: no collision where one file passes every declaration 2026-10-05 22:12:23 +02:00
Abdessamad Derraz 151dd5ea04 fix: judge a slot on the declaration the pack ships 2026-10-05 22:10:48 +02:00
Abdessamad Derraz eaf315ea8f fix: check every zipped member a platform declares 2026-10-05 22:05:10 +02:00
Abdessamad Derraz 366598e662 fix: ship the archive every member declaration accepts 2026-10-05 22:05:10 +02:00
Abdessamad Derraz 5f3e9fbacd chore: regenerate install manifests 2026-10-05 22:00:13 +02:00
Abdessamad Derraz 045372992c fix: keep unsourceable entries out of the asset store 2026-10-05 22:00:13 +02:00
Abdessamad Derraz 38ffccc465 chore: regenerate install manifests 2026-10-05 21:53:59 +02:00
Abdessamad Derraz 68bcef544d fix: give the install manifest the arbitrated file 2026-10-05 21:53:59 +02:00
Abdessamad Derraz 81995a4c18 fix: emulator region report reads the pack drop set 2026-10-05 21:43:32 +02:00
Abdessamad Derraz 95d14819ce fix: one rule for the variant an emulator accepts 2026-10-05 21:36:14 +02:00
Abdessamad Derraz dc31a90dca fix: keep profile identity on second-pass extras 2026-10-05 21:27:35 +02:00
Abdessamad DerrazandClaude Opus 5.5 8e3a486107 fix: resolve held catacomb files, profile refs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:29 +02:00
Abdessamad DerrazandClaude Opus 5.5 a3d5e6c048 chore: regenerate database, manifests and readme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:23 +02:00
Abdessamad DerrazandClaude Opus 5.5 8e0c7bad38 feat: collect ap6, shareddata, introset, a2 scsi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:23 +02:00
Abdessamad DerrazandClaude Opus 5.5 67f1b29fec feat: reprofile beetle_saturn, collect mpeg cards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:22 +02:00
Abdessamad DerrazandClaude Opus 5.5 b9644f720c feat: build engine data at pinned commits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:22 +02:00
Abdessamad DerrazandClaude Opus 5.5 0a9d36cae3 fix: name homonymous release assets by path
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:13 +02:00
Abdessamad DerrazandClaude Opus 5.5 5816562bf6 fix: one size predicate, size_options retired
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:51:13 +02:00
70 changed files with 2968 additions and 270463 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, ...)
- **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
- **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
## 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,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 |
| 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 |
| MiSTer FPGA | 81/81 | - | 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 |
| 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 |
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-09T21:03:52Z*
*Auto-generated on 2026-10-06T11:05:39Z*
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
+46 -166
View File
@@ -1,9 +1,8 @@
{
"schema_version": 1,
"generated_at": "2026-10-09T20:41:14Z",
"total_files": 19002,
"total_size": 17497698358,
"bios_dir": "bios",
"generated_at": "2026-10-06T10:11:17Z",
"total_files": 18997,
"total_size": 17487414657,
"files": {
"738c7f5fd0c8b57ee2e87ae7a97bf8e21a821d07": {
"path": "bios/3D Realms/Duke Nukem 3D/DUKE.RTS",
@@ -131660,16 +131659,6 @@
"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",
@@ -132720,6 +132709,16 @@
"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",
@@ -132960,16 +132959,6 @@
"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",
@@ -133040,16 +133029,6 @@
"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",
@@ -135697,16 +135676,6 @@
"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",
@@ -192862,16 +192831,6 @@
"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",
@@ -192882,16 +192841,6 @@
"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",
@@ -208117,7 +208066,6 @@
"dfdd9924295f491d54f30569874287f8": "0c15d6753cf0ae2a1737b6e857e8e82ee135a697",
"9a293eaaebca601f671070ad56cb81b6": "56306e7234bcffd14b4f977d42d237c4ad9c12d5",
"b494140c6250b2faa8a801e0efef08d5": "042ce2001fb438a0a34e0c5d0001a9d95d5516df",
"3758db793dae7bc206fbbd799c3cbb05": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"8080d51e2d787aa3fa181e4ddbd455ed": "8acfbc4a51909de382e33a221a82f0b8efc04794",
"5bac80439b16ee31feb5b45489a1a5da": "ad28302b3bdd8aef667969fceb073f0845942017",
"888b281fa986de2eaccb0f9c8b591395": "7fad1628fd7da3b7a4bad6a7040f9fb4ed945d3a",
@@ -208223,6 +208171,7 @@
"dcde75e5d5c62a03f7bf35312f5116a9": "0f37f2795ca071eb712d5243247675f140897c50",
"65e1739196c3163ec5e77f49eea118ae": "bd9262879f8a72ec610a9554b503d747194a0f49",
"4981d3d22bda9c3d0791c13cb612e95d": "343f90558952049a0232faf6cf136fc2b8d4be84",
"dbf48fe7b36fab316a1682961178ffd7": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"86da78cb59576328483a11c6ef74bc2b": "00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"8ad82b1dc550319993a7d6c932b2656d": "9056e59b69c6f1160fdfb92a0773f17f6307f3ad",
"a7470da305b78df13804a1e62c7bb41e": "660c7fe74de23ee604bea47673f84710bd34f075",
@@ -208247,7 +208196,6 @@
"083257a65f869a927d8f5a4534f46106": "36cc8f46014a321b7602e52c062ab23d7c256ccc",
"95f294d24b5e97ceab96282f1284190f": "8dba66121f3bbc4b2bdf67722ef4c2b6b6b23ee6",
"6f47c5daf33a759df3395db8ebe62ed1": "7b0bb79d38c573648de218645c6fb16449e156e0",
"ec65dbfe7379cfac816b4d02259c0ca5": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"75527903c6a793772b02807c5343f4c8": "456e74b0805d1931a7786d3d04673a3b02dd4dd3",
"46dbe14f577b56ade221acf89631fd7b": "69253a2722f999ba4205f2e679d0b75e3b17109f",
"5199b3892ba596a6bf8d0f930729157f": "9dc5783345f258d0c66c17028d39652daa234473",
@@ -208255,7 +208203,6 @@
"e910afbd441c5247227fb4a731d65799": "83d3c03dc1fc16f2a5b8bbf929d4cc21a078282c",
"19498325f22381f551df06ce1b4cad4f": "01299427e4d789295c614752c8398261f006479b",
"ad18e8fc265252917dd3698e268977d7": "59815234d228a7f390364c6d6380cc518ea63db1",
"809868f006eebec4e4615dd8bc397ef7": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"d91939cc76fb66f536476320987d0dc0": "8618516b26a0bc319ed174b66fd2d990eb5b74a3",
"7547b120ce49d79d50addee5ea6fe38b": "fb33e36d016aedea36478eb4bb15b35ca1b7bb0c",
"2c928cbd0410a7e7edae6b2808a1f566": "b478ce9d1343b8dd201c045f010bd1b64ba158ae",
@@ -208520,7 +208467,6 @@
"f4ae6809bd8c31573370e8da72514012": "e18b9aabbda0cc178436855ef8d04bce96533049",
"bf5e583b038933c5f7bc4f0a6f70617c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"602ea57b1d141e81b2a96f20a4b8098c": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"dbf48fe7b36fab316a1682961178ffd7": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"ed4d4e8f81d9707dbf4931a786b6b56d": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"efa6392d3dd6e66abf94542159889e4c": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"b56eb507fac5ac64a7079514c02b8ca7": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
@@ -213973,9 +213919,7 @@
"2e70ad008d4ec8549aada8002fdf42fb": "caa18ed04854a91e68f2d61a782560edd6373bbf",
"b53d51edc7fc086685e31b811dc32aad": "34a81db03ab617fbfdd7f9b861692dd2ecd57b82",
"5dea62f70439682a6cee16ba3823d11e": "6eddec30056cde7c664a0cf508dcad29353a12bb",
"fc3c14208a929c386445635b19baa3f2": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"de6da198a7359d1200c3eeb6df9c7eda": "40ecf6138c99a0aba775ef93240b295025a45500",
"0525ad1f1381152f20bfb05775cf39cd": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"d66b0bccd743bd11eaca55bcaec925da": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"44552702b05697a14ccbe2ca22ee7139": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"362e2dac5e4de13591c67fb5eceeb94a": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -247352,20 +247296,6 @@
"b624812e3aa0ef2f75847f47907296e1f370cecd",
"ca84ef31b6d48c6ab9530b743da373790a4f723b"
],
"GameIndex.yaml": [
"71fc274cbe27f382afb0324024ebdc6f59aa666d",
"72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"997fdfb1c1873c1a43972b2984ac0da61fd47b14",
"fca26d4f7c1fe0abf6534b470da2ee5ad461eba5",
"58fb1f8ea6cf2d526904349640453e682fe388a1",
"7efb2ed721cf0c0f0677324e09be311a8337e793",
"57369df9ca82a460e596cea7835c5a0797dee668",
"b22389650f6c0a1e276b48213fed8a9c1c6476ce",
"6eddec30056cde7c664a0cf508dcad29353a12bb",
"5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"810-archiver.rom": [
"8acfbc4a51909de382e33a221a82f0b8efc04794",
"1eb8a3ee2366c605f7e47f8025a9e714ee9cf129"
@@ -247681,6 +247611,9 @@
"NotoColorEmoji-Regular.ttf": [
"343f90558952049a0232faf6cf136fc2b8d4be84"
],
"RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"Roboto-Regular.ttf": [
"00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"d0860202be85147f3ce96d71aa88541d939a1aea"
@@ -247779,12 +247712,6 @@
"NotoSansTC-Regular.ttf": [
"59815234d228a7f390364c6d6380cc518ea63db1"
],
"patches.zip": [
"f7d0f1606c87019f00109ebdf0f260299f0a72da",
"a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"2562e542d66db945ccf158d7a221c2a28e3b6be4",
"541a74165496fb7ef35ce6ec03f711ebbe263e11"
],
"ffx_a_fsr1.h": [
"8618516b26a0bc319ed174b66fd2d990eb5b74a3"
],
@@ -248561,9 +248488,21 @@
"games.db": [
"0e2ee74d1f9ebc3a6faf9dbaebbacc2ff053f4df"
],
"RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0",
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a"
"GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"997fdfb1c1873c1a43972b2984ac0da61fd47b14",
"fca26d4f7c1fe0abf6534b470da2ee5ad461eba5",
"58fb1f8ea6cf2d526904349640453e682fe388a1",
"7efb2ed721cf0c0f0677324e09be311a8337e793",
"57369df9ca82a460e596cea7835c5a0797dee668",
"b22389650f6c0a1e276b48213fed8a9c1c6476ce",
"6eddec30056cde7c664a0cf508dcad29353a12bb",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"2562e542d66db945ccf158d7a221c2a28e3b6be4",
"541a74165496fb7ef35ce6ec03f711ebbe263e11"
],
"libopenglplugin.so": [
"407f0fde89ac1a628fed0329072dfae1f8d304b1",
@@ -283931,7 +283870,6 @@
"feeddf41": "0c15d6753cf0ae2a1737b6e857e8e82ee135a697",
"2b3e5a61": "56306e7234bcffd14b4f977d42d237c4ad9c12d5",
"6b7152b1": "042ce2001fb438a0a34e0c5d0001a9d95d5516df",
"5acc416a": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"bd90e77d": "8acfbc4a51909de382e33a221a82f0b8efc04794",
"196eed02": "ad28302b3bdd8aef667969fceb073f0845942017",
"55dfc9ac": "7fad1628fd7da3b7a4bad6a7040f9fb4ed945d3a",
@@ -284037,6 +283975,7 @@
"84ad7d58": "0f37f2795ca071eb712d5243247675f140897c50",
"1094a89e": "bd9262879f8a72ec610a9554b503d747194a0f49",
"a5b357e6": "343f90558952049a0232faf6cf136fc2b8d4be84",
"0496a60f": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"f49fbea6": "00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"27922b13": "9056e59b69c6f1160fdfb92a0773f17f6307f3ad",
"84d6cb99": "660c7fe74de23ee604bea47673f84710bd34f075",
@@ -284061,7 +284000,6 @@
"8216948a": "36cc8f46014a321b7602e52c062ab23d7c256ccc",
"2c7a1b2a": "8dba66121f3bbc4b2bdf67722ef4c2b6b6b23ee6",
"6e8b2b47": "7b0bb79d38c573648de218645c6fb16449e156e0",
"1a8e4cf7": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"92685e68": "456e74b0805d1931a7786d3d04673a3b02dd4dd3",
"b02dadf9": "69253a2722f999ba4205f2e679d0b75e3b17109f",
"96886a26": "9dc5783345f258d0c66c17028d39652daa234473",
@@ -284069,7 +284007,6 @@
"0fcd7543": "83d3c03dc1fc16f2a5b8bbf929d4cc21a078282c",
"07694737": "01299427e4d789295c614752c8398261f006479b",
"05eb51ca": "59815234d228a7f390364c6d6380cc518ea63db1",
"5120eae7": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"3a8aeca2": "8618516b26a0bc319ed174b66fd2d990eb5b74a3",
"6743e2ad": "fb33e36d016aedea36478eb4bb15b35ca1b7bb0c",
"ce9e57e4": "b478ce9d1343b8dd201c045f010bd1b64ba158ae",
@@ -284334,7 +284271,6 @@
"e1232465": "e18b9aabbda0cc178436855ef8d04bce96533049",
"ca72899c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"e8038ec9": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"0496a60f": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"8820b708": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"ea1fd68a": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"13eed5a5": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
@@ -289787,9 +289723,7 @@
"41391dd3": "caa18ed04854a91e68f2d61a782560edd6373bbf",
"25f6212a": "34a81db03ab617fbfdd7f9b861692dd2ecd57b82",
"0b805686": "6eddec30056cde7c664a0cf508dcad29353a12bb",
"a7541e89": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"a3e573a0": "40ecf6138c99a0aba775ef93240b295025a45500",
"28709e73": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"d766f908": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"2c3bcd32": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"0509cea5": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -302935,7 +302869,6 @@
"68757d34c21e28cce68c38ec1cc0de37b9fb046eed0716036ba4f1d0df3bcc5e": "0c15d6753cf0ae2a1737b6e857e8e82ee135a697",
"2a5448dd69dcd13c48c7b256e51907aa43bdc2ce2dde618a039c0b7cc009a57d": "56306e7234bcffd14b4f977d42d237c4ad9c12d5",
"9e7fb1335e56027f150b7ff9b7b6cddd9c0844af19be6dbc17347e1882022038": "042ce2001fb438a0a34e0c5d0001a9d95d5516df",
"e7c70faf16ce180e6a17c040dc2bb326c6fcca63e7fb3f83f992d65fbe480443": "71fc274cbe27f382afb0324024ebdc6f59aa666d",
"b861b939d32cb2c1e672cb9c50668394f1d83d723fd5f331727a19c36001f5ad": "8acfbc4a51909de382e33a221a82f0b8efc04794",
"5160ac22a55d6c82ee2304046b2b6eac13d153c0d0b1164bd693e93acc053623": "ad28302b3bdd8aef667969fceb073f0845942017",
"acb8f9c481ffe762021f980c0cee42bd1a512ccd210686e6e0ac7d6e34f5a374": "7fad1628fd7da3b7a4bad6a7040f9fb4ed945d3a",
@@ -303041,6 +302974,7 @@
"dedbaf271c3257db2a02493a53616d1da78302c678575c9b0f4d254cdb1e4293": "0f37f2795ca071eb712d5243247675f140897c50",
"c217cc125856b0ee9776208b8e0bf5f1a4f04561ff2fa644f690518f87f55942": "bd9262879f8a72ec610a9554b503d747194a0f49",
"1ca0775b9dc92d8d959cd06dd773e2d4dacc97301ad81e6b5aa02cd5e7233a27": "343f90558952049a0232faf6cf136fc2b8d4be84",
"755962450efd5effd2f3fe2f9a60c73d6cb3f9b4acea964abf5bb25a86ee25bc": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"797e35f7f5d6020a5c6ea13b42ecd668bcfb3bbc4baa0e74773527e5b6cb3174": "00d62fbdc8d5dec4c659005e116d0ba2ee63b547",
"4d901b560f20f455b3c82c6616b17b72360956747cb35c3808aa370052fe8e32": "9056e59b69c6f1160fdfb92a0773f17f6307f3ad",
"c4541eefde3f31e4216559ada91f52d1b7ec95b849f7e9afc7a05613a56c8bf2": "660c7fe74de23ee604bea47673f84710bd34f075",
@@ -303065,7 +302999,6 @@
"022c8b05d8f3e7e6046077a2d6ad2d3da1e7fa0e69c8321f3c556b9963be2477": "36cc8f46014a321b7602e52c062ab23d7c256ccc",
"c749cd9513bd345b9f12ff46eac0ffbf4e54c0b0884921df97ca8297396e3fea": "8dba66121f3bbc4b2bdf67722ef4c2b6b6b23ee6",
"ab829598e15b771112130cbafbd982736400690c11e3d7c81e3fc8c582c24f47": "7b0bb79d38c573648de218645c6fb16449e156e0",
"61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"90f50de40af560ad55f2fcf460af8205f842b534b057bd8b8bcf7d3e10d3223a": "456e74b0805d1931a7786d3d04673a3b02dd4dd3",
"11fa517464dbd568e855e449650a9004d1072c4a54ac1845aaafc462c0652b64": "69253a2722f999ba4205f2e679d0b75e3b17109f",
"1edcc5695148a632b8a5516ba41a86edf01f06289c5c2e84a1afafe833eb25da": "9dc5783345f258d0c66c17028d39652daa234473",
@@ -303073,7 +303006,6 @@
"9db318b65ee9c575a43e7efd273dbdd1afef26e467eea3e1073a50e1a6595f6d": "83d3c03dc1fc16f2a5b8bbf929d4cc21a078282c",
"ae82f4e2a55e1316a55bcc1d05e9555ce08d8bda07e893b486896b626fd852ff": "01299427e4d789295c614752c8398261f006479b",
"6b137e2eb57a2d1e4cf391c886ab1b783a0e5ddb5c75254748bde00c15cb8ff5": "59815234d228a7f390364c6d6380cc518ea63db1",
"8d95f1fba3acc83195bbac2d3e042b5f34c3c399856d9865e681d173f4cee609": "f7d0f1606c87019f00109ebdf0f260299f0a72da",
"f60e2722fcd13989523b9164d776ab382b3692791767f3bf8bb19967f763f3fb": "8618516b26a0bc319ed174b66fd2d990eb5b74a3",
"93c3922362ea7fc99cbcc698ca30c98de4f8c246d1fbb0b09e015ddef38ce3a5": "fb33e36d016aedea36478eb4bb15b35ca1b7bb0c",
"3d5b9cef750ea8bedceef43c4c52215e8280abb81b5b8fa002007bb3f97f0506": "b478ce9d1343b8dd201c045f010bd1b64ba158ae",
@@ -303338,7 +303270,6 @@
"306b53ecfb182a504dd8a7446093c316387d2fd8dc350d0792ed1753fe0996cd": "e18b9aabbda0cc178436855ef8d04bce96533049",
"cdefaf8efd47045f6820928eba84db5bed7557539328952b5f828315485e02ee": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"d5558a0e0aa033482b098cc8a6449e78137f5069beabf421ab60d9eccbb62d8f": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"755962450efd5effd2f3fe2f9a60c73d6cb3f9b4acea964abf5bb25a86ee25bc": "dceb4536fba7fb89452864fc512a4d071a19eae0",
"aa14a46908c1114e0c0f1c06accbfa7efc83c1768fb501699e043cbdb1af797f": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"817eb2a258e44bb730b94ab77b00cac2480c2e27449078949e2897bad3cb3bc3": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"53e5ddc6825ecde7b502acb5fae3a173ba6696ec5b8bb7445f378f7b0ad1beb8": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
@@ -308791,9 +308722,7 @@
"6b61e27861c288a99ee9b4878862ed824173a037ebaca2bef0581017a85241e1": "caa18ed04854a91e68f2d61a782560edd6373bbf",
"e8eed9831c39fc298be087ba11d620673134bc683b97ccc909c555d29cc1b207": "34a81db03ab617fbfdd7f9b861692dd2ecd57b82",
"3e14a0d3db871704a9652a5d90d4ba62be2bceefbca6bb46da65275b09d4f78e": "6eddec30056cde7c664a0cf508dcad29353a12bb",
"6531239315d759d0588985b31576696d2672c1a0e192633ea2d4f3d011bfdb37": "5e2d50a3376dc6a0750505bec68626d39a5bd4c1",
"483face8f50f5e96d89528059522be3766b44f5e07e854bae1fa92ff0111d0a7": "40ecf6138c99a0aba775ef93240b295025a45500",
"302b57fdd1f9472ef590789e4b347381cc8442a04a765c7eae3d3f2e4b06394c": "bf2c73d668cd671df8adfe2ec3c7074e8ba9176a",
"dfd8035387a463722ad07af3c91934e0a685f2afa57e31d305f0602656d1edb1": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"41de2047af8382988bfd568035ff26eec5f1cabc3efe773680546aa00a82857d": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"fc1b5e12e426b229fc4f114a4b8cbe6a5441f7e0ce6654abce382e677f1c051d": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -469596,18 +469525,6 @@
"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"
],
@@ -470259,6 +470176,12 @@
"armsx2/NotoColorEmoji-Regular.ttf": [
"343f90558952049a0232faf6cf136fc2b8d4be84"
],
"Other/armsx2/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"armsx2/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"Other/armsx2/Roboto-Regular.ttf": [
"00d62fbdc8d5dec4c659005e116d0ba2ee63b547"
],
@@ -470406,15 +470329,6 @@
"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"
],
@@ -470529,19 +470443,6 @@
"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"
],
@@ -473120,15 +473021,9 @@
"emucorex/resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039"
],
"Other/emucorex/resources/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"emucorex/resources/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0"
],
"resources/RedumpDatabase.yaml": [
"dceb4536fba7fb89452864fc512a4d071a19eae0",
"bf2c73d668cd671df8adfe2ec3c7074e8ba9176a"
"resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"Other/emucorex/resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
@@ -473136,6 +473031,9 @@
"emucorex/resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"Other/emucorex/resources/shaders/common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e"
],
@@ -544479,30 +544377,12 @@
".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,9 +148,6 @@ 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,8 +272,6 @@ 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
@@ -299,8 +297,6 @@ 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
@@ -312,8 +308,6 @@ 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
@@ -793,8 +787,6 @@ 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,8 +289,6 @@ files:
system: sony-playstation-2
required: false
bundled: true
sha1: 2d9878fdd70979ecf6dd0da7652efd3797c26039
size: 2697551
category: game_data
description: "per-title compatibility and fix database"
note: >-
@@ -305,8 +303,6 @@ files:
system: sony-playstation-2
required: false
bundled: true
sha1: dceb4536fba7fb89452864fc512a4d071a19eae0
size: 1906463
category: game_data
description: "disc hash database"
note: >-
@@ -319,8 +315,6 @@ files:
system: sony-playstation-2
required: false
bundled: true
sha1: a3d939af27fe0929e61b8e20d49ef58316c0a66c
size: 2091144
category: game_data
description: "built-in game patch archive"
note: >-
@@ -560,8 +554,6 @@ files:
- name: unlock.wav
path: resources/sounds/achievements/unlock.wav
sha1: da328a0a3074ec86fee2d6741bfc2e5a1f68d063
size: 202702
system: sony-playstation-2
required: false
description: "achievement unlock sound"
+1046 -1153
View File
File diff suppressed because it is too large. Load diff
+430 -443
View File
File diff suppressed because it is too large. Load diff
-4
View File
@@ -69,8 +69,6 @@ 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"
@@ -80,8 +78,6 @@ 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, filed as the Japanese BIOS"
region: [japan]
description: "PSP embedded PS1 BIOS (region detected at runtime)"
region: [world]
size: 524288
required: false
hle_fallback: true
validation: [size]
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."
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[3])"
note: "Region detected from content at runtime. Must be 512 KB in libretro context."
- 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 = "df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107"
$defaultInstallSha256 = "60e18da18b5a9a43b18d37020b676ad47babd6d12c5314e8fcc1370e4864c4e5"
$maximumInstallerBytes = 2MB
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
$temporary = $null
+10 -19
View File
@@ -120,8 +120,7 @@ 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's installer defaults to C:\RetroBat (installer.iss).
"retrobat": Path("C:/RetroBat/bios"),
"retrobat": profile / "RetroBat" / "bios",
"bizhawk": profile / "BizHawk" / "Firmware",
}
if os_type == "darwin":
@@ -1295,7 +1294,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, int]:
) -> tuple[int, int]:
"""Copy BIOS files to standalone emulator directories.
Supports:
@@ -1308,19 +1307,16 @@ 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, 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".
Returns (copied_count, skipped_count).
"""
from fnmatch import fnmatch
copies = manifest.get("standalone_copies", [])
if not copies:
return 0, 0, 0
return 0, 0
copied = 0
skipped = 0
failed = 0
for entry in copies:
# Note entries: print message if emulator detected
@@ -1379,11 +1375,10 @@ def do_standalone_copies(
try:
shutil.copy2(src, dest)
copied += 1
except OSError as exc:
print(f" ERROR: {dest}: {exc.strerror or exc}")
failed += 1
except OSError:
skipped += 1
return copied, skipped, failed
return copied, skipped
def format_size(n: int) -> str:
@@ -1743,15 +1738,11 @@ 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, copy_failed = do_standalone_copies(
copied, skipped = do_standalone_copies(
manifest, bios_path, os_type, extra_dirs
)
if copied or skipped or copy_failed:
print(
f" {copied} copied, {skipped} skipped (dir not found), "
f"{copy_failed} failed"
)
total_errors += copy_failed
if copied or skipped:
print(f" {copied} copied, {skipped} skipped (dir not found)")
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="df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107"
DEFAULT_INSTALL_SHA256="60e18da18b5a9a43b18d37020b676ad47babd6d12c5314e8fcc1370e4864c4e5"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+195 -15
View File
@@ -5,7 +5,7 @@
"platform": "batocera",
"display_name": "Batocera",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T06:50:25Z",
"base_destination": "bios",
"detect": [
{
@@ -237,10 +237,10 @@
}
}
],
"total_files": 12808,
"total_size": 8059515793,
"pack_files": 12810,
"pack_size": 8059515793,
"total_files": 12826,
"total_size": 8065338769,
"pack_files": 12828,
"pack_size": 8065338769,
"total_omitted": 30,
"omitted_files": [
{
@@ -3467,6 +3467,36 @@
"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",
@@ -40987,6 +41017,16 @@
"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",
@@ -41567,6 +41607,16 @@
"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",
@@ -41607,6 +41657,26 @@
"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",
@@ -84703,6 +84773,36 @@
"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",
@@ -89473,6 +89573,86 @@
"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",
@@ -89493,6 +89673,16 @@
"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",
@@ -126021,16 +126211,6 @@
"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",
+65 -5
View File
@@ -5,7 +5,7 @@
"platform": "bizhawk",
"display_name": "BizHawk",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T10:11:17Z",
"base_destination": "Firmware",
"detect": [
{
@@ -20,10 +20,10 @@
}
],
"standalone_copies": [],
"total_files": 948,
"total_size": 2585741346,
"pack_files": 950,
"pack_size": 2585741346,
"total_files": 954,
"total_size": 2588624930,
"pack_files": 956,
"pack_size": 2588624930,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -7839,6 +7839,16 @@
"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",
@@ -7849,6 +7859,16 @@
"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",
@@ -7869,6 +7889,16 @@
"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",
@@ -7879,6 +7909,16 @@
"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",
@@ -7889,6 +7929,26 @@
"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",
+15 -5
View File
@@ -5,7 +5,7 @@
"platform": "emudeck",
"display_name": "EmuDeck",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T10:11:17Z",
"base_destination": "bios",
"detect": [
{
@@ -250,10 +250,10 @@
}
}
],
"total_files": 618,
"total_size": 3472900342,
"pack_files": 620,
"pack_size": 3472900342,
"total_files": 619,
"total_size": 3473031414,
"pack_files": 621,
"pack_size": 3473031414,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -2191,6 +2191,16 @@
"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",
+80 -22
View File
@@ -5,7 +5,7 @@
"platform": "lakka",
"display_name": "Lakka",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T06:50:25Z",
"base_destination": "system",
"detect": [
{
@@ -16,10 +16,10 @@
}
],
"standalone_copies": [],
"total_files": 5861,
"total_size": 6398490526,
"pack_files": 8504,
"pack_size": 6439023690,
"total_files": 5867,
"total_size": 6399839286,
"pack_files": 8510,
"pack_size": 6440372450,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -1319,6 +1319,14 @@
"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",
@@ -1859,10 +1867,10 @@
},
{
"dest": "BS-X.bin",
"sha1": "604556b2e62860af18db5a77f2956ebc75450020",
"sha256": "3ce321496edc5d77038de2034eb3fb354d7724afd0bc7fd0319f3eb5d57b984d",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/Satellaview_BS-X.sfc",
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"cores": null
},
{
@@ -4107,10 +4115,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"size": 2690355,
"repo_path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"cores": null
},
{
@@ -4261,6 +4269,16 @@
"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",
@@ -4271,6 +4289,16 @@
"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",
@@ -25511,6 +25539,16 @@
"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",
@@ -25541,6 +25579,26 @@
"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",
@@ -53027,6 +53085,16 @@
"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",
@@ -56529,16 +56597,6 @@
"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",
+95 -5
View File
@@ -5,7 +5,7 @@
"platform": "recalbox",
"display_name": "Recalbox",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T06:50:25Z",
"base_destination": "bios",
"detect": [
{
@@ -237,10 +237,10 @@
}
}
],
"total_files": 3220,
"total_size": 4192446979,
"pack_files": 3222,
"pack_size": 4192446979,
"total_files": 3229,
"total_size": 4193661699,
"pack_files": 3231,
"pack_size": 4193661699,
"total_omitted": 1,
"omitted_files": [
{
@@ -3383,6 +3383,16 @@
"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",
@@ -3393,6 +3403,16 @@
"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",
@@ -4093,6 +4113,16 @@
"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",
@@ -21423,6 +21453,16 @@
"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",
@@ -21823,6 +21863,16 @@
"fuse"
]
},
{
"dest": "colecovision.rom",
"sha1": "45bedc4cbdeac66c7df59e9e599195c778d86a92",
"sha256": "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad",
"size": 8192,
"repo_path": "bios/Coleco/ColecoVision/BIOS.col",
"cores": [
"gearcoleco"
]
},
{
"dest": "eos.rom",
"sha1": "ad3c20ef444f10af7ae8eb75c81e500d9b1bba3d",
@@ -21943,6 +21993,16 @@
"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",
@@ -27983,6 +28043,16 @@
"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",
@@ -28863,6 +28933,26 @@
"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",
+80 -22
View File
@@ -5,7 +5,7 @@
"platform": "retroarch",
"display_name": "RetroArch",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T06:50:25Z",
"base_destination": "system",
"detect": [
{
@@ -255,10 +255,10 @@
}
}
],
"total_files": 5861,
"total_size": 6398490526,
"pack_files": 8504,
"pack_size": 6439023690,
"total_files": 5867,
"total_size": 6399839286,
"pack_files": 8510,
"pack_size": 6440372450,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -1558,6 +1558,14 @@
"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",
@@ -2098,10 +2106,10 @@
},
{
"dest": "BS-X.bin",
"sha1": "604556b2e62860af18db5a77f2956ebc75450020",
"sha256": "3ce321496edc5d77038de2034eb3fb354d7724afd0bc7fd0319f3eb5d57b984d",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/Satellaview_BS-X.sfc",
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"cores": null
},
{
@@ -4346,10 +4354,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"size": 2690355,
"repo_path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"cores": null
},
{
@@ -4500,6 +4508,16 @@
"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",
@@ -4510,6 +4528,16 @@
"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",
@@ -25750,6 +25778,16 @@
"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",
@@ -25780,6 +25818,26 @@
"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",
@@ -53266,6 +53324,16 @@
"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",
@@ -56768,16 +56836,6 @@
"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",
+125 -10
View File
@@ -5,14 +5,9 @@
"platform": "retrobat",
"display_name": "RetroBat",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T10:11:17Z",
"base_destination": "bios",
"detect": [
{
"os": "windows",
"method": "path_exists",
"path": "C:\\RetroBat\\bios"
},
{
"os": "windows",
"method": "path_exists",
@@ -241,10 +236,10 @@
}
}
],
"total_files": 12397,
"total_size": 8504927168,
"pack_files": 12399,
"pack_size": 8504927168,
"total_files": 12409,
"total_size": 8505437376,
"pack_files": 12411,
"pack_size": 8505437376,
"total_omitted": 30,
"omitted_files": [
{
@@ -41513,6 +41508,16 @@
"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",
@@ -41553,6 +41558,26 @@
"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",
@@ -58031,6 +58056,16 @@
"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",
@@ -58561,6 +58596,26 @@
"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",
@@ -86447,6 +86502,46 @@
"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",
@@ -86477,6 +86572,16 @@
"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",
@@ -120507,6 +120612,16 @@
"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",
+255 -95
View File
@@ -5,7 +5,7 @@
"platform": "retrodeck",
"display_name": "RetroDECK",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T10:11:17Z",
"base_destination": "",
"detect": [
{
@@ -237,10 +237,10 @@
}
}
],
"total_files": 7791,
"total_size": 8622202286,
"pack_files": 7793,
"pack_size": 8622202286,
"total_files": 7807,
"total_size": 8628195246,
"pack_files": 7809,
"pack_size": 8628195246,
"total_omitted": 1,
"omitted_files": [
{
@@ -16371,6 +16371,16 @@
"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",
@@ -16381,6 +16391,16 @@
"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",
@@ -18231,6 +18251,16 @@
"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",
@@ -18261,6 +18291,26 @@
"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",
@@ -31789,6 +31839,16 @@
"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",
@@ -32509,6 +32569,26 @@
"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",
@@ -38169,6 +38249,16 @@
"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",
@@ -38249,6 +38339,16 @@
"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",
@@ -38279,6 +38379,16 @@
"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",
@@ -59419,6 +59529,16 @@
"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",
@@ -60229,6 +60349,76 @@
"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",
@@ -65855,6 +66045,46 @@
"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",
@@ -66317,16 +66547,6 @@
"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",
@@ -67157,6 +67377,26 @@
"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",
@@ -71243,66 +71483,6 @@
"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",
@@ -71433,16 +71613,6 @@
"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",
@@ -71463,16 +71633,6 @@
"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",
+120 -32
View File
@@ -5,7 +5,7 @@
"platform": "retropie",
"display_name": "RetroPie",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T06:50:25Z",
"base_destination": "BIOS",
"detect": [
{
@@ -15,10 +15,10 @@
}
],
"standalone_copies": [],
"total_files": 9890,
"total_size": 8732256943,
"pack_files": 12533,
"pack_size": 8772790107,
"total_files": 9899,
"total_size": 8731269966,
"pack_files": 12542,
"pack_size": 8771803130,
"total_omitted": 8,
"omitted_files": [
{
@@ -1399,6 +1399,14 @@
"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",
@@ -1939,10 +1947,10 @@
},
{
"dest": "BS-X.bin",
"sha1": "604556b2e62860af18db5a77f2956ebc75450020",
"sha256": "3ce321496edc5d77038de2034eb3fb354d7724afd0bc7fd0319f3eb5d57b984d",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/Satellaview_BS-X.sfc",
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"cores": null
},
{
@@ -4187,10 +4195,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "72bfdf91fa11a86e2810a7c167cdcf11e20418a8",
"sha256": "61c77b789ccde856096f024e4a3eef6891e15ca59c127e0dd13e762a7d338a7e",
"size": 2690355,
"repo_path": "bios/Other/armsx2/pcsx2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"cores": null
},
{
@@ -4571,6 +4579,16 @@
"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",
@@ -4581,6 +4599,16 @@
"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",
@@ -41945,6 +41973,16 @@
"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",
@@ -41985,6 +42023,16 @@
"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",
@@ -41995,6 +42043,16 @@
"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",
@@ -42005,6 +42063,16 @@
"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",
@@ -62783,6 +62851,16 @@
"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",
@@ -62813,6 +62891,26 @@
"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",
@@ -90569,6 +90667,16 @@
"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",
@@ -95707,26 +95815,6 @@
"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",
+35 -15
View File
@@ -5,7 +5,7 @@
"platform": "rocknix",
"display_name": "ROCKNIX",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T06:50:25Z",
"base_destination": "bios",
"detect": [
{
@@ -17,10 +17,10 @@
}
],
"standalone_copies": [],
"total_files": 5802,
"total_size": 6570592972,
"pack_files": 5804,
"pack_size": 6570592972,
"total_files": 5804,
"total_size": 6570724300,
"pack_files": 5806,
"pack_size": 6570724300,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -15720,6 +15720,16 @@
"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",
@@ -53048,6 +53058,26 @@
"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",
@@ -56080,16 +56110,6 @@
"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",
+47 -538
View File
@@ -5,7 +5,7 @@
"platform": "romm",
"display_name": "RomM",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T10:11:17Z",
"base_destination": "bios",
"detect": [
{
@@ -15,543 +15,12 @@
}
],
"standalone_copies": [],
"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"
]
}
],
"total_files": 1089,
"total_size": 1586368550,
"pack_files": 1091,
"pack_size": 1586368550,
"total_omitted": 0,
"omitted_files": [],
"files": [
{
"dest": "3do/3do_arcade_saot.bin",
@@ -4271,6 +3740,16 @@
"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",
@@ -4311,6 +3790,26 @@
"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",
@@ -5521,6 +5020,16 @@
"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,10 +805,6 @@ 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'
+37 -9
View File
@@ -163,15 +163,22 @@ shared_groups:
# QUASI88 searches "quasi88/" subfolder first
# ref: quasi88-libretro/src/LIBRETRO/libretro.c:463 — snprintf(..., "%s%cquasi88%c%s", ...)
quasi88:
# 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: 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"
- name: N88EXT0.ROM
destination: quasi88/N88EXT0.ROM
required: true
@@ -188,6 +195,27 @@ 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()
+24 -105
View File
@@ -4,136 +4,55 @@
"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
},
"bios_dir": {
"type": "string",
"description": "Directory the files were scanned from; _mame_clones.json sits beside it."
},
"schema_version": { "const": 1 },
"generated_at": { "type": "string", "format": "date-time" },
"total_files": { "type": "integer", "minimum": 0 },
"total_size": { "type": "integer", "minimum": 0 },
"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
}
+1 -2
View File
@@ -129,8 +129,7 @@
"hash_mismatch",
"not_found",
"external",
"user_provided",
"no_platform_slug"
"user_provided"
]
},
"cores": {
+6 -30
View File
@@ -43,18 +43,11 @@ def write_if_changed(path: str, content: str, normalize=None) -> bool:
)
if _strip_timestamps(before) == _strip_timestamps(after):
return False
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.
"""
# 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.
directory = os.path.dirname(os.path.abspath(path))
handle, scratch = tempfile.mkstemp(
dir=directory, prefix=f".{os.path.basename(path)}.", suffix=".tmp"
@@ -67,24 +60,7 @@ def write_text_atomic(path: str, content: str) -> None:
with contextlib.suppress(OSError):
os.unlink(scratch)
raise
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
return True
def _strip_timestamps(text: str) -> str:
"""Remove known timestamp patterns for content comparison."""
+4 -12
View File
@@ -152,13 +152,12 @@ def print_report(report: dict) -> None:
print(f"\nSummary: {', '.join(parts)}")
def update_changed(report: dict) -> list[str]:
"""Refresh entries that have changed; the keys whose refresh failed."""
failed: list[str] = []
def update_changed(report: dict) -> None:
"""Refresh entries that have changed."""
for e in report.get("entries", []):
if e["status"] == "UPDATED" and e.get("key"):
log.info("refreshing %s ...", e["key"])
result = subprocess.run(
subprocess.run(
[
sys.executable,
"scripts/refresh_data_dirs.py",
@@ -168,10 +167,6 @@ def update_changed(report: dict) -> list[str]:
],
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:
@@ -199,10 +194,7 @@ def main() -> None:
print_report(report)
if args.update:
failed = update_changed(report)
if failed:
print(f"ERROR: refresh failed for {', '.join(failed)}", file=sys.stderr)
sys.exit(1)
update_changed(report)
# Unreachable upstream means the freshness question was not answered, and
# a zero exit says it was answered "fresh".
+40 -113
View File
@@ -489,51 +489,6 @@ 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.
@@ -552,15 +507,14 @@ 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, bool, int]:
def score(path: str) -> tuple[int, 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.
# Another emulator's own tree ranks below everything else.
return fits, bool(owners & tokens), not _foreign_tree(path, owners | wanted), len(wanted & tokens)
return fits, bool(owners & tokens), len(wanted & tokens)
return sorted(paths, key=score, reverse=True)
@@ -762,14 +716,9 @@ 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 = _rank_tail_candidates(
by_path_suffix.get(tail, []), start, tail, files_db, owners
)
candidates = by_path_suffix.get(tail, [])
if start and len(candidates) > 1:
continue
# The index names the file this destination designates. Not on
@@ -780,6 +729,20 @@ 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
@@ -912,7 +875,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(db.get("bios_dir"))
clone_map = get_mame_clone_map()
canonical = clone_map.get(name)
if canonical and canonical != name:
canonical_entry = {"name": canonical}
@@ -960,34 +923,28 @@ def resolve_local_file(
return None, "not_found"
_mame_clone_map_cache: dict[str, dict[str, str]] = {}
_mame_clone_map_cache: dict[str, str] | None = None
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] = {}
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",
)
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", []):
clone_map[clone] = canonical
_mame_clone_map_cache[clone_path] = clone_map
return clone_map
_mame_clone_map_cache[clone] = canonical
else:
_mame_clone_map_cache = {}
return _mame_clone_map_cache
@@ -1000,27 +957,6 @@ 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,
@@ -1709,24 +1645,17 @@ 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.
Counted on the normalized id, as --system selects: listed per spelling,
3do showed two emulators and panasonic-3do six for one system.
"""
"""Print available system IDs with emulator count."""
profiles = load_emulator_profiles(emulators_dir)
system_emus: dict[str, set[str]] = {}
spelling: dict[str, str] = {}
for name, p in sorted(profiles.items()):
system_emus: dict[str, list[str]] = {}
for name, p in profiles.items():
if p.get("type") in ("alias", "test", "launcher"):
continue
for sys_id in p.get("systems", []):
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 ''})")
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 ''})")
def list_platform_system_ids(platform_name: str, platforms_dir: str) -> None:
@@ -1802,8 +1731,6 @@ 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,
+4 -26
View File
@@ -70,25 +70,6 @@ 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]:
@@ -154,7 +135,6 @@ 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.
@@ -197,7 +177,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(bios_dir).get(fname)
canonical = get_mame_clone_map().get(fname)
if canonical and canonical != fname:
if canonical in by_name and _name_hit(canonical):
return "bios"
@@ -289,7 +269,6 @@ 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
@@ -534,7 +513,6 @@ 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(
@@ -633,13 +611,13 @@ def main():
print("No emulator profiles found.", file=sys.stderr)
return
selected = [args.platform] if args.platform else None
declared, plat_data_dirs = load_platform_files(args.platforms_dir, selected)
declared, plat_data_dirs = load_platform_files(
args.platforms_dir, [args.platform] if args.platform else None
)
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,
)
+10 -43
View File
@@ -62,29 +62,10 @@ 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], before: dict | None = None) -> str:
return ", ".join(self._bios_items(files, before))
def _bios_files(self, files: list[NativeFile]) -> str:
return ", ".join(self._bios_items(files))
@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)
}
def _bios_items(self, files: list[NativeFile]) -> list[str]:
parts: list[str] = []
for fe in files:
# The platform states an unhashed file as an empty md5 rather
@@ -99,25 +80,14 @@ 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
@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))
def _entry_line(self, system: NativeSystem, files: list[NativeFile]) -> str:
return (
f'{_INDENT}"{system.native_id}": '
f'{{ "name": "{self.display_name(system)}", {runner}'
f'"biosFiles": [ {self._bios_files(files, own)} ] }},'
f'{{ "name": "{self.display_name(system)}", '
f'"biosFiles": [ {self._bios_files(files)} ] }},'
)
@staticmethod
@@ -157,17 +127,14 @@ class Exporter(BaseExporter):
) -> list[str]:
"""One entry, keeping the layout and the notes it was written with."""
trailing, leading = self._item_notes(original)
before = self._parse_entry(original)
own = self._own_entry(system, before)
items = self._bios_items(files, own)
items = self._bios_items(files)
one_line = len(original) == 1 and not trailing and not leading
if one_line:
return [self._entry_line(system, files, before)]
return [self._entry_line(system, files)]
runner = "".join(f"{key}, " for key in self._runner_keys(own))
head = (
f'{_INDENT}"{system.native_id}": '
f'{{ "name": "{self.display_name(system)}", {runner}"biosFiles": ['
f'{{ "name": "{self.display_name(system)}", "biosFiles": ['
)
pad = " " * (len(_INDENT) + 4)
lines = [head]
@@ -277,8 +244,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
+2 -9
View File
@@ -82,15 +82,8 @@ 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. 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")
):
# have made MSXHawk refuse the dump it accepts.
if fe.hash("sha1") and not _hash_values(fe.platform or {}, "sha1"):
seen.setdefault(fe.name.casefold(), []).append(fe)
resolved: dict[str, NativeFile] = {}
for name, entries in seen.items():
+8 -10
View File
@@ -176,8 +176,8 @@ class Exporter(BaseExporter):
return nested, "bios"
return None
@classmethod
def _merge(cls, existing: object, ours: list[OrderedDict]) -> list[OrderedDict]:
@staticmethod
def _merge(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,9 +206,6 @@ 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 = (
@@ -216,17 +213,18 @@ 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 or not keys:
if other_revision:
merged.append(OrderedDict(entry))
corrected.update(keys)
continue
if 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 field in cls.carries() and not (field == "system" and declared)
if not (field == "system" and declared)
)
merged.append(combined)
corrected.update(keys)
-3
View File
@@ -386,9 +386,6 @@ 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,
}
+26 -55
View File
@@ -31,8 +31,6 @@ 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,
@@ -725,13 +723,10 @@ def generate_pack(
preferred_entries: dict[str, int] = {}
if source != "truth":
# 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.
# A manifest lists the full set and carries no data directories, so
# the preference is resolved with neither narrowing nor a cache.
preferred_entries = _preferred_entries(
pack_systems, db, bios_dir, base_dest, required_only,
pack_systems, db, bios_dir, base_dest, False,
zip_contents, None, offline,
)
@@ -1561,7 +1556,16 @@ def generate_system_pack(
) -> str | None:
"""Generate a ZIP pack for all emulators supporting given system IDs."""
profiles = load_emulator_profiles(emulators_dir)
matching = profiles_for_systems(profiles, system_ids, standalone)
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)
if not matching:
all_systems: set[str] = set()
@@ -1583,9 +1587,10 @@ def generate_system_pack(
)
return None
# 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)
# Use system-based ZIP name
sys_display = "_".join(
_name_part("_".join(w.title() for w in sid.split("-"))) 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)
@@ -1821,16 +1826,7 @@ def generate_md5_pack(
if emulator_name and emulators_dir:
profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
if emulator_name in profiles:
# 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
profile = profiles[emulator_name]
emu_display = profile.get("emulator", emulator_name)
emu_pack_structure = profile.get("pack_structure")
for fe in profile.get("files", []):
@@ -2003,7 +1999,9 @@ 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"
write_text_atomic(out_path, json.dumps(result, indent=2, sort_keys=True) + "\n")
with open(out_path, "w") as f:
json.dump(result, f, indent=2, sort_keys=True)
f.write("\n")
count += 1
print(f" {yml_file.stem}: {len(result)} targets")
print(f"Generated {count} target manifest(s) in {output_dir}")
@@ -2105,7 +2103,8 @@ 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
write_text_atomic(path, new_json)
with open(path, "w") as f:
f.write(new_json)
def _run_manifest_mode(
@@ -2298,11 +2297,6 @@ 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,
@@ -2337,8 +2331,6 @@ 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:
@@ -2407,12 +2399,6 @@ 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)
@@ -3092,18 +3078,6 @@ 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,
@@ -3213,10 +3187,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.
# Decided among the declarations this manifest keeps, as the pack
# does; a manifest carries no data directories.
# A manifest lists the full set and carries no data directories, so
# the preference is resolved with neither narrowing nor a cache.
preferred_entries = _preferred_entries(
pack_systems, db, bios_dir, base_dest, required_only,
pack_systems, db, bios_dir, base_dest, False,
zip_contents, None, offline,
)
for sys_id, system in sorted(pack_systems.items()):
@@ -3345,7 +3319,6 @@ 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,
@@ -3356,11 +3329,9 @@ 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,
+15 -54
View File
@@ -134,42 +134,6 @@ 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,
@@ -448,18 +412,9 @@ 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
@@ -517,18 +472,25 @@ def _collect_emulator_extras(
# Slug-based platforms: prefix dest with system slug
if is_slug_based:
slug = _slug_for(u, profiles, platform_systems, norm_map, sys_to_slug)
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,
)
if not slug:
if unplaceable is not None:
unplaceable.append(u)
continue
continue # can't place without slug
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)
@@ -719,7 +681,6 @@ 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", "")))
+1 -5
View File
@@ -198,13 +198,9 @@ 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}: MISMATCH pack {p_ok}/{p_total} OK "
f"> verify {v_ok}/{v_total}"
f" {v_label}: verify {v_ok}/{v_total}, pack {p_ok}/{p_total} OK (pack resolves more)"
)
all_ok = False
else:
print(f" {v_label}: {v_ok}/{v_total} (no separate pack)")
all_ok = False
-8
View File
@@ -88,13 +88,6 @@ 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()):
@@ -105,7 +98,6 @@ 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
+20 -18
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_database, load_emulator_profiles, resolve_local_file
from common import load_emulator_profiles, load_database, parse_md5_list
# No-Intro filename tokens: full English territory names.
NOINTRO = {
@@ -100,21 +100,24 @@ def catalog_regions(db: dict) -> dict[str, tuple[set[str], str]]:
return out
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 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 build_report(profiles: dict, db: dict) -> dict:
@@ -127,7 +130,6 @@ 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
@@ -135,7 +137,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(dict(f, source_profile=emu_name), db, by_path)
sha1 = resolve_sha1(f, db)
if not sha1 or sha1 not in catalog:
unchecked += 1
continue
+1 -2
View File
@@ -24,7 +24,6 @@ import zipfile
from pathlib import Path
import split_pack
from common import write_text_atomic
RECORD = "release.json"
@@ -125,7 +124,7 @@ def main() -> int:
print(f"Error: {line}", file=sys.stderr)
if mismatches:
return 1
write_text_atomic(str(args.output), json.dumps(record, indent=2) + "\n")
args.output.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
print(f"Wrote {args.output}")
return 0
+3 -4
View File
@@ -19,10 +19,11 @@ 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 copy_file_atomic, load_database
from common import load_database
def gitignored_paths(gitignore: str) -> set[str]:
@@ -71,9 +72,7 @@ def restore(
unsatisfied.append(path)
continue
os.makedirs(os.path.dirname(path), exist_ok=True)
# 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)
shutil.copy2(source, path)
print(f"Restored: {path}")
restored += 1
print(f"Total: {restored} files restored")
+12 -59
View File
@@ -28,7 +28,6 @@ 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
@@ -156,10 +155,10 @@ def profile_claims(
for entry in profile.get("files") or []:
if not isinstance(entry, dict):
continue
# 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):
entry_mode = entry.get("mode")
if entry_mode == "standalone" and not is_standalone:
continue
if entry_mode == "libretro" and 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
@@ -243,14 +242,10 @@ 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 not any(_satisfies(c, by_dest[key], db) for c in claims)
if all(c.local_path != by_dest[key].local_path for c in claims)
}
return [
@@ -460,51 +455,6 @@ 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(
@@ -520,10 +470,13 @@ def _accepted_by_all(path: str, entries: list[dict], db: dict) -> bool:
):
return False
continue
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
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
return True
+37 -60
View File
@@ -228,71 +228,36 @@ def validate_file(
def get_changed_files() -> list[str]:
"""BIOS files the branch changes against origin's main line.
"""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
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")
@@ -307,11 +272,23 @@ def main():
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
files = _files_to_validate(args, parser)
if not files:
return
files = args.files
if args.changed:
files = get_changed_files()
if not files:
print("No changed BIOS files detected")
return
db = _load_database_if_any(args.db)
if not files:
parser.error("No files specified. Use --changed or provide file paths.")
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
platform_hashes = load_platform_hashes(args.platforms_dir)
results = []
+21 -115
View File
@@ -34,7 +34,6 @@ 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,
@@ -85,7 +84,6 @@ 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")
@@ -399,11 +397,8 @@ def _candidate_verdict(
if outside_gap_scope(file_entry, is_standalone):
return "skip"
if not include_all:
# 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:
archive = file_entry.get("archive")
if fname in declared_names or (archive and archive in declared_names):
return "settled"
return "keep"
@@ -720,75 +715,6 @@ 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,
@@ -904,7 +830,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 packextras import platform_region_groups
from generate_pack import platform_region_groups
region_groups, region_extra_dests = platform_region_groups(
config,
@@ -919,15 +845,6 @@ 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 (
@@ -937,10 +854,9 @@ def verify_platform(
in region_drops
):
continue
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,
local_path, resolve_status = _resolve_for_platform(
file_entry, sys_id, db, zip_contents, data_dir_registry,
slot_overrides, mode, platform_profiles,
)
destination = sanitize_pack_path(
file_entry.get("destination", file_entry.get("name", ""))
@@ -977,8 +893,6 @@ 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", ""))
@@ -1035,7 +949,6 @@ 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
)
@@ -1135,10 +1048,6 @@ 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
@@ -1207,27 +1116,17 @@ 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
game_data, bios_files, unplaced = _split_undeclared(undeclared)
# 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"]
req_not_in_repo = [
u
@@ -1250,8 +1149,6 @@ 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)
@@ -1665,7 +1562,16 @@ def verify_system(
) -> dict:
"""Verify files for all emulators supporting given system IDs."""
profiles = load_emulator_profiles(emulators_dir)
matching = profiles_for_systems(profiles, system_ids, standalone)
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)
if not matching:
all_systems: set[str] = set()
-69
View File
@@ -1,69 +0,0 @@
"""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()
+12 -49
View File
@@ -482,20 +482,6 @@ 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()):
@@ -939,37 +925,17 @@ class RegionRegressions(unittest.TestCase):
)
self.assertNotEqual(north_america, europe)
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)
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,
)
class ArchiveSecurityRegressions(unittest.TestCase):
@@ -1325,10 +1291,7 @@ 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",
"no_platform_slug",
}
allowed = {"hash_mismatch", "not_found", "external", "user_provided"}
for path in sorted((ROOT / "install").glob("*.json")):
with self.subTest(manifest=path.name):
data = json.loads(path.read_text())
-49
View File
@@ -1,49 +0,0 @@
"""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,21 +271,5 @@ 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 *_a: {"bctvidbs.zip": "v4bios.zip"}
cross_reference.get_mame_clone_map = lambda: {"bctvidbs.zip": "v4bios.zip"}
try:
entry = {"name": "bctvidbs.zip", "required": True}
self.assertEqual(self._gap_source(entry, db), "bios")
-71
View File
@@ -355,76 +355,5 @@ 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,21 +56,5 @@ 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
@@ -1,72 +0,0 @@
"""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()
+7 -71
View File
@@ -218,15 +218,6 @@ 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)
@@ -798,7 +789,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)
@@ -810,7 +801,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)
@@ -820,64 +811,10 @@ 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, 0)
install.do_standalone_copies(self._manifest(), bios, "windows"), (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.
@@ -907,12 +844,11 @@ class TargetFlagIsAppliedOrRefused(unittest.TestCase):
for name in ("install/targets/retroarch.json",):
path = REPO_ROOT / name
if not path.is_file():
self.skipTest(f"{name} not generated yet")
continue
targets = json.loads(path.read_text())
for alias in ("switch", "ps2", "psp"):
# 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}")
if alias not in targets:
continue
with self.subTest(alias=alias):
resolved = load_target_config(
"retroarch", alias, str(REPO_ROOT / "platforms")
@@ -1071,7 +1007,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)
+4 -16
View File
@@ -48,25 +48,13 @@ def _registered_platforms() -> list[str]:
return list_registered_platforms(PLATFORMS_DIR, include_archived=True)
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
def _profiles_newer_than(path: str) -> list[str]:
"""Profiles modified after the pack was built."""
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.splitext(os.path.basename(p))[0] in relevant
and os.path.getmtime(p) > built
if os.path.getmtime(p) > built
]
@@ -99,7 +87,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, platform_name)
newer = _profiles_newer_than(pack)
if newer:
self.skipTest(
f"{platform_name} pack predates {len(newer)} profile(s) "
-14
View File
@@ -353,20 +353,6 @@ 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
@@ -1,67 +0,0 @@
"""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()
+6 -22
View File
@@ -436,32 +436,26 @@ 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__), "..")
completed = subprocess.run(
return subprocess.run(
[sys.executable, "scripts/verify.py", *args],
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
capture_output=True, text=True, cwd=repo, timeout=900,
).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)
@@ -582,16 +576,6 @@ 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
@@ -1,103 +0,0 @@
"""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
@@ -1,96 +0,0 @@
"""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,46 +157,6 @@ 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
@@ -1,93 +0,0 @@
"""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
@@ -1,83 +0,0 @@
"""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
@@ -1,141 +0,0 @@
"""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()