Compare commits

..
132 Commits
Author SHA1 Message Date
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
174 changed files with 4602 additions and 365581 deletions

No files matched your search

+1 -1
View File
@@ -84,7 +84,7 @@ jobs:
# mkdocs-material >= 9.7.5 caps mkdocs < 2; the upper bound keeps a major
# theme release from changing the site without a deliberate bump here.
- run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0" "mkdocs-material>=9.7.5,<10" "pymdown-extensions>=10.14"
- run: pip install pyyaml jsonschema==4.26.0 "mkdocs-material>=9.7.5,<10" "pymdown-extensions>=10.14"
- name: Validate data contracts
run: python scripts/validate_schemas.py
+2 -2
View File
@@ -112,7 +112,7 @@ jobs:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0"
run: pip install pyyaml jsonschema==4.26.0
- name: Validate platform configs and emulator profiles
run: python scripts/validate_schemas.py --source-only
@@ -127,7 +127,7 @@ jobs:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0"
run: pip install pyyaml jsonschema==4.26.0
# The exporter fidelity cases compare what we hand back against the
# file each platform publishes, so they need those files. They skip
+17 -17
View File
@@ -55,9 +55,9 @@ Every release ships `SHA256SUMS.txt` and a detached signature of it, checkable a
| RetroBat | 1,466 | 4.4 GB | `bios/` | [Download](../../releases/latest) |
| RetroDECK | 3,519 | 6.2 GB | `~/retrodeck/` | [Download](../../releases/latest) |
| RetroPie * | 4,525 | 5.5 GB | `~/RetroPie/BIOS/` | [Download](../../releases/latest) |
| RomM | 617 | 1.3 GB | `library/bios/` | [Download](../../releases/latest) |
| RomM | 617 | 1.3 GB | `bios/{platform_slug}/` | [Download](../../releases/latest) |
The RetroDECK pack already carries its own `bios/` folder, so it extracts into `~/retrodeck/` rather than into the BIOS folder.
The RetroDECK pack already contains its own `bios/` folder, so it extracts into `~/retrodeck/` rather than into the BIOS folder.
\* Archived: the configuration is kept and packs are still built, but upstream is no longer scraped on a schedule.
@@ -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,990 files in the collection**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 5,252 system files, 2,810 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,252 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index
- **16672 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,307/12,738 | MD5 hash |
| BizHawk | 118/118 | 649/650 | SHA1 hash |
| EmuDeck | 168/168 | 381/383 | MD5 hash |
| Lakka | 527/527 | 5,263/5,293 | 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,702/5,732 | MD5 hash |
| Recalbox | 351/351 | 2,728/2,736 | MD5 hash |
| RetroArch | 527/527 | 5,263/5,293 | file presence |
| RetroBat | 348/348 | 11,749/12,179 | MD5 hash |
| RetroDECK | 2,008/2,008 | 5,296/5,346 | MD5 hash |
| RetroPie * | 527/527 | 9,182/9,324 | file presence |
| RomM | 381/381 | 624/630 | 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-06T03:31:49Z*
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
Binary file not shown.
@@ -1,498 +0,0 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#ifndef FXAA_HLSL
#define FXAA_HLSL 0
#endif
#ifndef FXAA_GLSL_130
#define FXAA_GLSL_130 0
#endif
#ifndef FXAA_GLSL_VK
#define FXAA_GLSL_VK 0
#endif
#define UHQ_FXAA 1 //High Quality Fast Approximate Anti Aliasing. Adapted for GS from Timothy Lottes FXAA 3.11.
#define FxaaSubpixMax 0.0 //[0.00 to 1.00] Amount of subpixel aliasing removal. 0.00: Edge only antialiasing (no blurring)
#define FxaaEarlyExit 1 //[0 or 1] Use Fxaa early exit pathing. When disabled, the entire scene is antialiased(FSAA). 0 is off, 1 is on.
/*------------------------------------------------------------------------------
[GLOBALS|FUNCTIONS]
------------------------------------------------------------------------------*/
#if (FXAA_GLSL_130 == 1)
in vec2 PSin_t;
layout(location = 0) out vec4 SV_Target0;
layout(binding = 0) uniform sampler2D TextureSampler;
#elif (FXAA_GLSL_VK == 1)
layout(location = 0) in vec2 PSin_t;
layout(location = 0) out vec4 SV_Target0;
layout(set = 0, binding = 0) uniform sampler2D TextureSampler;
#elif (FXAA_HLSL == 1)
Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);
struct VS_INPUT
{
float4 p : POSITION;
float2 t : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 p : SV_Position;
float2 t : TEXCOORD0;
};
struct PS_OUTPUT
{
float4 c : SV_Target0;
};
#elif defined(__METAL_VERSION__)
static constexpr sampler MAIN_SAMPLER(coord::normalized, address::clamp_to_edge, filter::linear);
#endif
/*------------------------------------------------------------------------------
[FXAA CODE SECTION]
------------------------------------------------------------------------------*/
#if (FXAA_HLSL == 1)
struct FxaaTex { SamplerState smpl; Texture2D tex; };
#define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0)
#define FxaaTexOff(t, p, o, r) t.tex.SampleLevel(t.smpl, p, 0.0, o)
#define FxaaDiscard clip(-1)
#define FxaaSat(x) saturate(x)
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
#define int2 ivec2
#define float2 vec2
#define float3 vec3
#define float4 vec4
#define FxaaDiscard discard
#define FxaaSat(x) clamp(x, 0.0, 1.0)
#define FxaaTex sampler2D
#define FxaaTexTop(t, p) textureLod(t, p, 0.0)
#define FxaaTexOff(t, p, o, r) textureLodOffset(t, p, 0.0, o)
#elif defined(__METAL_VERSION__)
#define FxaaTex texture2d<float>
#define FxaaTexTop(t, p) t.sample(MAIN_SAMPLER, p)
#define FxaaTexOff(t, p, o, r) t.sample(MAIN_SAMPLER, p, o)
#define FxaaDiscard discard_fragment()
#define FxaaSat(x) saturate(x)
#endif
#define FxaaEdgeThreshold 0.063
#define FxaaEdgeThresholdMin 0.00
#define FXAA_QUALITY_P0 1.0
#define FXAA_QUALITY_P1 1.5
#define FXAA_QUALITY_P2 2.0
#define FXAA_QUALITY_P3 2.0
#define FXAA_QUALITY_P4 2.0
#define FXAA_QUALITY_P5 2.0
#define FXAA_QUALITY_P6 2.0
#define FXAA_QUALITY_P7 2.0
#define FXAA_QUALITY_P8 2.0
#define FXAA_QUALITY_P9 2.0
#define FXAA_QUALITY_P10 4.0
#define FXAA_QUALITY_P11 8.0
#define FXAA_QUALITY_P12 8.0
/*------------------------------------------------------------------------------
[GAMMA PREPASS CODE SECTION]
------------------------------------------------------------------------------*/
float RGBLuminance(float3 color)
{
const float3 lumCoeff = float3(0.2126729, 0.7151522, 0.0721750);
return dot(color.rgb, lumCoeff);
}
float3 RGBGammaToLinear(float3 color, float gamma)
{
color = FxaaSat(color);
color.r = (color.r <= 0.0404482362771082) ?
color.r / 12.92 : pow((color.r + 0.055) / 1.055, gamma);
color.g = (color.g <= 0.0404482362771082) ?
color.g / 12.92 : pow((color.g + 0.055) / 1.055, gamma);
color.b = (color.b <= 0.0404482362771082) ?
color.b / 12.92 : pow((color.b + 0.055) / 1.055, gamma);
return color;
}
float3 LinearToRGBGamma(float3 color, float gamma)
{
color = FxaaSat(color);
color.r = (color.r <= 0.00313066844250063) ?
color.r * 12.92 : 1.055 * pow(color.r, 1.0 / gamma) - 0.055;
color.g = (color.g <= 0.00313066844250063) ?
color.g * 12.92 : 1.055 * pow(color.g, 1.0 / gamma) - 0.055;
color.b = (color.b <= 0.00313066844250063) ?
color.b * 12.92 : 1.055 * pow(color.b, 1.0 / gamma) - 0.055;
return color;
}
float4 PreGammaPass(float4 color)
{
const float GammaConst = 2.233;
color.rgb = RGBGammaToLinear(color.rgb, GammaConst);
color.rgb = LinearToRGBGamma(color.rgb, GammaConst);
color.a = RGBLuminance(color.rgb);
return color;
}
/*------------------------------------------------------------------------------
[FXAA CODE SECTION]
------------------------------------------------------------------------------*/
float FxaaLuma(float4 rgba)
{
rgba.w = RGBLuminance(rgba.xyz);
return rgba.w;
}
float4 FxaaPixelShader(float2 pos, FxaaTex tex, float2 fxaaRcpFrame, float fxaaSubpix, float fxaaEdgeThreshold, float fxaaEdgeThresholdMin)
{
float2 posM = pos;
float4 rgbyM = FxaaTexTop(tex, posM);
rgbyM.w = RGBLuminance(rgbyM.xyz);
#define lumaM rgbyM.w
float lumaS = FxaaLuma(FxaaTexOff(tex, posM, int2( 0, 1), fxaaRcpFrame.xy));
float lumaE = FxaaLuma(FxaaTexOff(tex, posM, int2( 1, 0), fxaaRcpFrame.xy));
float lumaN = FxaaLuma(FxaaTexOff(tex, posM, int2( 0,-1), fxaaRcpFrame.xy));
float lumaW = FxaaLuma(FxaaTexOff(tex, posM, int2(-1, 0), fxaaRcpFrame.xy));
float maxSM = max(lumaS, lumaM);
float minSM = min(lumaS, lumaM);
float maxESM = max(lumaE, maxSM);
float minESM = min(lumaE, minSM);
float maxWN = max(lumaN, lumaW);
float minWN = min(lumaN, lumaW);
float rangeMax = max(maxWN, maxESM);
float rangeMin = min(minWN, minESM);
float range = rangeMax - rangeMin;
float rangeMaxScaled = rangeMax * fxaaEdgeThreshold;
float rangeMaxClamped = max(fxaaEdgeThresholdMin, rangeMaxScaled);
#if (FxaaEarlyExit == 1)
// Potential optimization, early exit.
if (range < rangeMaxClamped)
return rgbyM;
#endif
float lumaNW = FxaaLuma(FxaaTexOff(tex, posM, int2(-1,-1), fxaaRcpFrame.xy));
float lumaSE = FxaaLuma(FxaaTexOff(tex, posM, int2( 1, 1), fxaaRcpFrame.xy));
float lumaNE = FxaaLuma(FxaaTexOff(tex, posM, int2( 1,-1), fxaaRcpFrame.xy));
float lumaSW = FxaaLuma(FxaaTexOff(tex, posM, int2(-1, 1), fxaaRcpFrame.xy));
float lumaNS = lumaN + lumaS;
float lumaWE = lumaW + lumaE;
float subpixRcpRange = 1.0/range;
float subpixNSWE = lumaNS + lumaWE;
float edgeHorz1 = (-2.0 * lumaM) + lumaNS;
float edgeVert1 = (-2.0 * lumaM) + lumaWE;
float lumaNESE = lumaNE + lumaSE;
float lumaNWNE = lumaNW + lumaNE;
float edgeHorz2 = (-2.0 * lumaE) + lumaNESE;
float edgeVert2 = (-2.0 * lumaN) + lumaNWNE;
float lumaNWSW = lumaNW + lumaSW;
float lumaSWSE = lumaSW + lumaSE;
float edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2);
float edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2);
float edgeHorz3 = (-2.0 * lumaW) + lumaNWSW;
float edgeVert3 = (-2.0 * lumaS) + lumaSWSE;
float edgeHorz = abs(edgeHorz3) + edgeHorz4;
float edgeVert = abs(edgeVert3) + edgeVert4;
float subpixNWSWNESE = lumaNWSW + lumaNESE;
float lengthSign = fxaaRcpFrame.x;
bool horzSpan = edgeHorz >= edgeVert;
float subpixA = subpixNSWE * 2.0 + subpixNWSWNESE;
if(!horzSpan) lumaN = lumaW;
if(!horzSpan) lumaS = lumaE;
if(horzSpan) lengthSign = fxaaRcpFrame.y;
float subpixB = (subpixA * (1.0/12.0)) - lumaM;
float gradientN = lumaN - lumaM;
float gradientS = lumaS - lumaM;
float lumaNN = lumaN + lumaM;
float lumaSS = lumaS + lumaM;
bool pairN = abs(gradientN) >= abs(gradientS);
float gradient = max(abs(gradientN), abs(gradientS));
if(pairN) lengthSign = -lengthSign;
float subpixC = FxaaSat(abs(subpixB) * subpixRcpRange);
float2 posB;
posB.x = posM.x;
posB.y = posM.y;
float2 offNP;
offNP.x = (!horzSpan) ? 0.0 : fxaaRcpFrame.x;
offNP.y = ( horzSpan) ? 0.0 : fxaaRcpFrame.y;
if(!horzSpan) posB.x += lengthSign * 0.5;
if( horzSpan) posB.y += lengthSign * 0.5;
float2 posN;
posN.x = posB.x - offNP.x * FXAA_QUALITY_P0;
posN.y = posB.y - offNP.y * FXAA_QUALITY_P0;
float2 posP;
posP.x = posB.x + offNP.x * FXAA_QUALITY_P0;
posP.y = posB.y + offNP.y * FXAA_QUALITY_P0;
float subpixD = ((-2.0)*subpixC) + 3.0;
float lumaEndN = FxaaLuma(FxaaTexTop(tex, posN));
float subpixE = subpixC * subpixC;
float lumaEndP = FxaaLuma(FxaaTexTop(tex, posP));
if(!pairN) lumaNN = lumaSS;
float gradientScaled = gradient * 1.0/4.0;
float lumaMM = lumaM - lumaNN * 0.5;
float subpixF = subpixD * subpixE;
bool lumaMLTZero = lumaMM < 0.0;
lumaEndN -= lumaNN * 0.5;
lumaEndP -= lumaNN * 0.5;
bool doneN = abs(lumaEndN) >= gradientScaled;
bool doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P1;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P1;
bool doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P1;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P1;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P2;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P2;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P2;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P2;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P3;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P3;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P3;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P3;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P4;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P4;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P4;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P4;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P5;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P5;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P5;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P5;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P6;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P6;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P6;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P6;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P7;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P7;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P7;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P7;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P8;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P8;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P8;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P8;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P9;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P9;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P9;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P9;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P10;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P10;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P10;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P10;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P11;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P11;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P11;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P11;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P12;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P12;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P12;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P12;
}}}}}}}}}}}
float dstN = posM.x - posN.x;
float dstP = posP.x - posM.x;
if(!horzSpan) dstN = posM.y - posN.y;
if(!horzSpan) dstP = posP.y - posM.y;
bool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero;
float spanLength = (dstP + dstN);
bool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero;
float spanLengthRcp = 1.0/spanLength;
bool directionN = dstN < dstP;
float dst = min(dstN, dstP);
bool goodSpan = directionN ? goodSpanN : goodSpanP;
float subpixG = subpixF * subpixF;
float pixelOffset = (dst * (-spanLengthRcp)) + 0.5;
float subpixH = subpixG * fxaaSubpix;
float pixelOffsetGood = goodSpan ? pixelOffset : 0.0;
float pixelOffsetSubpix = max(pixelOffsetGood, subpixH);
if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;
if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;
return float4(FxaaTexTop(tex, posM).xyz, lumaM);
}
#if (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
float4 FxaaPass(float4 FxaaColor, float2 uv0)
#elif (FXAA_HLSL == 1)
float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
#elif defined(__METAL_VERSION__)
float4 FxaaPass(float4 FxaaColor, float2 uv0, texture2d<float> tex)
#endif
{
#if (FXAA_HLSL == 1)
FxaaTex tex;
tex.tex = Texture;
tex.smpl = TextureSampler;
float2 PixelSize;
Texture.GetDimensions(PixelSize.x, PixelSize.y);
FxaaColor = FxaaPixelShader(uv0, tex, 1.0/PixelSize.xy, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
vec2 PixelSize = vec2(textureSize(TextureSampler, 0));
FxaaColor = FxaaPixelShader(uv0, TextureSampler, 1.0/PixelSize.xy, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#elif defined(__METAL_VERSION__)
float2 PixelSize = float2(tex.get_width(), tex.get_height());
FxaaColor = FxaaPixelShader(uv0, tex, 1.f/PixelSize, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#endif
return FxaaColor;
}
/*------------------------------------------------------------------------------
[MAIN() & COMBINE PASS CODE SECTION]
------------------------------------------------------------------------------*/
#if (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
void main()
{
vec4 color = texture(TextureSampler, PSin_t);
color = PreGammaPass(color);
color = FxaaPass(color, PSin_t);
SV_Target0 = float4(color.rgb, 1.0);
}
#elif (FXAA_HLSL == 1)
PS_OUTPUT main(VS_OUTPUT input)
{
PS_OUTPUT output;
float4 color = Texture.Sample(TextureSampler, input.t);
color = PreGammaPass(color);
color = FxaaPass(color, input.t);
output.c = float4(color.rgb, 1.0);
return output;
}
// Metal main function in in fxaa.metal
#endif
File diff suppressed because it is too large. Load diff
@@ -1,470 +0,0 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
//#version 420 // Keep it for text editor detection
layout(std140, binding = 1) uniform cb20
{
vec2 VertexScale;
vec2 VertexOffset;
vec2 TextureScale;
vec2 TextureOffset;
vec2 PointSize;
uint MaxDepth;
float LineAA1Width;
};
#ifdef VERTEX_SHADER
#ifndef VS_EXPAND_NONE
#define VS_EXPAND_NONE 0
#define VS_EXPAND_POINT 1
#define VS_EXPAND_LINE 2
#define VS_EXPAND_SPRITE 3
#define VS_EXPAND_LINE_AA1 4
#define VS_EXPAND_TRIANGLE_AA1 5
#endif
out SHADER
{
vec4 t_float;
vec4 t_int;
#if VS_IIP != 0
vec4 c;
#else
flat vec4 c;
#endif
float inv_cov; // We use the inverse to make it simpler to interpolate.
flat uint interior; // 1 for triangle interior; 0 for edge.
} VSout;
const float exp_min32 = exp2(-32.0f);
#if VS_EXPAND == VS_EXPAND_NONE
layout(location = 0) in vec2 i_st;
layout(location = 2) in vec4 i_c;
layout(location = 3) in float i_q;
layout(location = 4) in uvec2 i_p;
layout(location = 5) in uint i_z;
layout(location = 6) in uvec2 i_uv;
layout(location = 7) in vec4 i_f;
void texture_coord()
{
vec2 uv = vec2(i_uv) - TextureOffset;
vec2 st = i_st - TextureOffset;
// Float coordinate
VSout.t_float.xy = st;
VSout.t_float.w = i_q;
// Integer coordinate => normalized
VSout.t_int.xy = uv * TextureScale;
#if VS_FST
// Integer coordinate => integral
VSout.t_int.zw = uv;
#else
// Some games uses float coordinate for post-processing effect
VSout.t_int.zw = st / TextureScale;
#endif
}
void vs_main()
{
// Clamp to max depth, gs doesn't wrap
highp uint z = min(i_z, MaxDepth);
// pos -= 0.05 (1/320 pixel) helps avoiding rounding problems (integral part of pos is usually 5 digits, 0.05 is about as low as we can go)
// example: ceil(afterseveralvertextransformations(y = 133)) => 134 => line 133 stays empty
// input granularity is 1/16 pixel, anything smaller than that won't step drawing up/left by one pixel
// example: 133.0625 (133 + 1/16) should start from line 134, ceil(133.0625 - 0.05) still above 133
gl_Position.xy = vec2(i_p) - vec2(0.05f, 0.05f);
gl_Position.xy = gl_Position.xy * VertexScale - VertexOffset;
#if HAS_CLIP_CONTROL
gl_Position.z = float(z) * exp_min32;
#else
gl_Position.z = (float(z) * exp_min32) * 2.0f - 1.0f;
#endif
gl_Position.w = 1.0f;
texture_coord();
VSout.c = i_c;
VSout.t_float.z = i_f.x; // pack for with texture
#if VS_POINT_SIZE
gl_PointSize = PointSize.x;
#endif
}
#else // VS_EXPAND
struct RawVertex
{
vec2 ST;
uint RGBA;
float Q;
uint XY;
uint Z;
uint UV;
uint FOG;
};
layout(std140, binding = 4) uniform cb22
{
uint BaseVertex;
uint BaseIndex;
uint pad_cb22_0;
uint pad_cb22_1;
};
layout(std140, binding = 2) readonly buffer VertexBuffer {
RawVertex vertex_buffer[];
};
// Warning: use std430 instead of std140 so that the ints are tightly packed.
layout(std430, binding = 3) readonly buffer IndexBuffer {
uint index_buffer[];
};
struct ProcessedVertex
{
vec4 p;
vec4 t_float;
vec4 t_int;
vec4 c;
};
uint load_index(uint _i)
{
uint i = _i + BaseIndex;
// i is even => load lower 16 bits; i odd => load upper 16 bits.
uint shift = (i & 1u) << 4u;
return (index_buffer[i >> 1u] >> shift) & 0xFFFFu;
}
ProcessedVertex load_vertex(uint index)
{
RawVertex rvtx = vertex_buffer[BaseVertex + index];
vec2 i_st = rvtx.ST;
vec4 i_c = vec4(uvec4(bitfieldExtract(rvtx.RGBA, 0, 8), bitfieldExtract(rvtx.RGBA, 8, 8),
bitfieldExtract(rvtx.RGBA, 16, 8), bitfieldExtract(rvtx.RGBA, 24, 8)));
float i_q = rvtx.Q;
uvec2 i_p = uvec2(bitfieldExtract(rvtx.XY, 0, 16), bitfieldExtract(rvtx.XY, 16, 16));
uint i_z = rvtx.Z;
uvec2 i_uv = uvec2(bitfieldExtract(rvtx.UV, 0, 16), bitfieldExtract(rvtx.UV, 16, 16));
vec4 i_f = unpackUnorm4x8(rvtx.FOG);
ProcessedVertex vtx;
uint z = min(i_z, MaxDepth);
vtx.p.xy = vec2(i_p) - vec2(0.05f, 0.05f);
vtx.p.xy = vtx.p.xy * VertexScale - VertexOffset;
#if HAS_CLIP_CONTROL
vtx.p.z = float(z) * exp_min32;
#else
vtx.p.z = (float(z) * exp_min32) * 2.0f - 1.0f;
#endif
vtx.p.w = 1.0f;
vec2 uv = vec2(i_uv) - TextureOffset;
vec2 st = i_st - TextureOffset;
vtx.t_float.xy = st;
vtx.t_float.w = i_q;
vtx.t_int.xy = uv * TextureScale;
#if VS_FST
vtx.t_int.zw = uv;
#else
vtx.t_int.zw = st / TextureScale;
#endif
vtx.c = i_c;
vtx.t_float.z = i_f.x;
return vtx;
}
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
vec2 get_xy_unscaled(vec2 xy)
{
return round(xy / VertexScale) / 16.0f;
}
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
mat2 get_xy_deltas_unscaled(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
vec2 xy0 = get_xy_unscaled(v0.p.xy);
vec2 xy1 = get_xy_unscaled(v1.p.xy);
vec2 xy2 = get_xy_unscaled(v2.p.xy);
return mat2(xy1 - xy0, xy2 - xy0);
}
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
// expand on on the side towards the outside of the triangle.
vec2 get_aa1_triangle_expand_dir(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
mat2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2);
vec2 line_delta = xy_deltas[0];
vec2 line_opposite = xy_deltas[1];
vec2 line_normal = vec2(line_delta.y, -line_delta.x);
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 1.0f) : vec2(1.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
return line_expand;
}
mat2 get_inverse(mat2 mat, float det)
{
return mat2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1.0f / det);
}
// Extrapolate triangle attributes from the first vertex along the given direction.
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
void extrapolate_aa1_triangle_edge(inout ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2, mat2 dp_mat, vec2 dp)
{
// Get texture deltas
#if VS_TME
#if VS_FST
mat2 dt = mat2(v1.t_int.zw - v0.t_int.zw, v2.t_int.zw - v0.t_int.zw);
#else
mat2 dt = mat2(v1.t_float.xy - v0.t_float.xy, v2.t_float.xy - v0.t_float.xy);
#endif
#endif
// Get color delta if interpolating
#if VS_IIP
mat2x4 dc = mat2x4(v1.c - v0.c, v2.c - v0.c);
#endif
vec2 dz = vec2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
vec2 df = vec2(v1.t_float.z - v0.t_float.z, v2.t_float.z - v0.t_float.z); // Fog deltas
vec2 dq = vec2(v1.t_float.w - v0.t_float.w, v2.t_float.w - v0.t_float.w); // Q deltas
// To prevent unstable extrapolation, do not extrapolate if the
// minimum perpendicular length of the triangle is < 2 pixels.
float dp_det = determinant(dp_mat); // Twice signed triangle area.
float len0 = length(dp_mat[0]);
float len1 = length(dp_mat[1]);
float len2 = length(dp_mat[1] - dp_mat[0]);
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
// Get the position -> barycentric weight matrix
mat2 inv_dp_mat = get_inverse(dp_mat, dp_det);
vec2 weights = min_perp_length < 2.0f ? vec2(0) : inv_dp_mat * dp;
v0.p.xy += dp * PointSize; // Extrapolate position
// Extrapolate texture coords
#if VS_TME
#if VS_FST
v0.t_int.zw += dt * weights;
v0.t_int.xy = v0.t_int.zw * TextureScale;
#else
v0.t_float.xy += dt * weights;
v0.t_int.zw = v0.t_float.xy / TextureScale;
v0.t_float.w += dot(dq, weights);
#endif
#endif
// Extrapolate and clamp color
#if VS_IIP
v0.c += dc * weights;
v0.c = clamp(v0.c, vec4(0), vec4(255));
#endif
v0.p.z += dot(dz, weights); // Extrapolate depth
v0.t_float.z += dot(df, weights); // Extrapolate fog
}
void main()
{
ProcessedVertex vtx;
uint vid = uint(gl_VertexID);
#if VS_EXPAND == VS_EXPAND_POINT
vtx = load_vertex(vid >> 2);
vtx.p.x += ((vid & 1u) != 0u) ? PointSize.x : 0.0f;
vtx.p.y += ((vid & 2u) != 0u) ? PointSize.y : 0.0f;
#elif (VS_EXPAND == VS_EXPAND_LINE) || (VS_EXPAND == VS_EXPAND_LINE_AA1)
uint vid_base = vid >> 2;
bool is_bottom = (vid & 2u) != 0u;
bool is_right = (vid & 1u) != 0u;
uint vid_other = is_bottom ? vid_base - 1u : vid_base + 1u;
vtx = load_vertex(vid_base);
ProcessedVertex other = load_vertex(vid_other);
// Use bottom minus top for delta regardless of which vertex we are expanding.
vec2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
vec2 line_vector = normalize(line_delta / VertexScale);
vec2 line_expand = vec2(line_vector.y, -line_vector.x);
#if VS_EXPAND == VS_EXPAND_LINE_AA1
line_expand *= 2.0f * LineAA1Width;
#endif
vec2 line_width = (line_expand * PointSize) / 2.0f;
vec2 offset = is_right ? line_width : -line_width;
vtx.p.xy += offset;
#if VS_EXPAND == VS_EXPAND_LINE_AA1
VSout.inv_cov = is_right ? 1.0f : -1.0f;
#endif
// Lines will be run as (0 1 2) (1 2 3)
// This means that both triangles will have a point based off the top line point as their first point
// So we don't have to do anything for !IIP
#elif VS_EXPAND == VS_EXPAND_SPRITE
// Sprite points are always in pairs
uint vid_base = vid >> 1;
uint vid_lt = vid_base & ~1u;
uint vid_rb = vid_base | 1u;
ProcessedVertex lt = load_vertex(vid_lt);
ProcessedVertex rb = load_vertex(vid_rb);
vtx = rb;
bool is_right = ((vid & 1u) != 0u);
vtx.p.x = is_right ? lt.p.x : vtx.p.x;
vtx.t_float.x = is_right ? lt.t_float.x : vtx.t_float.x;
vtx.t_int.xz = is_right ? lt.t_int.xz : vtx.t_int.xz;
bool is_bottom = ((vid & 2u) != 0u);
vtx.p.y = is_bottom ? lt.p.y : vtx.p.y;
vtx.t_float.y = is_bottom ? lt.t_float.y : vtx.t_float.y;
vtx.t_int.yw = is_bottom ? lt.t_int.yw : vtx.t_int.yw;
#elif VS_EXPAND == VS_EXPAND_TRIANGLE_AA1
// Triangles with AA1 are expanded as follows:
// - Vertices 0-2: Interior of triangle (1 triangle).
// - Vertices 3-8: First edge expanded (2 triangles).
// - Vertices 9-14: Second edge expanded (2 triangles).
// - Vertices 15-20: Third edge expanded (2 triangles).
// - Vertices 21-26: First corner cap (2 triangles).
// - Vertices 27-32: Second corner cap (2 triangles).
// - Vertices 33-38: Third corner cap (2 triangles).
uint prim_id = vid / 39u;
uint prim_offset = vid - 39u * prim_id; // range: 0-38
bool interior = prim_offset < 3u;
bool edge = 3u <= prim_offset && prim_offset < 21u;
if (interior)
{
vtx = load_vertex(load_index(3u * prim_id + prim_offset));
VSout.inv_cov = 0.0f; // Full coverage
VSout.interior = 1u;
}
else if (edge)
{
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
uint prim_offset_edges = prim_offset - 3u; // range: 0-17
uint i0 = prim_offset_edges / 6u;
uint i1 = (i0 >= 2u) ? i0 - 2u : i0 + 1u;
uint i2 = (i0 >= 1u) ? i0 - 1u : i0 + 2u;
uint edge_offset = prim_offset_edges - 6u * i0; // range: 0-5
// Note: order of top/bottom, inside/outside is arbitrary,
// as long as it assembles into two triangles forming a quad.
bool is_bottom = (2u <= edge_offset) && (edge_offset <= 4u);
bool is_outside = (edge_offset & 1u) != 0u;
vtx = load_vertex(load_index(3u * prim_id + (is_bottom ? i1 : i0)));
ProcessedVertex other = load_vertex(load_index(3u * prim_id + (is_bottom ? i0 : i1)));
ProcessedVertex opposite = load_vertex(load_index(3u * prim_id + i2));
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
vec2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(vtx, other, opposite) : vec2(0);
// Do actual extrapolation, or no-op if expand_dir == 0.
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
VSout.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
VSout.interior = 0u;
}
else // Corner cap
{
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
uint prim_offset_cap = prim_offset - 21u; // range: 0-17
uint i0 = prim_offset_cap / 6u;
uint i1 = (i0 >= 2u) ? i0 - 2u : i0 + 1u;
uint i2 = (i0 >= 1u) ? i0 - 1u : i0 + 2u;
uint cap_offset = prim_offset_cap - 6u * i0; // range: 0-5
bool is_near_corner = cap_offset == 0u || cap_offset == 3u;
bool is_far_corner = cap_offset == 2u || cap_offset == 5u;
bool is_first_tri = cap_offset < 3u;
// First triangle is on the side of vertex i1 and second is on the side of vertex i2.
vtx = load_vertex(load_index(3u * prim_id + i0));
ProcessedVertex other = load_vertex(load_index(3u * prim_id + (is_first_tri ? i1 : i2)));
ProcessedVertex opposite = load_vertex(load_index(3u * prim_id + (is_first_tri ? i2 : i1)));
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
// Get the edge expansion directions of both incident edges.
vec2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(vtx, other, opposite);
vec2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(vtx, opposite, other);
// Check if the corner is already filled by the expanded edges.
// This happens if the expand directions are the same.
// If so we output a degenerate triangle at this corner.
bool corner_filled = all(equal(edge_expand_dir_0, edge_expand_dir_1));
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
vec2 far_corner_dir = corner_filled ? vec2(0) : -normalize((pos_deltas[0] + pos_deltas[1]) / 2.0f);
// Determine the expand direction.
vec2 expand_dir = is_near_corner ? vec2(0) : // No extrapolation
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
edge_expand_dir_0; // Standard AA1 edge expansion
// Do the actual extrapolation (no-op if expand_dir == 0).
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
VSout.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
VSout.interior = 0u;
}
#endif
gl_Position = vtx.p;
VSout.t_float = vtx.t_float;
VSout.t_int = vtx.t_int;
VSout.c = vtx.c;
}
#endif // VS_EXPAND
#endif // VERTEX_SHADER
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+129 -414
View File
File diff suppressed because it is too large. Load diff
-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"
+39
View File
@@ -93,6 +93,7 @@ files:
- name: colecovision.rom
system: colecovision
required: true
mode: standalone
sha256: "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad"
validation: [sha256]
description: "ColecoVision BIOS"
@@ -102,6 +103,7 @@ files:
- name: disksys.rom
system: famicom-disk-system
required: true
mode: standalone
sha256: "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72"
validation: [sha256]
description: "Famicom Disk System BIOS"
@@ -111,6 +113,7 @@ files:
- name: gba_bios.bin
system: game-boy-advance
required: true
mode: standalone
sha256: "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570"
validation: [sha256]
description: "GBA BIOS"
@@ -120,6 +123,7 @@ files:
- name: bios.gg
system: game-gear
required: false
mode: standalone
sha256: "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2"
validation: [sha256]
description: "Game Gear BIOS"
@@ -130,6 +134,7 @@ files:
system: master-system
region: [north-america]
required: false
mode: standalone
sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50"
validation: [sha256]
description: "Master System BIOS (US)"
@@ -139,6 +144,7 @@ files:
system: master-system
region: [japan]
required: false
mode: standalone
sha256: "67846e26764bd862f19179294347f7353a4166b62ac4198a5ec32933b7da486e"
validation: [sha256]
description: "Master System BIOS (Japan)"
@@ -148,6 +154,7 @@ files:
system: master-system
region: [europe]
required: false
mode: standalone
sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50"
validation: [sha256]
description: "Master System BIOS (Europe), same binary as US"
@@ -159,6 +166,7 @@ files:
system: mega-cd
region: [north-america]
required: true
mode: standalone
sha256: "fb477cdbf94c84424c2feca4fe40656d85393fe7b7b401911b45ad2eb991258c"
validation: [sha256]
description: "Mega CD / Sega CD BIOS (US)"
@@ -168,6 +176,7 @@ files:
system: mega-cd
region: [japan]
required: true
mode: standalone
sha256: "7133fc2dd2fe5b7d0acd53a5f10f3d00b5d31270239ad20d74ef32393e24af88"
validation: [sha256]
description: "Mega CD BIOS (Japan)"
@@ -177,6 +186,7 @@ files:
system: mega-cd
region: [europe]
required: true
mode: standalone
sha256: "fe608a2a07676a23ab5fd5eee2f53c9e2526d69a28aa16ccd85c0ec42e6933cb"
validation: [sha256]
description: "Mega CD BIOS (Europe)"
@@ -187,6 +197,7 @@ files:
system: mega-ld
region: [north-america]
required: true
mode: standalone
sha256: "e89b5a319f66406611ec82fe5c4aa6827c175a05135bd7bd177366cba0465021"
validation: [sha256]
description: "LaserActive SEGA PAC BIOS (US), v1.04"
@@ -196,6 +207,7 @@ files:
system: mega-ld
region: [japan]
required: true
mode: standalone
sha256: "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94"
validation: [sha256]
description: "LaserActive SEGA PAC BIOS (Japan), v1.02"
@@ -205,6 +217,7 @@ files:
- name: MSX.ROM
system: msx
required: true
mode: standalone
sha256: "413a2b601a94b3792e054be2439cc77a1819cceadbfa9542f88d51c7480f2ef0"
validation: [sha256]
description: "MSX BIOS ROM (Japan)"
@@ -214,6 +227,7 @@ files:
- name: MSX2.ROM
system: msx2
required: true
mode: standalone
sha256: "0c672d86ead61a97f49a583b88b7c1905da120645cd44f0c9f2baf4f4631e0b1"
validation: [sha256]
description: "MSX2 main BIOS ROM (Japan)"
@@ -222,6 +236,7 @@ files:
- name: MSX2EXT.ROM
system: msx2
required: true
mode: standalone
sha256: "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300"
validation: [sha256]
description: "MSX2 sub ROM (Japan)"
@@ -231,6 +246,7 @@ files:
- name: neo-epo.bin
system: neo-geo
required: true
mode: standalone
description: "Neo Geo AES BIOS. Accepts neogeo.zip (extracts neo-epo.bin) or direct file. Byte-swapped on load."
source_ref: "desktop-ui/emulator/neo-geo-aes.cpp:14, mia/system/neo-geo-aes.cpp:16"
@@ -238,6 +254,7 @@ files:
- name: sp-45.sp1
system: neo-geo
required: true
mode: standalone
description: "Neo Geo MVS BIOS. Accepts neogeo.zip (extracts sp-45.sp1) or direct file. Byte-swapped on load."
source_ref: "desktop-ui/emulator/neo-geo-mvs.cpp:15, mia/system/neo-geo-mvs.cpp:16"
@@ -245,6 +262,7 @@ files:
- name: ngp_bios.rom
system: neo-geo-pocket
required: true
mode: standalone
sha256: "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7"
validation: [sha256]
description: "Neo Geo Pocket BIOS"
@@ -254,6 +272,7 @@ files:
- name: ngpc_bios.rom
system: neo-geo-pocket-color
required: true
mode: standalone
sha256: "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d"
validation: [sha256]
description: "Neo Geo Pocket Color BIOS"
@@ -264,6 +283,7 @@ files:
system: nintendo-64dd
region: [japan]
required: true
mode: standalone
sha256: "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b"
validation: [sha256]
description: "Nintendo 64DD IPL ROM (Japan retail)"
@@ -273,6 +293,7 @@ files:
system: nintendo-64dd
region: [north-america]
required: true
mode: standalone
sha256: "e9fec87a45fba02399e88064b9e2f8cf0f2106e351c58279a87f05da5bc984ad"
validation: [sha256]
description: "Nintendo 64DD IPL ROM (US dev kit)"
@@ -281,6 +302,7 @@ files:
- name: 64dd_ipl_DEV.bin
system: nintendo-64dd
required: true
mode: standalone
sha256: "9c2962a8b994a29e4cd04b3a6e4ed730a751414655ab6a9799ebf5fc08b79d44"
validation: [sha256]
description: "Nintendo 64DD IPL ROM (development)"
@@ -290,6 +312,7 @@ files:
- name: syscard1.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "afe9f27f91ac918348555b86298b4f984643eafa2773196f2c5441ea84f0c3bb"
validation: [sha256]
description: "PC Engine CD-ROM2 System Card v1.0 (Japan). Also used by PC Engine LD."
@@ -298,6 +321,7 @@ files:
- name: syscard3.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce"
validation: [sha256]
description: "Super CD-ROM2 System Card v3.0 (Japan). Also used by SuperGrafx CD as Arcade Card."
@@ -306,6 +330,7 @@ files:
- name: syscard3u.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "cadac2725711b3c442bcf237b02f5a5210c96f17625c35fa58f009e0ed39e4db"
validation: [sha256]
description: "TurboGrafx-CD Super System Card v3.0 (US)"
@@ -314,6 +339,7 @@ files:
- name: games_express.pce
system: pc-engine-cd
required: true
mode: standalone
sha256: "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d"
validation: [sha256]
description: "Games Express CD Card (Japan). Also used by PC Engine LD."
@@ -323,6 +349,7 @@ files:
- name: pac-n10.pce
system: pc-engine-ld
required: true
mode: standalone
sha256: "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4"
validation: [sha256]
description: "NEC PAC-N10 LaserActive module (US)"
@@ -331,6 +358,7 @@ files:
- name: pac-n1.pce
system: pc-engine-ld
required: true
mode: standalone
sha256: "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f"
validation: [sha256]
description: "NEC PAC-N1 LaserActive module (Japan)"
@@ -339,6 +367,7 @@ files:
- name: pce-lp1.pce
system: pc-engine-ld
required: true
mode: standalone
sha256: "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0"
validation: [sha256]
description: "NEC PCE-LP1 LaserActive module (Japan)"
@@ -348,6 +377,7 @@ files:
- name: scph5501.bin
system: sony-playstation
required: true
mode: standalone
sha256: "11052b6499e466bbf0a709b1f9cb6834a9418e66680387912451e971cf8a1fef"
validation: [sha256]
description: "PlayStation BIOS (US)"
@@ -356,6 +386,7 @@ files:
- name: scph5500.bin
system: sony-playstation
required: true
mode: standalone
sha256: "9c0421858e217805f4abe18698afea8d5aa36ff0727eb8484944e00eb5e7eadb"
validation: [sha256]
description: "PlayStation BIOS (Japan)"
@@ -364,6 +395,7 @@ files:
- name: scph5502.bin
system: sony-playstation
required: true
mode: standalone
sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09"
validation: [sha256]
description: "PlayStation BIOS (Europe)"
@@ -374,6 +406,7 @@ files:
system: saturn
region: [north-america]
required: true
mode: standalone
description: "Sega Saturn BIOS (US)"
source_ref: "desktop-ui/emulator/saturn.cpp:14"
@@ -381,6 +414,7 @@ files:
system: saturn
region: [japan]
required: true
mode: standalone
description: "Sega Saturn BIOS (Japan)"
source_ref: "desktop-ui/emulator/saturn.cpp:15"
@@ -388,6 +422,7 @@ files:
system: saturn
region: [europe]
required: true
mode: standalone
description: "Sega Saturn BIOS (Europe)"
source_ref: "desktop-ui/emulator/saturn.cpp:16"
@@ -395,6 +430,7 @@ files:
- name: SGB1.sfc
system: super-famicom
required: false
mode: standalone
category: game_data
description: "Super Game Boy cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
@@ -402,6 +438,7 @@ files:
- name: SGB2.sfc
system: super-famicom
required: false
mode: standalone
category: game_data
description: "Super Game Boy 2 cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
@@ -409,6 +446,7 @@ files:
- name: STBIOS.bin
system: super-famicom
required: false
mode: standalone
category: game_data
description: "SuFami Turbo adapter BIOS. Loaded via CLI as SFC cartridge alongside SuFami games."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
@@ -416,6 +454,7 @@ files:
- name: BS-X.bin
system: super-famicom
required: false
mode: standalone
category: game_data
description: "Satellaview BS-X Town cartridge ROM. Loaded via CLI as SFC cartridge for BS games."
source_ref: "EmuDeck emuDeckares.sh (launcher)"
-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"
+3
View File
@@ -101,6 +101,7 @@ files:
- name: "psxonpsp660.bin"
description: "PSP embedded PS1 BIOS (region-free override)"
region: [world]
required: false
size: 524288
sha1: "96880d1ca92a016ff054be5159bb06fe03cb4e14"
@@ -113,6 +114,7 @@ files:
- name: "ps1_rom.bin"
description: "PS3 embedded PS1 BIOS, first 512KB extracted (region-free override)"
region: [world]
required: false
sha1: "c40146361eb8cf670b19fdc9759190257803cab7"
md5: "81bbe60ba7a3d1cea1d48c14cbcc647b"
@@ -124,6 +126,7 @@ files:
- name: "openbios.bin"
description: "OpenBIOS (open-source PS1 HLE BIOS, region-free override)"
region: [world]
required: false
size: 524288
mode: libretro
+5 -6
View File
@@ -14,14 +14,13 @@ notes: >
Doom 3 source port based on dhewm3 (GPL Doom 3 engine). The core accepts
.pk4 extensions (retro_get_system_info sets valid_extensions = "pk4").
Content is loaded directly from the .pk4 file path - the engine extracts
the grandparent directory as fs_basepath (libretro.cpp:2381,2449-2450
the grandparent directory as fs_basepath (libretro.cpp:1097,1135-1136
via extract_directory called twice on info->path) and expects the standard
Doom 3 directory layout with base/ containing pak000.pk4 through pak008.pk4.
BASE_GAMEDIR is hardcoded to "base" (Licensee.h:58). One binary serves both
games: content in a directory named d3xp, or the doom_game core option,
passes "+set fs_game d3xp" (libretro.cpp:1635-1643, 2384-2405) to load the
Resurrection of Evil expansion from a d3xp/ subdirectory alongside base/.
The filesystem code (FileSystem.cpp:2207)
BASE_GAMEDIR is hardcoded to "base" (Licensee.h:58). The boom3_xp variant
compiles with _D3XP defined, which passes "+set fs_game d3xp" at startup
(libretro.cpp:538-544) to load the Resurrection of Evil expansion from a
d3xp/ subdirectory alongside base/. The filesystem code (FileSystem.cpp:2125)
scans for .pk4 files in the game directory and loads them in order, with
higher-numbered paks overriding lower ones. The core requires OpenGL
(RETRO_ENVIRONMENT_SET_HW_RENDER) and XRGB8888 pixel format. All required
+4
View File
@@ -25,18 +25,21 @@ files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:655-656"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:659-660"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:661-662"
@@ -44,6 +47,7 @@ files:
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:274-292"
+118
View File
@@ -62,6 +62,7 @@ files:
size: 2097152
crc32: "54c0c963"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:58-65"
# -------------------------------------------------------
@@ -74,6 +75,7 @@ files:
crc32: "79434781"
required: true
aliases: [basic2.rom]
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:78-85"
- name: os12.rom
@@ -82,6 +84,7 @@ files:
size: 16384
crc32: "3c14fc70"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:436-443"
- name: dfs09.rom
@@ -91,6 +94,7 @@ files:
crc32: "3ce609cf"
required: false
note: "Conditional: only when using 8271 disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:444-451"
- name: dfs-2.26.rom
@@ -100,6 +104,7 @@ files:
crc32: "5ae33e94"
required: false
note: "Conditional: needed for 1770 DFS or tube processor"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:452-459"
- name: adfs-1.30.rom
@@ -109,6 +114,7 @@ files:
crc32: "d3855588"
required: false
note: "Conditional: needed for ADFS support"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:460-467"
- name: ADT-1.40.rom
@@ -118,6 +124,7 @@ files:
crc32: "8314fed0"
required: false
note: "Optional: fetched separately, silently skipped if absent"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:468-475"
- name: TUBE110.rom
@@ -127,6 +134,7 @@ files:
crc32: "9ec2dbd0"
required: false
note: "Conditional: needed for 6502 tube processor"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:476-483"
- name: Z80_122.rom
@@ -136,6 +144,7 @@ files:
crc32: "b5851efc"
required: false
note: "Conditional: needed for Z80 tube processor"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:484-491"
# -------------------------------------------------------
@@ -147,6 +156,7 @@ files:
size: 16384
crc32: "79434781"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:90-97"
- name: os.rom
@@ -155,6 +165,7 @@ files:
size: 16384
crc32: "bf63fb1f"
required: true
mode: standalone
note: >
The catalogue accepts five images: 406a42ce, 0886741b, bf63fb1f, 6fea0978
and d61135d7. They differ only in the credits text, which lives on the IO
@@ -168,6 +179,7 @@ files:
crc32: "51523993"
required: false
note: "Conditional: needed for PRES ADFS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:98-105"
- name: ADFS-E00_2.rom
@@ -177,6 +189,7 @@ files:
crc32: "8d17de0e"
required: false
note: "Conditional: needed for PRES ADFS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:106-113"
- name: adfs.rom
@@ -186,6 +199,7 @@ files:
crc32: "3289bdc6"
required: false
note: "Conditional: needed for Acorn ADFS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:114-121"
- name: DFS-1770-2.20.rom
@@ -195,6 +209,7 @@ files:
crc32: "f3dc9bc5"
required: false
note: "Conditional: needed for DFS disk images"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:122-129"
- name: ELK130E.rom
@@ -204,6 +219,7 @@ files:
crc32: "a923368d"
required: false
note: "Conditional: needed for IDE interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:130-137"
- name: AP6v133.rom
@@ -213,6 +229,7 @@ files:
crc32: "e0013cfc"
required: false
note: "Conditional: needed for AP6 commands. The catalogue records crc32 e0013cfc, which is the 7772-byte RetroHardware Plus 1 Support 1.33c build of 16 Jun 2020, and declares 8 KiB: Request::Node::validate() resizes the loaded image to 8192 bytes and the fetcher reads any length."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:138-145, Machines/Utility/ROMCatalogue.cpp:1484-1499, OSBindings/SDL/main.cpp:756-783"
# -------------------------------------------------------
@@ -224,6 +241,7 @@ files:
size: 262144
crc32: "c4f0f55f"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:193-200"
# -------------------------------------------------------
@@ -237,6 +255,7 @@ files:
crc32: "815752df"
required: false
note: "Built into binary. Required for CPC 464 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:261-268, Machines/Utility/ROMLibrary.cpp:14535"
- name: basic464.rom
@@ -246,6 +265,7 @@ files:
crc32: "7d9a3bac"
required: false
note: "Built into binary. Required for CPC 464 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:269-276, Machines/Utility/ROMLibrary.cpp:14532"
- name: os664.rom
@@ -255,6 +275,7 @@ files:
crc32: "3f5a6dc4"
required: false
note: "Built into binary. Required for CPC 664 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:277-283, Machines/Utility/ROMLibrary.cpp:14537"
- name: basic664.rom
@@ -264,6 +285,7 @@ files:
crc32: "32fee492"
required: false
note: "Built into binary. Required for CPC 664 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:284-291, Machines/Utility/ROMLibrary.cpp:14534"
- name: os6128.rom
@@ -273,6 +295,7 @@ files:
crc32: "0219bb74"
required: false
note: "Built into binary. Required for CPC 6128 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:292-299, Machines/Utility/ROMLibrary.cpp:14536"
- name: basic6128.rom
@@ -282,6 +305,7 @@ files:
crc32: "ca6af63d"
required: false
note: "Built into binary. Required for CPC 6128 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:300-307, Machines/Utility/ROMLibrary.cpp:14533"
- name: amsdos.rom
@@ -291,6 +315,7 @@ files:
crc32: "1fe22ecd"
required: false
note: "Built into binary. Needed for CPC 664/6128 disk support."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:253-260, Machines/Utility/ROMLibrary.cpp:14531"
# -------------------------------------------------------
@@ -303,6 +328,7 @@ files:
crc32: "ba210588"
required: true
note: "Required for original Apple II model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:336-343"
- name: apple2.rom
@@ -312,6 +338,7 @@ files:
crc32: "f66f9c26"
required: true
note: "Required for Apple II+ model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:328-335"
- name: apple2eu.rom
@@ -321,6 +348,7 @@ files:
crc32: "e12be18d"
required: true
note: "Required for Apple IIe model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:320-327"
- name: apple2e.rom
@@ -330,6 +358,7 @@ files:
crc32: "65989942"
required: true
note: "Required for Enhanced IIe model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:312-319"
- name: apple2-character.rom
@@ -339,6 +368,7 @@ files:
crc32: "64f415c6"
required: true
note: "Required for Apple II and II+ models"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:344-351"
- name: apple2eu-character.rom
@@ -348,6 +378,7 @@ files:
crc32: "816a86f1"
required: true
note: "Required for Apple IIe model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:352-359"
- name: apple2e-character.rom
@@ -357,6 +388,7 @@ files:
crc32: "2651014d"
required: true
note: "Required for Enhanced IIe model. Also used by Apple IIgs."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:360-367"
- name: scsi.rom
@@ -366,6 +398,7 @@ files:
crc32: "5aff85d3"
required: false
note: "Conditional: needed for SCSI controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:368-375"
# -------------------------------------------------------
@@ -378,6 +411,7 @@ files:
crc32: "42f124b0"
required: true
note: "Required for ROM01 model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:380-387"
- name: apple2gs.rom2
@@ -387,6 +421,7 @@ files:
crc32: "de7ddf29"
required: true
note: "Required for ROM03 model (default)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:388-395"
- name: apple2gs.chr
@@ -395,6 +430,7 @@ files:
size: 4096
crc32: "91e53cd8"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:396-403"
- name: "341s0632-2"
@@ -403,6 +439,7 @@ files:
size: 4096
crc32: "e1c11fb0"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:404-411"
# -------------------------------------------------------
@@ -414,6 +451,7 @@ files:
size: 196608
crc32: "1a586c64"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:416-423"
# -------------------------------------------------------
@@ -425,6 +463,7 @@ files:
size: 8192
crc32: "3aa93ef3"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:496-503"
# -------------------------------------------------------
@@ -438,6 +477,7 @@ files:
crc32: "718d42b1"
required: false
note: "Conditional: needed when VIC-20 has 1540 drive"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:508-515"
- name: 1541.bin
@@ -447,6 +487,7 @@ files:
crc32: "fb760019"
required: false
note: "Conditional: needed when Plus/4 has 1541 drive"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:516-523"
# -------------------------------------------------------
@@ -460,6 +501,7 @@ files:
crc32: "ce7144f6"
required: false
note: "Conditional: needed for 16-sector disk controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:528-535"
- name: state-machine-16.rom
@@ -476,6 +518,7 @@ files:
other image is read as the unadulterated P6 PROM (9796a238), its address
lines and the top nibble of each value remapped into that order. Both give
the same internal table.
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:536-543, Components/DiskII/DiskII.cpp:187-239"
- name: boot-13.rom
@@ -485,6 +528,7 @@ files:
crc32: "d34eb2ff"
required: false
note: "Conditional: needed for 13-sector disk controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:544-551"
- name: state-machine-13.rom
@@ -494,6 +538,7 @@ files:
crc32: "62e22620"
required: false
note: "Conditional: needed for 13-sector disk controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:552-559"
# -------------------------------------------------------
@@ -507,6 +552,7 @@ files:
required: true
aliases: ["Exos (198x)(Enterprise).bin"]
note: "One EXOS version required. Selected by target or any available."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:600-607"
- name: exos20.bin
@@ -517,6 +563,7 @@ files:
required: true
aliases: ["Expandible OS v2.0 (1984)(Intelligent Software).bin"]
note: "Alternative EXOS version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:611-618"
- name: exos21.bin
@@ -527,6 +574,7 @@ files:
required: true
aliases: ["Expandible OS v2.1 (1985)(Intelligent Software).bin"]
note: "Alternative EXOS version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:622-629"
- name: exos23.bin
@@ -537,6 +585,7 @@ files:
required: true
aliases: ["Expandible OS v2.3 (1987)(Intelligent Software).bin"]
note: "Alternative EXOS version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:633-640"
- name: basic10.bin
@@ -546,6 +595,7 @@ files:
crc32: "d62e4fb7"
required: true
note: "One BASIC version required. v1.0 single-file version."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:645-652"
- name: "BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin"
@@ -555,6 +605,7 @@ files:
crc32: "37bf48e1"
required: false
note: "Alternative: both parts needed together as substitute for basic10.bin"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:653-660"
- name: "BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin"
@@ -564,6 +615,7 @@ files:
crc32: "c5298c79"
required: false
note: "Alternative: both parts needed together as substitute for basic10.bin"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:661-668"
- name: basic11.bin
@@ -573,6 +625,7 @@ files:
crc32: "683cf455"
required: true
note: "Alternative BASIC version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:669-676"
- name: "BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin"
@@ -582,6 +635,7 @@ files:
crc32: "c96b7602"
required: false
note: "Alternative format of BASIC v1.1 with trailing byte"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:677-684"
- name: basic21.bin
@@ -594,6 +648,7 @@ files:
- "BASIC Interpreter v2.1 (1985)(Intelligent Software).bin"
- "BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin"
note: "Alternative BASIC version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:685-693"
- name: epdos.bin
@@ -604,6 +659,7 @@ files:
required: false
aliases: ["EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"]
note: "Conditional: needed for EPDOS disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:698-705"
- name: exdos.bin
@@ -614,6 +670,7 @@ files:
required: false
aliases: ["EX-DOS EPROM (198x)(Enterprise).bin"]
note: "Conditional: needed when DOS is set to EXDOS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:709-716"
# -------------------------------------------------------
@@ -626,6 +683,7 @@ files:
crc32: "6d0c8a28"
required: true
note: "Required for Macintosh 128k model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:724-731"
- name: mac512k.rom
@@ -635,6 +693,7 @@ files:
crc32: "cf759e0d"
required: true
note: "Required for Macintosh 512k model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:732-739"
- name: macplus.rom
@@ -649,6 +708,7 @@ files:
checksum 4D1F8172 of the third Mac Plus revision. The
machine copies whichever is supplied into its 128 KiB ROM without
reading the revision.
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:740-751, Machines/Apple/Macintosh/Macintosh.cpp:127-144"
# -------------------------------------------------------
@@ -662,6 +722,7 @@ files:
crc32: "0072ed54"
required: false
note: "Optional. Boots cartridge directly if absent."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:764-771"
- name: japanese-bios.sms
@@ -672,6 +733,7 @@ files:
crc32: "48d44a13"
required: false
note: "Optional. Used for Japan region. Boots without if absent."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:756-763"
# -------------------------------------------------------
@@ -685,6 +747,7 @@ files:
crc32: "94ee12f3"
required: true
note: "Required for MSX1. Region bytes patched in memory if regional BIOS absent."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:777-784"
- name: msx-japanese.rom
@@ -695,6 +758,7 @@ files:
crc32: "ee229390"
required: false
note: "Alternative to msx.rom for Japanese region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:785-792"
- name: msx-american.rom
@@ -704,6 +768,7 @@ files:
size: 32768
required: false
note: "Alternative to msx.rom for American region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:793-800"
- name: msx-european.rom
@@ -713,6 +778,7 @@ files:
size: 32768
required: false
note: "Alternative to msx.rom for European region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:801-808"
- name: disk.rom
@@ -722,6 +788,7 @@ files:
crc32: "721f61df"
required: false
note: "Conditional: needed for disk drive support"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:809-816"
- name: msx2.rom
@@ -732,6 +799,7 @@ files:
crc32: "6cdaf3a5"
required: true
note: "Required for MSX2"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:818-825"
- name: msx2ext.rom
@@ -742,6 +810,7 @@ files:
crc32: "66237ecf"
required: true
note: "Required for MSX2"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:826-833"
- name: fmpac.rom
@@ -751,6 +820,7 @@ files:
crc32: "0e84505d"
required: false
note: "Conditional: needed for MSX-Music support"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:834-841"
# -------------------------------------------------------
@@ -763,6 +833,7 @@ files:
crc32: "f18710b4"
required: true
note: "Required for Oric-1 model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:870-877"
- name: basic11.rom
@@ -772,6 +843,7 @@ files:
crc32: "c3a92bef"
required: true
note: "Required for Oric Atmos model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:878-885"
- name: pravetz.rom
@@ -781,6 +853,7 @@ files:
crc32: "58079502"
required: true
note: "Required for Pravetz 8D model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:886-893"
- name: colour.rom
@@ -790,6 +863,7 @@ files:
crc32: "d50fca65"
required: false
note: "Optional. Enables colour video output."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:846-853"
- name: ORIC.PAL-PROM-TBP24S10-1ab9b572.bin
@@ -799,6 +873,7 @@ files:
crc32: "1ab9b572"
required: false
note: "Optional. Alternative colour ROM."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:854-861"
- name: PRAVETZ-8D.PAL-PROM-82S129-1576a69b.bin
@@ -808,6 +883,7 @@ files:
crc32: "1576a69b"
required: false
note: "Optional. Colour ROM for Pravetz model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:862-869"
- name: bd500.rom
@@ -817,6 +893,7 @@ files:
crc32: "61952e34"
required: false
note: "Conditional: needed for Byte Drive 500 disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:894-901"
- name: jasmin.rom
@@ -826,6 +903,7 @@ files:
crc32: "37220e89"
required: false
note: "Conditional: needed for Jasmin disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:902-909"
- name: microdisc.rom
@@ -835,6 +913,7 @@ files:
crc32: "a9664a9c"
required: false
note: "Conditional: needed for Microdisc disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:910-917"
- name: 8dos.rom
@@ -844,6 +923,7 @@ files:
crc32: "49a74c06"
required: false
note: "Conditional: needed for Pravetz 8-DOS disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:918-925"
# -------------------------------------------------------
@@ -856,6 +936,7 @@ files:
crc32: "9576944c"
required: true
note: "Required for XT-class emulation"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:930-937"
- name: GLaTICK_0.8.5_AT.ROM
@@ -865,6 +946,7 @@ files:
crc32: "371ea3f1"
required: false
note: "XT-class RTC option ROM"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:938-945"
- name: "Phoenix 80286 ROM BIOS Version 3.05.bin"
@@ -874,6 +956,7 @@ files:
crc32: "8d0d318a"
required: true
note: "Required for AT-class emulation (alternative to IBM AT BIOS)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:946-953"
- name: at-bios.bin
@@ -883,6 +966,7 @@ files:
crc32: "674426be"
required: true
note: "AT-class: single-file IBM AT BIOS (alternative to split U27/U47)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:954-961"
- name: BIOS_5170_15NOV85_U27_61X9266_27256.BIN
@@ -892,6 +976,7 @@ files:
crc32: "4995be7a"
required: false
note: "AT-class: split BIOS, used with U47 chip"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:962-969"
- name: BIOS_5170_15NOV85_U47_61X9265_27256.BIN
@@ -901,6 +986,7 @@ files:
crc32: "c32713e4"
required: false
note: "AT-class: split BIOS, used with U27 chip"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:970-977"
- name: CGA.F08
@@ -910,6 +996,7 @@ files:
crc32: "a362ffe6"
required: true
note: "Required for CGA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:978-985"
- name: EUMDA9.F14
@@ -919,6 +1006,7 @@ files:
crc32: "7754882a"
required: true
note: "Required for MDA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:986-993"
- name: ibm_6277356_ega_card_u44_27128.bin
@@ -928,6 +1016,7 @@ files:
crc32: "2f2fbc40"
required: false
note: "Needed for EGA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:994-1001"
- name: ibm_vga.bin
@@ -937,6 +1026,7 @@ files:
crc32: "03b3f90d"
required: false
note: "Needed for VGA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1002-1009"
- name: ibm-basic-1.10.rom
@@ -946,6 +1036,7 @@ files:
crc32: "ebacb791"
required: false
note: "Required for AT-class with IBM BIOS (not needed with Phoenix)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1010-1017"
# -------------------------------------------------------
@@ -957,6 +1048,7 @@ files:
size: 16384
crc32: "74eaae87"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1046-1053"
- name: kernal.318004-05.bin
@@ -966,6 +1058,7 @@ files:
crc32: "71c07bd4"
required: true
note: "Default kernel version requested by machine code"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1038-1045"
- name: kernal.318004-03.bin
@@ -975,6 +1068,7 @@ files:
crc32: "77bab934"
required: false
note: "Alternative kernel version (in catalogue but not default)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1022-1029"
- name: kernal.318004-04.bin
@@ -984,6 +1078,7 @@ files:
crc32: "be54ed79"
required: false
note: "Alternative kernel version (in catalogue but not default)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1030-1037"
# -------------------------------------------------------
@@ -995,6 +1090,7 @@ files:
size: 16384
crc32: "f62d2f3a"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1157-1164"
- name: mo5-v1.1.rom
@@ -1003,6 +1099,7 @@ files:
size: 16384
crc32: "237c60bf"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1165-1172"
- name: mo6-v1.rom
@@ -1011,6 +1108,7 @@ files:
size: 65536
crc32: "45e859c6"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1173-1180"
- name: mo6-v2.rom
@@ -1019,6 +1117,7 @@ files:
size: 65536
crc32: "b4430a3b"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1181-1188"
- name: mo6-v3.rom
@@ -1027,6 +1126,7 @@ files:
size: 65536
crc32: "7a012322"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1189-1196"
- name: pc128.rom
@@ -1035,6 +1135,7 @@ files:
size: 65536
crc32: "fc173504"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1197-1204"
- name: cd90-640.rom
@@ -1044,6 +1145,7 @@ files:
crc32: "5114c0a5"
required: false
note: "Requested on top of the machine ROM when a disk is attached."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1206-1213"
# -------------------------------------------------------
@@ -1055,6 +1157,7 @@ files:
size: 8192
crc32: "db4c43c1"
required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1218-1225"
- name: characters-english.bin
@@ -1065,6 +1168,7 @@ files:
crc32: "83e032a6"
required: true
note: "Required for English region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1226-1233"
- name: kernel-pal.bin
@@ -1075,6 +1179,7 @@ files:
crc32: "4be07cb4"
required: true
note: "Required for English PAL region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1234-1241"
- name: kernel-ntsc.bin
@@ -1085,6 +1190,7 @@ files:
crc32: "e5e7c174"
required: true
note: "Required for English NTSC region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1242-1249"
- name: characters-danish.bin
@@ -1095,6 +1201,7 @@ files:
crc32: "7fc11454"
required: false
note: "Required for Danish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1250-1257"
- name: kernel-danish.bin
@@ -1105,6 +1212,7 @@ files:
crc32: "02adaf16"
required: false
note: "Required for Danish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1258-1265"
- name: characters-japanese.bin
@@ -1115,6 +1223,7 @@ files:
crc32: "fcfd8a4b"
required: false
note: "Required for Japanese region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1266-1273"
- name: kernel-japanese.bin
@@ -1125,6 +1234,7 @@ files:
crc32: "336900d7"
required: false
note: "Required for Japanese region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1274-1281"
- name: characters-swedish.bin
@@ -1135,6 +1245,7 @@ files:
crc32: "d808551d"
required: false
note: "Required for Swedish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1282-1289"
- name: kernel-swedish.bin
@@ -1145,6 +1256,7 @@ files:
crc32: "b2a60662"
required: false
note: "Required for Swedish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1290-1297"
# -------------------------------------------------------
@@ -1158,6 +1270,7 @@ files:
crc32: "ddee531f"
required: false
note: "Built into binary. Required for 16K/48K model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1302-1309, Machines/Utility/ROMLibrary.cpp:14539"
- name: 128.rom
@@ -1167,6 +1280,7 @@ files:
crc32: "2cbe8995"
required: false
note: "Built into binary. Required for 128K model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1310-1317, Machines/Utility/ROMLibrary.cpp:14540"
- name: plus2.rom
@@ -1176,6 +1290,7 @@ files:
crc32: "e7a517dc"
required: false
note: "Built into binary. Required for +2 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1318-1325, Machines/Utility/ROMLibrary.cpp:14541"
- name: plus3.rom
@@ -1185,6 +1300,7 @@ files:
crc32: "96e3c17a"
required: false
note: "Built into binary. Required for +2a/+3 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1326-1336, Machines/Utility/ROMLibrary.cpp:14542"
# -------------------------------------------------------
@@ -1198,6 +1314,7 @@ files:
crc32: "4c7fc597"
required: false
note: "Built into binary. Required for ZX80 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1341-1348, Machines/Utility/ROMLibrary.cpp:14544"
- name: zx81.rom
@@ -1207,4 +1324,5 @@ files:
crc32: "4b1dd6eb"
required: false
note: "Built into binary. Required for ZX81 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1349-1356, Machines/Utility/ROMLibrary.cpp:14545"
+3 -3
View File
@@ -28,11 +28,11 @@ notes: |
- ROMEXT[0] (upper ROM slot 0, 16KB): second half of cpc6128_bin (BASIC)
- ROMEXT[7] (upper ROM slot 7, 16KB): romdisc_bin (AMSDOS)
Called from HardResetCPC() (crocods-core/platform.c:1024-1035) using
Called from HardResetCPC() (crocods-core/platform.c:1009-1020) using
the extern byte arrays directly. No filesystem ROM loading exists in
the libretro path.
A Multiface ROM loader exists (crocods-core/multiface.c:53-64) that
A Multiface ROM loader exists (crocods-core/multiface.c:51-61) that
reads LocRomMulti via fopen(), but InitMultiface() is never called
from the libretro build or platform.c. Dead code in libretro context.
@@ -45,7 +45,7 @@ files: []
platform_details:
bios_mapping:
source_ref: "crocods-core/vga.c:185-200, crocods-core/platform.c:1024-1035"
source_ref: "crocods-core/vga.c:185-200, crocods-core/platform.c:1009-1020"
note: |
All ROMs are embedded. InitMemCPC() copies cpc6128_bin and romdisc_bin
into ROMINF and ROMEXT arrays via memcpy. No filesystem lookup occurs.
+5 -5
View File
@@ -148,7 +148,7 @@ files:
required: false
hle_fallback: true
note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:27-45,47-92"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:26-39,42-89"
- name: "keys.bin"
path: null
@@ -156,7 +156,7 @@ files:
required: false
hle_fallback: true
note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:20, Source/Core/DiscIO/NANDImporter.cpp:77-91, Source/Core/DiscIO/NANDImporter.h:35-38"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:20, Source/Core/DiscIO/NANDImporter.cpp:82-94, Source/Core/DiscIO/NANDImporter.h:46"
# -- Wii SD Card Image --
- name: "WiiSD.raw"
@@ -189,21 +189,21 @@ files:
required: false
hle_fallback: true
note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:220-304"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:239-323"
- name: "clientcakey.pem"
path: "Wii/clientcakey.pem"
required: false
hle_fallback: true
note: "SSL client private key. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:256"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:275"
- name: "rootca.pem"
path: "Wii/rootca.pem"
required: false
hle_fallback: true
note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:257"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:276"
# -- Realtek Bluetooth firmware (Wiimote passthrough) --
# Required for real Wiimote connectivity via USB Bluetooth adapters with Realtek chipsets.
+5
View File
@@ -28,24 +28,28 @@ files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:574-575, 853"
- name: "dev.keys"
required: false
path: "switch/"
mode: standalone
note: "Development-unit keys, read instead of prod.keys when use_dev_keys is set"
source_ref: "src/core/crypto/key_manager.cpp:570-571, 852"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:577-578, 850"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:579-580"
@@ -53,6 +57,7 @@ files:
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:281-302"
-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"
+16
View File
@@ -79,6 +79,7 @@ files:
description: "Sega CD BIOS (US)"
region: [north-america]
required: true
mode: standalone
size: 131072
validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:39-49 (read), frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (bios_path_for_region), backend/segacd-core/src/api.rs:77-79 (BIOS_LEN check)"
@@ -89,6 +90,7 @@ files:
description: "Mega CD BIOS (Europe)"
region: [europe]
required: true
mode: standalone
size: 131072
validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (eu_bios_path), backend/segacd-core/src/api.rs:77-79"
@@ -98,6 +100,7 @@ files:
description: "Mega CD BIOS (Japan)"
region: [japan]
required: true
mode: standalone
size: 131072
validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (jp_bios_path), backend/segacd-core/src/api.rs:77-79"
@@ -109,6 +112,7 @@ files:
system: nintendo-gba
description: "Game Boy Advance BIOS"
required: true
mode: standalone
size: 16384
validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/gba.rs:29-33 (GbaNoBios), backend/gba-core/src/memory.rs:10,102-107 (BIOS_ROM_LEN check)"
@@ -122,6 +126,7 @@ files:
system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-1"
required: true
mode: standalone
size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:336-353 (MissingDsp1Rom), backend/snes-coprocessors/src/upd77c25.rs:28-31,35-38,382-390"
note: "0x1800 program plus 0x800 data. Also used by DSP-1B titles, which have no separate slot. Layout is sliced at a fixed offset, size is not checked."
@@ -131,6 +136,7 @@ files:
system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-2"
required: true
mode: standalone
size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:340-342 (MissingDsp2Rom), :929 (Dungeon Master CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390"
@@ -139,6 +145,7 @@ files:
system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-3"
required: true
mode: standalone
size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:343-345 (MissingDsp3Rom), :931 (SD Gundam GX CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390"
@@ -147,6 +154,7 @@ files:
system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-4"
required: true
mode: standalone
size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:346-348 (MissingDsp4Rom), :933 (Top Gear 3000 CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390"
@@ -155,6 +163,7 @@ files:
system: nintendo-snes
description: "NEC uPD96050 firmware for ST010"
required: true
mode: standalone
size: 53248
source_ref: "backend/snes-core/src/memory/cartridge.rs:294-305 (MissingSt010Rom), :938-943 (variant detection), backend/snes-coprocessors/src/upd77c25.rs:28-31,35-38"
note: "0xC000 program plus 0x1000 data. Used by F1 ROC II."
@@ -164,6 +173,7 @@ files:
system: nintendo-snes
description: "NEC uPD96050 firmware for ST011"
required: true
mode: standalone
size: 53248
source_ref: "backend/snes-core/src/memory/cartridge.rs:298-300 (MissingSt011Rom), :938-943 (Hayazashi Nidan Morita Shougi CRC32 match)"
@@ -172,6 +182,7 @@ files:
system: nintendo-snes
description: "ARM firmware for ST018"
required: true
mode: standalone
min_size: 163840
validation: [size]
source_ref: "backend/snes-core/src/memory/cartridge.rs:310-320 (MissingSt018Rom), backend/snes-coprocessors/src/st018.rs:18-22,346-352 (TOTAL_ROM_LEN check)"
@@ -184,6 +195,7 @@ files:
system: sega-mastersystem
description: "Master System BIOS"
required: false
mode: standalone
source_ref: "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs:27-33,50-56,74-80,112-122, backend/smsgg-core/src/memory.rs:101-106,148-168"
note: "Read only when sms_boot_from_bios is enabled or the emulator runs without a cartridge, both off by default. One slot accepts any regional dump, no size or content check."
@@ -191,6 +203,7 @@ files:
system: sega-gamegear
description: "Game Gear BIOS"
required: false
mode: standalone
source_ref: "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs:27-33,50-56,74-80,112-122"
note: "Read only when gg_boot_from_bios is enabled or the emulator runs without a cartridge, both off by default."
@@ -201,6 +214,7 @@ files:
system: nintendo-gb
description: "Game Boy boot ROM"
required: false
mode: standalone
size: 256
validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/gb.rs:29-33,77-89, backend/gb-core/src/memory.rs:9,45-63 (DMG_BOOT_ROM_LEN check)"
@@ -210,6 +224,7 @@ files:
system: nintendo-gbc
description: "Game Boy Color boot ROM"
required: false
mode: standalone
min_size: 2048
max_size: 2304
validation: [size]
@@ -224,6 +239,7 @@ files:
system: nintendo-nes
description: "External NES color palette"
required: false
mode: standalone
min_size: 192
max_size: 1536
validation: [size]
+1 -1
View File
@@ -61,7 +61,7 @@ notes: |
the JVM default soundbank in place. VlcDir points at a VLC installation that
vlcj scans for libvlc.
ref: src/media/emulator/media/EmulatorMIDI.java:26-36,
src/main/emulator/ui/swt/Property.java:1863-1904
src/main/emulator/ui/swt/Property.java:1860-1901
files:
- name: M7_EmuSmw7.dll
+3 -4
View File
@@ -2,10 +2,9 @@ emulator: Kronos
type: libretro
core_classification: official_port
source: "https://github.com/libretro/yabause"
source_branch: kronos
upstream: "https://github.com/FCare/Kronos"
profiled_date: "2026-03-24"
source_commit: "3791ffb2221fda61d70aec5cca376418caf05669"
source_commit: "8926b0c6c347f8c5c755911ddb0ac695420ffbf8"
core_version: "v2.6.0"
display_name: "Sega - Saturn/ST-V (Kronos)"
cores:
@@ -245,7 +244,7 @@ files:
size: 2097152
validation: [size]
note: "ROM cartridge for The King of Fighters '95. Auto-detected via game DB."
source_ref: "yabause/src/utils/src/db.c:43-44, yabause/src/sys/memory/src/cs0.c:1526-1535"
source_ref: "yabause/src/utils/src/db.c:43-44, yabause/src/cs0.c:1207-1216"
- name: "mpr-19367-mx.ic1"
system: sega-saturn
@@ -253,4 +252,4 @@ files:
size: 2097152
validation: [size]
note: "ROM cartridge for Ultraman: Hikari no Kyojin Densetsu. Auto-detected via game DB."
source_ref: "yabause/src/utils/src/db.c:45, yabause/src/sys/memory/src/cs0.c:1526-1535"
source_ref: "yabause/src/utils/src/db.c:45, yabause/src/cs0.c:1207-1216"
+5 -5
View File
@@ -36,7 +36,7 @@ files:
min_size: 4194304
max_size: 8388608
validation: [size]
source_ref: "pcsx2/ps2/BiosTools.cpp:33-34,63-180,249-274,286-348"
source_ref: "pcsx2/ps2/BiosTools.cpp:231-254,267-322"
note: >
Scans pcsx2/bios/ for any file between 4 MB and 8 MB. Validates via romdir
structure parsing (RESET + ROMVER entries). User selects BIOS via core option
@@ -47,7 +47,7 @@ files:
required: false
max_size: 4194304
description: "DVD player ROM"
source_ref: "pcsx2/ps2/BiosTools.cpp:189-229,339"
source_ref: "pcsx2/ps2/BiosTools.cpp:190-211,314"
note: >
DVD player ROM. Tries <biospath>.rom1 (appended) then <biosbase>.rom1
(extension replaced). Silently skipped if not found.
@@ -57,7 +57,7 @@ files:
required: false
max_size: 524288
description: "Chinese ROM extension"
source_ref: "pcsx2/ps2/BiosTools.cpp:189-229,340"
source_ref: "pcsx2/ps2/BiosTools.cpp:190-211,315"
note: >
Chinese region ROM extension. Same naming convention as rom1.
Only present on Chinese region consoles.
@@ -68,7 +68,7 @@ files:
hle_fallback: true
size: 1024
description: "NVRAM / EEPROM data"
source_ref: "pcsx2/CDVD/CDVD.cpp:196-227"
source_ref: "pcsx2/CDVD/CDVD.cpp:224-267"
note: >
Console EEPROM data (language, timezone, region, iLink ID, OSD settings).
Path derived from BIOS path with extension replaced to .nvm.
@@ -80,7 +80,7 @@ files:
hle_fallback: true
size: 4
description: "Mechacon version"
source_ref: "pcsx2/CDVD/CDVD.cpp:229-245"
source_ref: "pcsx2/CDVD/CDVD.cpp:255-266"
note: >
Mechacon (disc drive controller) version as u32.
Auto-created with default 0x00020603 if missing.
+1047 -1154
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
+1 -1
View File
@@ -680,7 +680,7 @@ files:
required: true
category: bios_zip
system: casio-loopy
source_ref: "src/mame/casio/casloopy.cpp:2097-2105"
source_ref: "src/mame/casio/casloopy.cpp:2105-2462"
contents:
- name: hd6437021.lsi302
description: SH-1 CPU internal mask ROM
+2 -2
View File
@@ -292,7 +292,7 @@ files:
- name: "scph5502.bin"
system: sony-playstation
description: "SCPH-5502 / v3.0E BIOS"
region: [europe, oceania]
region: [europe]
required: true
size: 524288
sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09"
@@ -376,7 +376,7 @@ files:
- name: "epr-17952a.ic8"
system: sega-stv
description: "ST-V BIOS (North America)"
region: [north-america, south-korea, asia-pal, brazil, latin-america]
region: [north-america]
required: true
size: 524288
validation: [size]
+6
View File
@@ -23,24 +23,28 @@ notes: >
files:
- name: "syscard3.pce"
description: "CD-ROM System Card 3.0 (Japan)"
region: [japan]
required: true
source_ref: "libretro.c:65, libretro.c:1896-1901"
note: "Default value for pce_fast_cdbios option."
- name: "syscard2.pce"
description: "CD-ROM System Card 2.0 (Japan)"
region: [japan]
required: false
source_ref: "libretro.c:1903"
note: "pce_fast_cdbios = 'System Card 2'."
- name: "syscard1.pce"
description: "CD-ROM System Card 1.0 (Japan)"
region: [japan]
required: false
source_ref: "libretro.c:1905"
note: "pce_fast_cdbios = 'System Card 1'."
- name: "gexpress.pce"
description: "Games Express CD Card"
region: [japan]
required: false
aliases: ["gecard.pce"]
source_ref: "libretro.c:1906-1907"
@@ -48,12 +52,14 @@ files:
- name: "syscard3u.pce"
description: "CD-ROM System Card 3.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false
source_ref: "libretro.c:1908-1909"
note: "pce_fast_cdbios = 'System Card 3 US'. Not in .info."
- name: "syscard2u.pce"
description: "CD-ROM System Card 2.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false
source_ref: "libretro.c:1910-1911"
note: "pce_fast_cdbios = 'System Card 2 US'. Not in .info."
+9 -9
View File
@@ -30,7 +30,7 @@ notes: |
mgba_skip_bios (default OFF) - skip the boot animation even if loaded
The libretro port resolves BIOS filenames in retro_load_game()
(src/platform/libretro/libretro.c:2060-2118). It picks the filename
(src/platform/libretro/libretro.c:2091-2148). It picks the filename
based on detected model:
GBA -> gba_bios.bin
GB (DMG/MGB) -> gb_bios.bin
@@ -38,7 +38,7 @@ notes: |
SGB -> sgb_bios.bin
SGB2 -> gb_bios.bin (falls to default, missing case)
The standalone core (src/gb/core.c:649-696) correctly maps SGB2 to
The standalone core (src/gb/core.c:637-684) correctly maps SGB2 to
sgb_bios.bin (marked TODO for a dedicated file). The libretro port
omits GB_MODEL_SGB2 from its switch, so SGB2 falls to default.
@@ -57,7 +57,7 @@ files:
size: 16384 # 16 KB (0x4000, GBA_SIZE_BIOS)
validation: [size]
note: "GBA BIOS. HLE replacement built in. Real BIOS adds boot logo and full SWI accuracy."
source_ref: "src/platform/libretro/libretro.c:2068"
source_ref: "src/platform/libretro/libretro.c:2099"
# -------------------------------------------------------
# Game Boy - boot ROM (optional, skip fallback)
@@ -68,7 +68,7 @@ files:
size: 256 # 256 B (0x100)
validation: [crc32]
note: "DMG/MGB boot ROM. Scrolling Nintendo logo. Skipped if absent."
source_ref: "src/platform/libretro/libretro.c:2104"
source_ref: "src/platform/libretro/libretro.c:971"
# -------------------------------------------------------
# Game Boy Color - boot ROM (optional, skip fallback)
@@ -79,7 +79,7 @@ files:
size: 2304 # 2304 B (0x900)
validation: [crc32]
note: "CGB boot ROM. Used for CGB, AGB (GBA in GB mode), and SCGB models."
source_ref: "src/platform/libretro/libretro.c:2097"
source_ref: "src/platform/libretro/libretro.c:2127"
# -------------------------------------------------------
# Super Game Boy - boot ROM (optional, skip fallback)
@@ -89,14 +89,14 @@ files:
required: false
size: 256 # 256 B (0x100)
validation: [crc32]
note: "SGB boot ROM. SGB2 maps here in standalone (src/gb/core.c:655) but falls to gb_bios.bin in libretro."
source_ref: "src/platform/libretro/libretro.c:2100"
note: "SGB boot ROM. SGB2 maps here in standalone (src/gb/core.c:643) but falls to gb_bios.bin in libretro."
source_ref: "src/platform/libretro/libretro.c:2130"
platform_details:
gba:
bios_size: 16384 # 16 KB
hle_bios: true
source_ref: "src/gba/hle-bios.c, src/gba/gba.c:513-552"
source_ref: "src/gba/hle-bios.c, src/gba/gba.c:520-551"
note: |
GBALoadBIOS() validates size (must be exactly 0x4000) then computes
an internal checksum. Two official checksums are recognized:
@@ -125,7 +125,7 @@ platform_details:
sgb:
bios_size: 256 # 256 B
hle_bios: false
source_ref: "src/gb/gb.c:570-605, src/gb/core.c:654-656"
source_ref: "src/gb/gb.c:570-605, src/gb/core.c:642-644"
note: |
Accepted SGB CRC32 checksums:
SGB = 0xEC8A83B9, SGB2 = 0x53D0DD63
+3 -3
View File
@@ -98,7 +98,7 @@ notes: |
MSX.emu/src/blueMSX/Memory/romMapperDRAM.c:88-99), and the Philips FDC, Panasonic
FDC and MoonSound mappers test nothing at all
(MSX.emu/src/blueMSX/Memory/romMapperPhilipsFdc.c:139-146,
MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:228-255,
MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:236-262,
MSX.emu/src/blueMSX/Memory/romMapperMoonsound.c:129-158).
The ROM database is compiled in. mediaDbGuessRom matches a cartridge's SHA-1 against
@@ -396,7 +396,7 @@ files:
required: true
size: 16384
description: "Panasonic floppy disk ROM of the MSX2+ machine"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:228-255"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:236-262"
- name: "MSXTR.rom"
path: "Machines/Shared Roms/MSXTR.rom"
@@ -436,7 +436,7 @@ files:
required: true
size: 65536
description: "MSX-DOS 2.3 disk ROM of the turbo R machine"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:228-255"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:236-262"
- name: "coleco.rom"
path: "Machines/COL - ColecoVision/coleco.rom"
+4 -6
View File
@@ -151,18 +151,16 @@ files:
source_ref: "cbus/ideio.c:1928-1930, cbus/ideio.c:1944-1981"
# -- SCSI BIOS ROM --
# Read into a 0x4000 buffer; any non-empty read is kept. Falls back to the
# scsibios[] stub.
# Loaded by scsiio.c:219. 16 KB (0x4000). Falls back to scsibios[] stub.
- name: "scsi.rom"
path: "np2kai/scsi.rom"
max_size: 16384
size: 16384
required: false
hle_fallback: true
note: >
SCSI controller BIOS ROM, up to 16 KB: the full-size test is commented
out and a shorter file is used as read. The core includes a built-in
SCSI controller BIOS ROM (16 KB). The core includes a built-in
SCSI BIOS stub as fallback.
source_ref: "cbus/scsiio.c:720-732"
source_ref: "cbus/scsiio.c:214-231"
# -- SASI BIOS ROM --
# Loaded by sasiio.c:455. 4 KB (0x1000). Falls back to sasibios[] stub.
+16 -26
View File
@@ -25,7 +25,7 @@ files:
min_size: 4194304
max_size: 8388608
validation: [size]
source_ref: "pcsx2/ps2/BiosTools.cpp:16-17,80-197,258-283,317-370"
source_ref: "pcsx2/ps2/BiosTools.cpp:258-362"
note: "Accepts any file 4-8 MB with valid romdir (RESET + ROMVER). Naming convention ps2-VVVVr-YYYYMMDD.bin (version, region, date)."
- name: ps2-0230a-20080220.rom1
@@ -48,7 +48,7 @@ files:
size: 1024
description: "Console NVRAM"
note: "Read at the BIOS path with the extension replaced by nvm, opened case-insensitively. Built in memory from region defaults when absent or blank."
source_ref: "pcsx2/CDVD/CDVD.cpp:165-189"
source_ref: "pcsx2/CDVD/CDVD.cpp:160-188"
- name: ps2-0230a-20080220.mec
required: false
@@ -66,32 +66,22 @@ files:
note: "DEV9 network adapter EEPROM. Falls back to built-in defaults if missing."
- name: GameIndex.yaml
path: resources/GameIndex.yaml
path: pcsx2/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"
note: "Read from the resources directory shipped with the application. Console error and no per-title fixes if missing."
mode: libretro
source_ref: "pcsx2/GameDatabase.cpp:46, pcsx2/GameDatabase.cpp:896"
note: "Game compatibility database. OSD warning if missing."
- name: RedumpDatabase.yaml
path: resources/RedumpDatabase.yaml
- name: cheats_ws.zip
path: pcsx2/resources/cheats_ws.zip
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"
note: "Read from the resources directory shipped with the application, used to name a disc from its dump."
mode: libretro
source_ref: "pcsx2/VMManager.cpp:348-361"
note: "Widescreen patches archive."
- name: patches.zip
path: resources/patches.zip
- name: cheats_ni.zip
path: pcsx2/resources/cheats_ni.zip
required: false
bundled: true
category: game_data
description: "built-in game patch archive"
source_ref: "pcsx2/Patch.cpp:117, pcsx2/Patch.cpp:289-318"
note: "Opened read only from the resources directory shipped with the application and searched for the pnach matching the running serial and CRC. Widescreen and no-interlacing patches live inside it."
mode: libretro
source_ref: "pcsx2/VMManager.cpp:383-396"
note: "No-interlacing patches archive."
+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
@@ -84,7 +84,7 @@ platform_details:
size: 4096
load_priority:
source_ref: "source/PokeMini.c:500-509"
source_ref: "source/PokeMini.c:515-523"
note: |
During PokeMini_Reset(), the core first reloads FreeBIOS as a
safe baseline, then attempts to load bios.min from disk. If
+33
View File
@@ -37,6 +37,7 @@ files:
region: [north-america]
size: 2097152
required: false
mode: standalone
hle_fallback: true
note: "GameCube NTSC-U boot ROM. HLE available but real IPL needed for GC menu boot and accurate fonts"
source_ref: "Source/Core/Common/CommonPaths.h:138, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:109-110"
@@ -46,6 +47,7 @@ files:
region: [europe]
size: 2097152
required: false
mode: standalone
hle_fallback: true
note: "GameCube PAL boot ROM"
source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:184"
@@ -55,6 +57,7 @@ files:
region: [japan]
size: 2097152
required: false
mode: standalone
hle_fallback: true
note: "GameCube NTSC-J boot ROM. JAP is the legacy directory name"
source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:186"
@@ -67,6 +70,7 @@ files:
path: "GC/dsp_rom.bin"
size: 8192
required: false
mode: standalone
hle_fallback: true
validation: [size]
known_hash_adler32: "0x66f334fe"
@@ -78,6 +82,7 @@ files:
path: "GC/dsp_coef.bin"
size: 4096
required: false
mode: standalone
hle_fallback: true
validation: [size]
known_hash_adler32: "0xf3b93527"
@@ -93,6 +98,7 @@ files:
path: "GC/font_western.bin"
size: 9589
required: false
mode: standalone
hle_fallback: true
note: "Windows-1252 font for GC/Wii text rendering. Free alternative bundled, real one from IPL dump preferred"
source_ref: "Source/Core/Common/CommonPaths.h:132, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:131,203-218"
@@ -101,6 +107,7 @@ files:
path: "GC/font_japanese.bin"
size: 303693
required: false
mode: standalone
hle_fallback: true
note: "Shift-JIS font for Japanese text. Free alternative bundled, real one from IPL dump preferred"
source_ref: "Source/Core/Common/CommonPaths.h:135, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:130"
@@ -111,6 +118,7 @@ files:
- name: "gba_bios.bin"
path: "GBA/gba_bios.bin"
required: false
mode: standalone
hle_fallback: true
note: "GBA BIOS for GC-GBA link feature (uses integrated mGBA). Path configurable in settings"
source_ref: "Source/Core/Common/CommonPaths.h:144, Source/Core/Core/HW/GBACore.cpp:344-361"
@@ -119,6 +127,7 @@ files:
- name: "SYSCONF"
path: "Wii/shared2/sys/SYSCONF"
required: false
mode: standalone
hle_fallback: true
note: "Wii system configuration. Auto-generated by PrimeHack, can be imported from NAND backup"
source_ref: "Source/Core/Common/CommonPaths.h:118, Source/Core/Core/WiiRoot.cpp:266"
@@ -127,6 +136,7 @@ files:
path: "Wii/title/00000001/00000002/data/setting.txt"
size: 256
required: false
mode: standalone
hle_fallback: true
note: "Wii region/language settings. Auto-generated during Wii boot emulation"
source_ref: "Source/Core/Common/CommonPaths.h:152, Source/Core/Core/Boot/Boot_BS2Emu.cpp:377-454"
@@ -135,6 +145,7 @@ files:
- name: "nand.bin"
path: null
required: false
mode: standalone
hle_fallback: true
note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:26-89"
@@ -143,6 +154,7 @@ files:
path: null
size: 1024
required: false
mode: standalone
hle_fallback: true
note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:19,76-88"
@@ -151,6 +163,7 @@ files:
- name: "WiiSD.raw"
path: "Load/WiiSD.raw"
required: false
mode: standalone
hle_fallback: true
note: "Virtual SD card image for Wii homebrew. Auto-created, supports SD/SDHC up to 4GB"
source_ref: "Source/Core/Common/CommonPaths.h:149"
@@ -159,6 +172,7 @@ files:
- name: "codehandler.bin"
path: null
required: false
mode: standalone
hle_fallback: true
note: "Gecko/Ocarina cheat code handler. Shipped with PrimeHack in Sys/"
source_ref: "Source/Core/Common/CommonPaths.h:154, Source/Core/Core/GeckoCode.cpp:121"
@@ -167,6 +181,7 @@ files:
- name: "Wii System Menu"
path: null
required: false
mode: standalone
hle_fallback: true
note: "Wii System Menu WAD. Installed to NAND via Tools > Install WAD, needed for Wii Menu boot"
source_ref: "Source/Core/DolphinQt/MenuBar.cpp:335, Source/Core/DolphinQt/MenuBar.cpp:1582-1590"
@@ -175,6 +190,7 @@ files:
- name: "clientca.pem"
path: "Wii/clientca.pem"
required: false
mode: standalone
hle_fallback: true
note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:201-285"
@@ -182,6 +198,7 @@ files:
- name: "clientcakey.pem"
path: "Wii/clientcakey.pem"
required: false
mode: standalone
hle_fallback: true
note: "SSL client private key. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:237"
@@ -189,6 +206,7 @@ files:
- name: "rootca.pem"
path: "Wii/rootca.pem"
required: false
mode: standalone
hle_fallback: true
note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:238"
@@ -202,90 +220,105 @@ files:
- name: "rtl8723a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723a_fw.bin"
required: false
mode: standalone
note: "Realtek 8723A BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:379"
- name: "rtl8723b_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723b_fw.bin"
required: false
mode: standalone
note: "Realtek 8723B BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:516-519"
- name: "rtl8723d_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723d_fw.bin"
required: false
mode: standalone
note: "Realtek 8723D BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:383"
- name: "rtl8761a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8761a_fw.bin"
required: false
mode: standalone
note: "Realtek 8761A BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:385"
- name: "rtl8761bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8761bu_fw.bin"
required: false
mode: standalone
note: "Realtek 8761BU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:387"
- name: "rtl8821a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8821a_fw.bin"
required: false
mode: standalone
note: "Realtek 8821A BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:389"
- name: "rtl8821c_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8821c_fw.bin"
required: false
mode: standalone
note: "Realtek 8821C BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:391"
- name: "rtl8822b_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8822b_fw.bin"
required: false
mode: standalone
note: "Realtek 8822B BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:395"
- name: "rtl8822cu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8822cu_fw.bin"
required: false
mode: standalone
note: "Realtek 8822CU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:393"
- name: "rtl8851bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8851bu_fw.bin"
required: false
mode: standalone
note: "Realtek 8851BU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:397"
- name: "rtl8852au_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852au_fw.bin"
required: false
mode: standalone
note: "Realtek 8852AU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:399"
- name: "rtl8852bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852bu_fw.bin"
required: false
mode: standalone
note: "Realtek 8852BU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:401"
- name: "rtl8852cu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852cu_fw.bin"
required: false
mode: standalone
note: "Realtek 8852CU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:403"
- name: "rtl8852btu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852btu_fw.bin"
required: false
mode: standalone
note: "Realtek 8852BT/8852BE-VT BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:405"
- name: "rtl8922au_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8922au_fw.bin"
required: false
mode: standalone
note: "Realtek 8922AU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:407"
+3
View File
@@ -21,18 +21,21 @@ files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Switch production keys for NCA decryption"
source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:239"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title decryption keys, needed for specific game content"
source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:240"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys for encrypted content"
source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:241"
+4
View File
@@ -26,18 +26,21 @@ files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:654"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:657"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:658"
@@ -45,6 +48,7 @@ files:
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294"
+4
View File
@@ -27,18 +27,21 @@ files:
- name: "prod.keys"
required: true
path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:655-656"
- name: "title.keys"
required: false
path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:659-660"
- name: "console.keys"
required: false
path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:661-662"
@@ -46,6 +49,7 @@ files:
required: false
path: "switch/"
size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294"
+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "df0adf1fd5088ef9509429fb1d5720e5ca5e7bf46aff9ad0021851dce7349107"
$defaultInstallSha256 = "42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60"
$maximumInstallerBytes = 2MB
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
$temporary = $null
+11 -49
View File
@@ -103,36 +103,8 @@ DEFAULT_DESTS = {
"rocknix": Path("/storage/roms/bios"),
"misterfpga": Path("/media/fat/games"),
"retropie": Path.home() / "RetroPie" / "BIOS",
"romm": Path("/romm/library/bios"),
}
def _os_default_dests(os_type: str) -> dict[str, Path]:
"""Where a platform keeps its BIOS on this OS, when it is not detected.
The same folders the platform's detection looks for: a platform named on
the command line but not found must not land in ~/bios, which none of
them reads.
"""
home = Path.home()
if os_type == "windows":
profile = Path(os.environ.get("USERPROFILE", str(home)))
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"),
"bizhawk": profile / "BizHawk" / "Firmware",
}
if os_type == "darwin":
return {
"retroarch": home / "Library" / "Application Support" / "RetroArch" / "system",
}
return {
"retroarch": home / ".config" / "retroarch" / "system",
"bizhawk": home / ".config" / "BizHawk" / "Firmware",
}
# Set when the run is interrupted, so queued downloads end instead of draining
# the pool: every future is submitted before the first one completes.
_stop = threading.Event()
@@ -1295,7 +1267,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 +1280,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 +1348,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:
@@ -1417,7 +1385,7 @@ def _prompt_manual_platform(os_type: str) -> list[tuple[str, Path]]:
plat = AVAILABLE_PLATFORMS[int(choice) - 1]
elif choice in AVAILABLE_PLATFORMS:
plat = choice
default_dest = _default_dest(os_type, plat)
default_dest = DEFAULT_DESTS.get(plat, Path.home() / "bios")
try:
raw = input(f"BIOS directory [{default_dest}]: ").strip()
except (EOFError, KeyboardInterrupt):
@@ -1437,9 +1405,7 @@ def _default_dest(os_type: str, platform_name: str) -> Path:
"""
if os_type == "android" and platform_name == "retroarch":
return _android_storage_root() / "RetroArch" / "system"
return _os_default_dests(os_type).get(
platform_name, DEFAULT_DESTS.get(platform_name, Path.home() / "bios")
)
return DEFAULT_DESTS.get(platform_name, Path.home() / "bios")
def _manual_usage_hint(os_type: str) -> list[str]:
@@ -1743,15 +1709,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="42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+207 -27
View File
@@ -5,7 +5,7 @@
"platform": "batocera",
"display_name": "Batocera",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"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": 8065364335,
"pack_files": 12828,
"pack_size": 8065364335,
"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",
@@ -40939,20 +40969,20 @@
},
{
"dest": "switch/prod.keys",
"sha1": "c146b651696d25287241f4bab18226e488bc45b3",
"sha256": "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112",
"size": 16025,
"repo_path": "bios/Nintendo/Switch/prod.keys",
"sha1": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"sha256": "76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990",
"size": 15039,
"repo_path": "bios/Arcade/Arcade/switch/prod.keys",
"cores": [
"citron"
]
},
{
"dest": "switch/title.keys",
"sha1": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
"sha256": "3ed66f7e82df79922739c6c2d34de90bc36a48d13acdff24f9d51013bd8c0677",
"size": 1224,
"repo_path": "bios/Nintendo/Switch/title.keys",
"sha1": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"sha256": "dc6e31aae8daa9067dafcf2bca6d9d3aa691a63474eb698ad8c54be3e360753b",
"size": 19584,
"repo_path": "bios/Arcade/Arcade/switch/title.keys",
"cores": [
"citron"
]
@@ -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",
@@ -85525,10 +85625,10 @@
},
{
"dest": "np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 8192,
"repo_path": "bios/NEC/PC-98/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5",
"size": 16384,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom",
"cores": [
"np2kai"
]
@@ -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",
+245 -1555
View File
File diff suppressed because it is too large. Load diff
+37 -527
View File
@@ -5,7 +5,7 @@
"platform": "emudeck",
"display_name": "EmuDeck",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "bios",
"detect": [
{
@@ -250,10 +250,10 @@
}
}
],
"total_files": 618,
"total_size": 3472900342,
"pack_files": 620,
"pack_size": 3472900342,
"total_files": 569,
"total_size": 3452229095,
"pack_files": 571,
"pack_size": 3452229095,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -331,18 +331,18 @@
},
{
"dest": "disksys.rom",
"sha1": "af5af53f66982e749643fdf8b2acbb7d4d3ed229",
"sha256": "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72",
"sha1": "57fe1bdee955bb48d357e463ccbf129496930b62",
"sha256": "99c18490ed9002d9c6d999b9d8d15be5c051bdfa7cc7e73318053c9a994b0178",
"size": 8192,
"repo_path": "bios/Nintendo/Famicom Disk System/.variants/disksys.rom.af5af53f",
"repo_path": "bios/Arcade/FBNeo/fdsbios.nes",
"cores": null
},
{
"dest": "prod.keys",
"sha1": "c146b651696d25287241f4bab18226e488bc45b3",
"sha256": "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112",
"size": 16025,
"repo_path": "bios/Nintendo/Switch/prod.keys",
"sha1": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"sha256": "76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990",
"size": 15039,
"repo_path": "bios/Arcade/Arcade/switch/prod.keys",
"cores": null
},
{
@@ -387,26 +387,26 @@
},
{
"dest": "bios_CD_E.bin",
"sha1": "7063192ae9f6b696c5b81bc8f0a9fe6f0c400e58",
"sha256": "fe608a2a07676a23ab5fd5eee2f53c9e2526d69a28aa16ccd85c0ec42e6933cb",
"sha1": "f891e0ea651e2232af0c5c4cb46a0cae2ee8f356",
"sha256": "16735df92fcc1d0fc6a1eced244867e95281c5006340926082b74e363e073dc4",
"size": 131072,
"repo_path": "bios/Sega/Mega CD/eu_mcd2_9303.bin",
"repo_path": "bios/Sega/Mega CD/MCD_eu_100.bin",
"cores": null
},
{
"dest": "bios_CD_U.bin",
"sha1": "5a8c4b91d3034c1448aac4b5dc9a6484fce51636",
"sha256": "fb477cdbf94c84424c2feca4fe40656d85393fe7b7b401911b45ad2eb991258c",
"sha1": "f4f315adcef9b8feb0364c21ab7f0eaf5457f3ed",
"sha256": "807384678ef259494d943eddb6954dfc7069693d0dde0c424e2028488d88e81a",
"size": 131072,
"repo_path": "bios/Sega/Mega CD/SegaCDBIOS9303.bin",
"repo_path": "bios/Sega/Mega CD/bios_CD_U.bin",
"cores": null
},
{
"dest": "bios_CD_J.bin",
"sha1": "d203cfe22c03ae479dd8ca33840cf8d9776eb3ff",
"sha256": "7133fc2dd2fe5b7d0acd53a5f10f3d00b5d31270239ad20d74ef32393e24af88",
"sha1": "4846f448160059a7da0215a5df12ca160f26dd69",
"sha256": "923eb9da740f98ab1572085e3d351e2c0982df0906b0b99efb68a9997c94cf8f",
"size": 131072,
"repo_path": "bios/Sega/Mega CD/jp_mcd2_921222.bin",
"repo_path": "bios/Sega/Mega CD/MCD_jp_100p.bin",
"cores": null
},
{
@@ -529,316 +529,6 @@
"repo_path": "bios/Microsoft/Xbox/Complex_4627.bin",
"cores": null
},
{
"dest": "gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
"sha256": "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570",
"size": 16384,
"repo_path": "bios/Nintendo/Game Boy Advance/GBA_bios.rom",
"cores": [
"ares"
]
},
{
"dest": "bios.gg",
"sha1": "914aa165e3d879f060be77870d345b60cfeb4ede",
"sha256": "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2",
"size": 1024,
"repo_path": "bios/Sega/Game Gear/GG_majesco.gg",
"cores": [
"ares"
]
},
{
"dest": "bios_U.sms",
"sha1": "c315672807d8ddb8d91443729405c766dd95cae7",
"sha256": "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50",
"size": 8192,
"repo_path": "bios/Other/zesarux/sms.rom",
"cores": [
"ares"
]
},
{
"dest": "bios_J.sms",
"sha1": "a8c1b39a2e41137835eda6a5de6d46dd9fadbaf2",
"sha256": "67846e26764bd862f19179294347f7353a4166b62ac4198a5ec32933b7da486e",
"size": 8192,
"repo_path": "bios/Sega/Master System/MasterSystem/japanese-bios.sms",
"cores": [
"ares"
]
},
{
"dest": "bios_E.sms",
"sha1": "c315672807d8ddb8d91443729405c766dd95cae7",
"sha256": "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50",
"size": 8192,
"repo_path": "bios/Other/zesarux/sms.rom",
"cores": [
"ares"
]
},
{
"dest": "mega_ld_bios_U.bin",
"sha1": "aa811861f8874775075bd3f53008c8aaf59b07db",
"sha256": "e89b5a319f66406611ec82fe5c4aa6827c175a05135bd7bd177366cba0465021",
"size": 131072,
"repo_path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.04 (1993)(Pioneer - Sega)(US).bin",
"cores": [
"ares"
]
},
{
"dest": "mega_ld_bios_J.bin",
"sha1": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"sha256": "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94",
"size": 131072,
"repo_path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin",
"cores": [
"ares"
]
},
{
"dest": "MSX.ROM",
"sha1": "302afb5d8be26c758309ca3df611ae69cced2821",
"sha256": "413a2b601a94b3792e054be2439cc77a1819cceadbfa9542f88d51c7480f2ef0",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/CLK/msx-japanese.rom",
"cores": [
"ares"
]
},
{
"dest": "MSX2.ROM",
"sha1": "0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd",
"sha256": "0c672d86ead61a97f49a583b88b7c1905da120645cd44f0c9f2baf4f4631e0b1",
"size": 32768,
"repo_path": "bios/Other/msx-emu/Machines/Shared Roms/MSX2J.rom",
"cores": [
"ares"
]
},
{
"dest": "MSX2EXT.ROM",
"sha1": "5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02",
"sha256": "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300",
"size": 16384,
"repo_path": "bios/Microsoft/MSX/MSX2EXT.ROM",
"cores": [
"ares"
]
},
{
"dest": "neo-epo.bin",
"sha1": "1b3b22092f30c4d1b2c15f04d1670eb1e9fbea07",
"sha256": "70f7906d68acbc3630b6e7792ebb485288b05ca72c6d4c243e07e230aae8d1e4",
"size": 131072,
"repo_path": "bios/SNK/Neo Geo/neo-epo.bin",
"cores": [
"ares"
]
},
{
"dest": "sp-45.sp1",
"sha1": "cdf1f49e3ff2bac528c21ed28449cf35b7957dc1",
"sha256": "0fbeee82b463187d6360e92811f1fad58649feb8ab2584c524222d0b5bf53a17",
"size": 524288,
"repo_path": "bios/SNK/Neo Geo/sp-45.sp1",
"cores": [
"ares"
]
},
{
"dest": "ngp_bios.rom",
"sha1": "2f6429b68446536d8b03f35d02f1e98beb6460a0",
"sha256": "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7",
"size": 65536,
"repo_path": "bios/SNK/Neo Geo Pocket/SNK Neo-Geo Pocket BIOS (1998)(SNK)(en-ja).bin",
"cores": [
"ares"
]
},
{
"dest": "ngpc_bios.rom",
"sha1": "edc13192054a59be49c6d55f83b70e2510968e86",
"sha256": "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d",
"size": 65536,
"repo_path": "bios/SNK/Neo Geo Pocket/SNK Neo-Geo Pocket Color BIOS (1999)(SNK)(en-ja).bin",
"cores": [
"ares"
]
},
{
"dest": "64dd_ipl_J.bin",
"sha1": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
"sha256": "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_v12_JPN.bin",
"cores": [
"ares"
]
},
{
"dest": "64dd_ipl_U.bin",
"sha1": "3c5b93ca231550c68693a14f03cea8d5dbd1be9e",
"sha256": "e9fec87a45fba02399e88064b9e2f8cf0f2106e351c58279a87f05da5bc984ad",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_USA.bin",
"cores": [
"ares"
]
},
{
"dest": "64dd_ipl_DEV.bin",
"sha1": "10c4173c2a7eb09c6579818f72ef18fa0b6d32de",
"sha256": "9c2962a8b994a29e4cd04b3a6e4ed730a751414655ab6a9799ebf5fc08b79d44",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_DEV.bin",
"cores": [
"ares"
]
},
{
"dest": "syscard1.pce",
"sha1": "a39a66da7de6ba94ab84d04eef7afeec7d4ee66a",
"sha256": "afe9f27f91ac918348555b86298b4f984643eafa2773196f2c5441ea84f0c3bb",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard1.pce",
"cores": [
"ares"
]
},
{
"dest": "syscard3.pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": [
"ares"
]
},
{
"dest": "syscard3u.pce",
"sha1": "d02611d99921986147c753df14c7349b31d71950",
"sha256": "cadac2725711b3c442bcf237b02f5a5210c96f17625c35fa58f009e0ed39e4db",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3u.pce",
"cores": [
"ares"
]
},
{
"dest": "games_express.pce",
"sha1": "014881a959e045e00f4db8f52955200865d40280",
"sha256": "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d",
"size": 32768,
"repo_path": "bios/NEC/PC Engine/gecard.pce",
"cores": [
"ares"
]
},
{
"dest": "pac-n10.pce",
"sha1": "f7412aa822d70a55b2ff3d7095137263dc54f6b6",
"sha256": "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4",
"size": 524288,
"repo_path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-N10 (US) (v1.02).bin",
"cores": [
"ares"
]
},
{
"dest": "pac-n1.pce",
"sha1": "6973e2593e66fd21627fedccec98d4a364afaaff",
"sha256": "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f",
"size": 524288,
"repo_path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin",
"cores": [
"ares"
]
},
{
"dest": "pce-lp1.pce",
"sha1": "c1b9202cbe072db12114b223a9ba5374b30718fb",
"sha256": "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0",
"size": 524288,
"repo_path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PCE-LP1 (Japan) (v1.02).bin",
"cores": [
"ares"
]
},
{
"dest": "saturn_bios_U.bin",
"sha1": "faa8ea183a6d7bbe5d4e03bb1332519800d3fbc3",
"sha256": "96e106f740ab448cf89f0dd49dfbac7fe5391cb6bd6e14ad5e3061c13330266f",
"size": 524288,
"repo_path": "bios/Sega/Saturn/SAT_1.00-(U+E).bin",
"cores": [
"ares"
]
},
{
"dest": "saturn_bios_J.bin",
"sha1": "df94c5b4d47eb3cc404d88b33a8fda237eaf4720",
"sha256": "dcfef4b99605f872b6c3b6d05c045385cdea3d1b702906a0ed930df7bcb7deac",
"size": 524288,
"repo_path": "bios/Sega/Saturn/SAT_1.01-(J).bin",
"cores": [
"ares"
]
},
{
"dest": "saturn_bios_E.bin",
"sha1": "faa8ea183a6d7bbe5d4e03bb1332519800d3fbc3",
"sha256": "96e106f740ab448cf89f0dd49dfbac7fe5391cb6bd6e14ad5e3061c13330266f",
"size": 524288,
"repo_path": "bios/Sega/Saturn/SAT_1.00-(U+E).bin",
"cores": [
"ares"
]
},
{
"dest": "SGB1.sfc",
"sha1": "973e10840db683cf3faf61bd443090786b3a9f04",
"sha256": "a75160f7b89b1f0e20fd2f6441bb86285c7378db5035ef6885485eaff6059376",
"size": 262144,
"repo_path": "bios/Nintendo/Super Game Boy/SGB1.sfc/program.rom",
"cores": [
"ares"
]
},
{
"dest": "SGB2.sfc",
"sha1": "e5b2922ca137051059e4269b236d07a22c07bc84",
"sha256": "c172498a23d1176672931bab33b629c7d28f914a43dca9e540b8af1b37ccf2c6",
"size": 524288,
"repo_path": "bios/Nintendo/Super Game Boy/SGB2.sfc/program.rom",
"cores": [
"ares"
]
},
{
"dest": "STBIOS.bin",
"sha1": "ef86ea192eed03d5c413fdbbfd46043be1d7a127",
"sha256": "edacb453da14f825f05d1134d6035f4bf034e55f7cfb97c70c4ee107eabc7342",
"size": 262144,
"repo_path": "bios/Nintendo/SuFami Turbo/STBIOS.bin",
"cores": [
"ares"
]
},
{
"dest": "BS-X.bin",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"cores": [
"ares"
]
},
{
"dest": "sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067",
@@ -1009,36 +699,6 @@
"cemu"
]
},
{
"dest": "switch/title.keys",
"sha1": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
"sha256": "3ed66f7e82df79922739c6c2d34de90bc36a48d13acdff24f9d51013bd8c0677",
"size": 1224,
"repo_path": "bios/Nintendo/Switch/title.keys",
"cores": [
"citron"
]
},
{
"dest": "switch/console.keys",
"sha1": "721ca59a261d04af13fb84497ad79b6d8c6b7609",
"sha256": "b98c31cc764c49b8259142de3ada5bb7440ce0fd11315ae64294ddb0d56a523d",
"size": 451,
"repo_path": "bios/Other/citron/switch/console.keys",
"cores": [
"citron"
]
},
{
"dest": "switch/key_retail.bin",
"sha1": "bbdbb49a917d14f7a997d327ba40d40c39e606ce",
"sha256": "7f9283654ec1097fbdff31de946651ae60c2854afb544abe8963d389639c710e",
"size": 160,
"repo_path": "bios/Other/citron/switch/key_retail.bin",
"cores": [
"citron"
]
},
{
"dest": "GC/dsp_rom.bin",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
@@ -2182,13 +1842,13 @@
]
},
{
"dest": "switch/dev.keys",
"sha1": "fc916ff462b842983d4061d09230163ac4b38f16",
"sha256": "72685b5ce6c7ea3e18e3e0d98be1a99de8197299ee15429687b27df614ec92b9",
"size": 781,
"repo_path": "bios/Other/eden/switch/dev.keys",
"dest": "dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
"sha256": "15a9bc3aa59fa998c03bfe0b1cf6cb1958c1660e8e72b3d7195b653f44038407",
"size": 131072,
"repo_path": "bios/Sega/Dreamcast/dc_nvmem.bin",
"cores": [
"eden"
"flycast"
]
},
{
@@ -3891,6 +3551,16 @@
"melonds"
]
},
{
"dest": "gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
"sha256": "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570",
"size": 16384,
"repo_path": "bios/Nintendo/Game Boy Advance/GBA_bios.rom",
"cores": [
"mgba"
]
},
{
"dest": "gb_bios.bin",
"sha1": "4ed31ec6b0b175bb109c0eb5fd3d193da823339f",
@@ -3961,156 +3631,6 @@
"ppsspp"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8723a_fw.bin",
"sha1": "2937529b25c46e29429811829988f56f28142a41",
"sha256": "756951ee36e417c0714b00547a6175d5d5decbe1e4cf7585f211019150c7cd10",
"size": 24548,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8723a_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8723b_fw.bin",
"sha1": "5c43fb17ef04a2ee13f9e697f685f68f0d0d1096",
"sha256": "c9c8a9f6ebd90b7449a7e4d38ee39952dc7cc47d6c1d054e42cfbb5e4d818853",
"size": 45048,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8723b_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8723d_fw.bin",
"sha1": "a9182178ae400dd68b3e82f5f4c650001e9d1b85",
"sha256": "6b70723b2db9b658cd9535fe36f1494b4494699e1ad07920c9356f8bd90d9263",
"size": 52080,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8723d_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8761a_fw.bin",
"sha1": "7eca505aad586772d66c3d6336d450c591e10670",
"sha256": "581e5e81f5b51a29f475f8a276be95ad3210b3e3065987541c508e7f7a83a462",
"size": 74488,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8761a_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8761bu_fw.bin",
"sha1": "f5bf3a7fc9605ee31b3904d64b89ce0fa3d9eaf5",
"sha256": "1d7a9597349ad89344fa16c1913d3e39e9a12e966e417ca16871bc79bbe59edb",
"size": 44484,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8761bu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8821a_fw.bin",
"sha1": "0183b48cda05bd4aed70b9f0b762eea5d2fc3b1d",
"sha256": "3ed5077f8f02ff2b06be257691dc72061efac976cd2dc174057833624c820b9d",
"size": 37420,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8821a_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8821c_fw.bin",
"sha1": "9a192c6a003390a5eab017fe79d42514ec6712e7",
"sha256": "dfe7ef772d078ec09490c02190a32fcd40b3eaf3b040049d8930eb80e6d5743c",
"size": 54636,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8821c_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8822b_fw.bin",
"sha1": "d5ec968f1609b1c436ad25db7d34f5e393373396",
"sha256": "eecef2c3b2ff03c3a41292a93a25eca6f4ec38eb4a7491a3651b530be89a04fc",
"size": 51176,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8822b_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8822cu_fw.bin",
"sha1": "08751f476d0d0aa165af020ed19d2ed1fbaeeef6",
"sha256": "09d81457457a7760b867c715517d12dd3919f9ecec7b080f2985e75f1563209f",
"size": 38207,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8822cu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8851bu_fw.bin",
"sha1": "0e877d65077a42db4971c7f930dddc7b63047617",
"sha256": "7fcf88c00b2548a5ff157102a35722db5e4be655dab1ce8d9bddb6df85f88f24",
"size": 49760,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8851bu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852au_fw.bin",
"sha1": "24118f0e3b1ac5be44abacd4fa0bc07e2364e223",
"sha256": "56be2eb99d36109bb5e01eee171573b6f3dd041238c01cd97ce4f01df8dbf09e",
"size": 67188,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852au_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852bu_fw.bin",
"sha1": "22cacf5d1b9da25801cd5d6e8b7f06dff0965439",
"sha256": "c2b0d34a4ff8a8ea136993f0dc4ba657522d84231b4094c2a53cda39afc91c57",
"size": 129733,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852bu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852cu_fw.bin",
"sha1": "acd067bdc7933d1d9b8f2d72a750de5d125a5529",
"sha256": "9b8b27741a914c1cecc1ab9837194236890522101a57ba1898eb55f8d538dae0",
"size": 113336,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852cu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852btu_fw.bin",
"sha1": "eb1139c1b27429eef639b6002b16212d702ad378",
"sha256": "bd162fd3b70e805cf7a8098f22f4ddf5d89da58fafff97d87da1a1e2e0cbeb80",
"size": 128140,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852btu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8922au_fw.bin",
"sha1": "088a3cf44d2372cdb557241bbbf519bcbbac156f",
"sha256": "112290ca9dc3985034f572594812f0deb8530dd031686461b55cac43a1c35a79",
"size": 71360,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8922au_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "PS3UPDAT.PUP",
"sha1": "093f8698b54b78dcb701de2043f82639de51d63b",
@@ -5047,16 +4567,6 @@
"cemu"
]
},
{
"dest": "switch/prod.keys",
"sha1": "c146b651696d25287241f4bab18226e488bc45b3",
"sha256": "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112",
"size": 16025,
"repo_path": "bios/Nintendo/Switch/prod.keys",
"cores": [
"citron"
]
},
{
"dest": "GC/USA/IPL.bin",
"sha1": "ef9194ab4804aa0aa8540d846caf291b28331165",
+91 -33
View File
@@ -5,7 +5,7 @@
"platform": "lakka",
"display_name": "Lakka",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"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": 6399851574,
"pack_files": 8510,
"pack_size": 6440384738,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -961,10 +961,10 @@
},
{
"dest": "galaksija/ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"cores": null
},
{
@@ -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",
@@ -1763,10 +1771,10 @@
},
{
"dest": "dolphin-emu/Sys/GC/dsp_rom.bin",
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
"size": 8192,
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
"cores": null
},
{
@@ -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",
@@ -47433,10 +47491,10 @@
},
{
"dest": "np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 8192,
"repo_path": "bios/NEC/PC-98/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5",
"size": 16384,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom",
"cores": [
"np2kai"
]
@@ -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",
+103 -13
View File
@@ -5,7 +5,7 @@
"platform": "recalbox",
"display_name": "Recalbox",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"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": 4193670403,
"pack_files": 3231,
"pack_size": 4193670403,
"total_omitted": 1,
"omitted_files": [
{
@@ -1815,10 +1815,10 @@
},
{
"dest": "pcenginecd/syscard3.pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"sha1": "1b4c260326d905bc718812dad0f68089977f427b",
"sha256": "df4f75feebb95e53dfef72dea0787df743e3474ba046ff5a4cb88e34dda93ff1",
"size": 262656,
"repo_path": "bios/NEC/PC Engine/.variants/syscard3.pce.1b4c2603",
"cores": null
},
{
@@ -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",
@@ -24965,10 +25025,10 @@
},
{
"dest": "np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 8192,
"repo_path": "bios/NEC/PC-98/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5",
"size": 16384,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom",
"cores": [
"np2kai"
]
@@ -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",
+91 -33
View File
@@ -5,7 +5,7 @@
"platform": "retroarch",
"display_name": "RetroArch",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"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": 6399851574,
"pack_files": 8510,
"pack_size": 6440384738,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -1200,10 +1200,10 @@
},
{
"dest": "galaksija/ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"cores": null
},
{
@@ -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",
@@ -2002,10 +2010,10 @@
},
{
"dest": "dolphin-emu/Sys/GC/dsp_rom.bin",
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
"size": 8192,
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
"cores": null
},
{
@@ -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",
@@ -47672,10 +47730,10 @@
},
{
"dest": "np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 8192,
"repo_path": "bios/NEC/PC-98/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5",
"size": 16384,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom",
"cores": [
"np2kai"
]
@@ -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",
+189 -1454
View File
File diff suppressed because it is too large. Load diff
+232 -1252
View File
File diff suppressed because it is too large. Load diff
+131 -43
View File
@@ -5,7 +5,7 @@
"platform": "retropie",
"display_name": "RetroPie",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"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": 8731282254,
"pack_files": 12542,
"pack_size": 8771815418,
"total_omitted": 8,
"omitted_files": [
{
@@ -1041,10 +1041,10 @@
},
{
"dest": "galaksija/ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"cores": null
},
{
@@ -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",
@@ -1843,10 +1851,10 @@
},
{
"dest": "dolphin-emu/Sys/GC/dsp_rom.bin",
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
"size": 8192,
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
"cores": null
},
{
@@ -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",
@@ -84705,10 +84803,10 @@
},
{
"dest": "np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 8192,
"repo_path": "bios/NEC/PC-98/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5",
"size": 16384,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom",
"cores": [
"np2kai"
]
@@ -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",
+39 -19
View File
@@ -5,7 +5,7 @@
"platform": "rocknix",
"display_name": "ROCKNIX",
"version": "1.0",
"generated": "2026-10-09T20:41:14Z",
"generated": "2026-10-06T03:26:07Z",
"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": 6570732492,
"pack_files": 5806,
"pack_size": 6570732492,
"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",
@@ -45694,10 +45704,10 @@
},
{
"dest": "np2kai/scsi.rom",
"sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 8192,
"repo_path": "bios/NEC/PC-98/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5",
"size": 16384,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom",
"cores": [
"np2kai"
]
@@ -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",
+21 -1822
View File
File diff suppressed because it is too large. Load diff
-16
View File
@@ -148,7 +148,6 @@ platforms:
retroarch:
config: retroarch.yml
status: active
extract_to: system/
logo: https://raw.githubusercontent.com/libretro/RetroArch/master/media/retroarch-vector_invader-only.svg
scraper: libretro
source_url: https://raw.githubusercontent.com/libretro/libretro-database/master/dat/System.dat
@@ -183,7 +182,6 @@ platforms:
batocera:
config: batocera.yml
status: active
extract_to: /userdata/bios/
logo: https://raw.githubusercontent.com/batocera-linux/batocera-emulationstation/master/resources/splash_batocera.svg
scraper: batocera
source_url: https://raw.githubusercontent.com/batocera-linux/batocera.linux/master/package/batocera/core/batocera-scripts/scripts/batocera-systems
@@ -409,7 +407,6 @@ platforms:
recalbox:
config: recalbox.yml
status: active
extract_to: /recalbox/share/bios/
logo: https://raw.githubusercontent.com/homarr-labs/dashboard-icons/main/svg/recalbox.svg
scraper: recalbox
source_url: https://gitlab.com/recalbox/recalbox/-/raw/master/board/recalbox/fsoverlay/recalbox/share_init/system/.emulationstation/es_bios.xml
@@ -623,7 +620,6 @@ platforms:
retrobat:
config: retrobat.yml
status: active
extract_to: bios/
logo: https://raw.githubusercontent.com/RetroBat-Official/retrobat/main/system/resources/retrobat_logo_notext.png
scraper: retrobat
source_url: https://raw.githubusercontent.com/RetroBat-Official/emulatorlauncher/master/batocera-systems/Resources/batocera-systems.json
@@ -805,10 +801,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'
@@ -816,7 +808,6 @@ platforms:
emudeck:
config: emudeck.yml
status: active
extract_to: ~/Emulation/bios/
logo: https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/icons/EmuDeck.png
scraper: emudeck
source_url: https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/functions/checkBIOS.sh
@@ -847,7 +838,6 @@ platforms:
lakka:
config: lakka.yml
status: active
extract_to: /storage/system/
logo: https://raw.githubusercontent.com/libretro/retroarch-assets/master/src/xmb/flatui/lakka.svg
scraper: libretro
inherits_from: retroarch
@@ -864,7 +854,6 @@ platforms:
retrodeck:
config: retrodeck.yml
status: active
extract_to: ~/retrodeck/
logo: https://raw.githubusercontent.com/RetroDECK/RetroDECK/main/res/icon.svg
scraper: retrodeck
source_url: https://github.com/RetroDECK/components
@@ -909,7 +898,6 @@ platforms:
romm:
config: romm.yml
status: active
extract_to: library/bios/
logo: https://avatars.githubusercontent.com/u/168586850
scraper: romm
source_url: https://raw.githubusercontent.com/rommapp/romm/master/backend/models/fixtures/known_bios_files.json
@@ -957,7 +945,6 @@ platforms:
rocknix:
config: rocknix.yml
status: active
extract_to: /storage/roms/bios/
logo: https://avatars.githubusercontent.com/u/152618023
scraper: rocknix
cores: all_libretro
@@ -972,7 +959,6 @@ platforms:
retropie:
config: retropie.yml
status: archived
extract_to: ~/RetroPie/BIOS/
logo: https://avatars.githubusercontent.com/u/11378204
scraper: retropie
source: https://github.com/RetroPie/RetroPie-Setup
@@ -989,7 +975,6 @@ platforms:
bizhawk:
config: bizhawk.yml
status: active
extract_to: Firmware/
logo: https://avatars.githubusercontent.com/u/11743303
scraper: bizhawk
source_url: https://raw.githubusercontent.com/TASEmulators/BizHawk/master/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs
@@ -1054,7 +1039,6 @@ platforms:
misterfpga:
config: misterfpga.yml
status: active
extract_to: /media/fat/games/
logo: https://avatars.githubusercontent.com/u/47264183
scraper: misterfpga
source_url: https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip
+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()
+1 -1
View File
@@ -16,7 +16,7 @@ dependencies = [
# and the CI contract check are the only callers, and no user path imports it.
[project.optional-dependencies]
dev = [
"jsonschema[format-nongpl]>=4.23",
"jsonschema>=4.23",
]
# The site build the repo generates and documents. Both were hard
# requirements of deploy-site.yml and of wiki/release-process.md while being
+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
}
-31
View File
@@ -31,37 +31,6 @@
"files"
]
}
},
{
"comment": "Per-file mode separates the two builds of a dual profile. A profile with only a standalone build has no libretro side to separate it from, and a file marked standalone there drops out of the gap analysis and of the packs of every platform that lists the emulator without standalone_cores.",
"if": {
"properties": {
"type": {
"const": "standalone"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"files": {
"items": {
"not": {
"properties": {
"mode": {
"const": "standalone"
}
},
"required": [
"mode"
]
}
}
}
}
}
}
],
"properties": {
+1 -2
View File
@@ -129,8 +129,7 @@
"hash_mismatch",
"not_found",
"external",
"user_provided",
"no_platform_slug"
"user_provided"
]
},
"cores": {
+6 -58
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."""
@@ -96,11 +72,6 @@ def _strip_timestamps(text: str) -> str:
class ArtifactLockBusy(RuntimeError):
"""Raised when another process already holds the artifact directory."""
# A run that holds an artifact directory for its whole duration names it
# here for the steps it spawns: they would otherwise be refused by it.
HELD_LOCK_ENV = "RETROBIOS_HELD_ARTIFACT_DIR"
@contextlib.contextmanager
def artifact_lock(directory: str, exclusive: bool = True):
"""Serialize access to a shared artifact directory across processes.
@@ -117,9 +88,6 @@ def artifact_lock(directory: str, exclusive: bool = True):
return
os.makedirs(directory, exist_ok=True)
if os.environ.get(HELD_LOCK_ENV) == os.path.realpath(directory):
yield
return
lock_path = os.path.join(directory, ".lock")
mode = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH
with open(lock_path, "w") as handle:
@@ -135,26 +103,6 @@ def artifact_lock(directory: str, exclusive: bool = True):
finally:
fcntl.flock(handle, fcntl.LOCK_UN)
@contextlib.contextmanager
def hold_artifact_lock(directory: str):
"""Hold a directory exclusively across several child steps.
A lock taken step by step frees the directory between them: another
run can purge the packs one step built before the next step checks
them. The children this process spawns inherit the hold.
"""
with artifact_lock(directory):
previous = os.environ.get(HELD_LOCK_ENV)
os.environ[HELD_LOCK_ENV] = os.path.realpath(directory)
try:
yield
finally:
if previous is None:
os.environ.pop(HELD_LOCK_ENV, None)
else:
os.environ[HELD_LOCK_ENV] = previous
def _build_timestamp(db: dict | None = None) -> str:
"""Timestamp for generated artifacts.
+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".
+7 -21
View File
@@ -157,22 +157,6 @@ def _core_names(config: dict) -> set[str]:
return names
def _keyed_files(files: list[dict]) -> dict[str, dict]:
"""Each declaration under its own key.
A platform declares one destination several times (an archive once per
inner ROM, MD5 alternatives): keyed by destination alone, the dict kept
the last and a change to the others read as identical.
"""
keyed: dict[str, dict] = {}
seen: dict[str, int] = {}
for f in files:
key = _file_key(f)
seen[key] = seen.get(key, 0) + 1
keyed[key if seen[key] == 1 else f"{key}#{seen[key]}"] = f
return keyed
def diff_platform(old: dict, new: dict) -> PlatformDiff:
"""Compare two platform files the way a reviewer reads the diff.
@@ -188,8 +172,12 @@ def diff_platform(old: dict, new: dict) -> PlatformDiff:
diff.systems_added = sorted(set(new_systems) - set(old_systems))
diff.systems_removed = sorted(set(old_systems) - set(new_systems))
for system in sorted(set(old_systems) & set(new_systems)):
old_files = _keyed_files((old_systems[system] or {}).get("files") or [])
new_files = _keyed_files((new_systems[system] or {}).get("files") or [])
old_files = {
_file_key(f): f for f in (old_systems[system] or {}).get("files") or []
}
new_files = {
_file_key(f): f for f in (new_systems[system] or {}).get("files") or []
}
diff.files_added.extend(
f"{system}/{k}" for k in sorted(set(new_files) - set(old_files))
)
@@ -804,9 +792,7 @@ def _check_fbneo(cache_dir: str, offline: bool) -> Finding:
# --- CI toolchain -----------------------------------------------------------
_PIN_RE = re.compile(
r'"?([A-Za-z0-9_.\-]+)(?:\[[^\]]*\])?((?:[<>=!~]=?[^,"\s]+)(?:,[<>=!~]=?[^,"\s]+)*)?"?'
)
_PIN_RE = re.compile(r'"?([A-Za-z0-9_.\-]+)((?:[<>=!~]=?[^,"\s]+)(?:,[<>=!~]=?[^,"\s]+)*)?"?')
_USES_RE = re.compile(r"uses:\s*([\w.\-]+/[\w.\-]+)@([0-9a-f]{40})\s*(?:#\s*(\S+))?")
+38 -127
View File
@@ -480,60 +480,6 @@ def _affinity_tokens(text: str) -> set[str]:
}
def _owner_tokens(file_entry: dict) -> set[str]:
"""Tokens naming the profile that asks for the file."""
tokens: set[str] = set()
for owner in (file_entry.get("source_profile"), file_entry.get("source_emulator")):
if owner:
tokens |= _affinity_tokens(str(owner).replace(" ", ""))
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.
@@ -549,18 +495,16 @@ def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str
return paths
hint = dest_hint or file_entry.get("path") or file_entry.get("destination") or ""
wanted = _affinity_tokens(hint.rsplit("/", 1)[0] if "/" in hint else "")
owners = _owner_tokens(file_entry)
for owner in (file_entry.get("source_profile"), file_entry.get("source_emulator")):
if owner:
wanted |= _affinity_tokens(str(owner).replace(" ", ""))
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, 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, len(wanted & _affinity_tokens(path.rsplit("/", 1)[0]))
return sorted(paths, key=score, reverse=True)
@@ -762,14 +706,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 +719,15 @@ def resolve_local_file(
for h in candidates
):
own_file_absent = True
if len(candidates) > 1:
depth = len(tail.split("/"))
candidates = sorted(
candidates,
key=lambda h: len(
files_db.get(h, {}).get("path", "").split("/")
)
- depth,
)
for match_sha1 in candidates:
if match_sha1 not in files_db:
continue
@@ -912,7 +860,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 +908,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 +942,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 +1630,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,11 +1716,8 @@ from ziptools import ( # noqa: E402,F401
)
from artifacts import ( # noqa: E402,F401
write_if_changed,
write_text_atomic,
copy_file_atomic,
ArtifactLockBusy,
artifact_lock,
hold_artifact_lock,
_TIMESTAMP_PATTERNS,
_strip_timestamps,
)
+96 -181
View File
@@ -20,7 +20,6 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import (
_norm_system_id,
get_mame_clone_map,
list_registered_platforms,
load_database,
@@ -29,10 +28,7 @@ from common import (
name_match_size_ok,
parse_md5_list,
require_yaml,
resolve_local_file,
runs_standalone,
)
from validation import outside_gap_scope
yaml = require_yaml()
@@ -48,9 +44,6 @@ def load_platform_files(
"""Collect declared filenames + data_directories per system.
Restricted to *platforms* when given, otherwise every registered platform.
Keyed by the normalized system ID: a profile writes `sega-megacd` where
RetroArch writes `sega-mega-cd`, and an exact match called 924 declared
files undeclared.
"""
declared = {}
platform_data_dirs = {}
@@ -62,87 +55,58 @@ def load_platform_files(
for fe in system.get("files", []):
name = fe.get("name", "")
if name:
declared.setdefault(_norm_system_id(sys_id), set()).add(name)
declared.setdefault(sys_id, set()).add(name)
for dd in system.get("data_directories", []):
ref = dd.get("ref", "")
if ref:
platform_data_dirs.setdefault(_norm_system_id(sys_id), set()).add(ref)
platform_data_dirs.setdefault(sys_id, set()).add(ref)
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]:
"""Build a set of filenames and directory names in data/ and inside bios/ ZIPs.
A directory is indexed only with its trailing slash: a bare name matched a
file entry of the same name, and BasiliskII's required `ROM` read as held
because cpcemu keeps a directory called ROM.
"""
"""Build a set of filenames and directory names in data/ and inside bios/ ZIPs."""
names: set[str] = set()
root_path = Path(data_root)
if root_path.is_dir():
for fpath in root_path.rglob("*"):
if fpath.name.startswith("."):
continue
if fpath.is_file():
names.add(fpath.name)
names.add(fpath.name.lower())
else:
names.add(fpath.name + "/")
names.add(fpath.name.lower() + "/")
names.add(fpath.name)
names.add(fpath.name.lower())
if fpath.is_dir():
# Also index relative path from data/subdir/ for directory entries
parts = fpath.relative_to(root_path).parts
if len(parts) > 1:
rel = "/".join(parts[1:])
names.add(rel)
names.add(rel + "/")
names.add(rel.lower())
names.add(rel.lower() + "/")
bios_path = Path(bios_root)
if bios_path.is_dir():
# Index directory names for directory-type entries (e.g., "nestopia/samples/moepro/")
for dpath in bios_path.rglob("*"):
if dpath.is_dir() and not dpath.name.startswith("."):
names.add(dpath.name)
names.add(dpath.name.lower())
names.add(dpath.name + "/")
names.add(dpath.name.lower() + "/")
names |= _zip_member_names(bios_path)
return names
import zipfile
def _zip_member_names(bios_path: Path) -> set[str]:
"""Basenames of the files inside every ZIP under bios/, both cases."""
import zipfile
names: set[str] = set()
for zpath in bios_path.rglob("*.zip"):
try:
with zipfile.ZipFile(zpath) as zf:
members = [m for m in zf.namelist() if not m.endswith("/")]
except (zipfile.BadZipFile, OSError) as exc:
print(f" WARNING: unreadable archive {zpath}: {exc}", file=sys.stderr)
continue
for member in members:
basename = member.rsplit("/", 1)[-1]
names.update((basename, basename.lower()))
for zpath in bios_path.rglob("*.zip"):
try:
with zipfile.ZipFile(zpath) as zf:
for member in zf.namelist():
if not member.endswith("/"):
basename = (
member.rsplit("/", 1)[-1] if "/" in member else member
)
names.add(basename)
names.add(basename.lower())
except (zipfile.BadZipFile, OSError):
pass
return names
@@ -154,7 +118,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 +160,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"
@@ -205,109 +168,15 @@ def _resolve_source(
by_name_lower[canonical.lower()]
):
return "bios"
if data_names and _data_hit(fname, basename, file_entry, data_names):
return "data"
# data/ supplemental index
if data_names:
if fname in data_names or key in data_names:
return "data"
if basename and (basename in data_names or basename.lower() in data_names):
return "data"
return None
def _data_hit(
fname: str, basename: str | None, file_entry: dict | None, data_names: set[str]
) -> bool:
"""Whether data/ or a ZIP holds the name: a directory entry looks for a
directory, a file entry for a file."""
looked_up = [fname, fname.lower()] + ([basename, basename.lower()] if basename else [])
if fname.endswith("/") or (file_entry or {}).get("type") == "directory":
looked_up = [name.rstrip("/") + "/" for name in looked_up]
return any(name in data_names for name in looked_up)
class FileConsumers:
"""Who reads each collected file, joined through the resolver.
The site joined database files to emulators and platforms by bare name:
each of the four pak0.pk3 of four games was attributed to every engine
reading a pak0.pk3. A consumer here is an entry that resolves to the
file by the evidence the pack uses, so a homonym is nobody's file.
"""
def __init__(self, db: dict, zip_contents: dict | None = None) -> None:
self._db = db
self._zip_contents = zip_contents
self._sha1_by_path = {
path: sha1
for sha1, entry in db.get("files", {}).items()
for path in {entry.get("path", ""), *entry.get("paths", [])}
if path
}
self.platforms: dict[str, set[str]] = {}
self.emulators: dict[str, set[str]] = {}
def sha1_of(self, entry: dict, dest: str = "") -> str | None:
"""The collected file an entry resolves to, or None."""
if not entry.get("name"):
return None
path, status = resolve_local_file(
entry, self._db, self._zip_contents, dest_hint=dest
)
if not path or status in ("not_found", "hash_mismatch"):
return None
return self._sha1_by_path.get(path)
def add_platforms(self, configs: dict[str, dict]) -> None:
for name, config in configs.items():
for system in config.get("systems", {}).values():
for entry in system.get("files", []):
sha1 = self.sha1_of(entry, entry.get("destination", ""))
if sha1:
self.platforms.setdefault(sha1, set()).add(name)
def add_emulators(self, profiles: dict[str, dict]) -> None:
for name, profile in profiles.items():
if profile.get("type") == "alias":
continue
for entry in profile.get("files", []):
sha1 = self.sha1_of(entry, entry.get("path") or "")
if sha1:
self.emulators.setdefault(sha1, set()).add(name)
def entry_source(f: dict, index: dict) -> str | None:
"""Where the collection holds a profile entry, or None.
By name, path and alias, then by any hash the entry declares. The gap
report and the site pages read this one function, so a file is held or
missing in the same way on both.
"""
by_name, by_name_lower = index["by_name"], index["by_name_lower"]
data_names, by_path_suffix = index.get("data_names"), index["by_path_suffix"]
db_files = index["db_files"]
fname = f.get("name", "")
path_field = f.get("path") or ""
for candidate in [fname, *([path_field] if path_field != fname else []),
*(f.get("aliases") or [])]:
if not candidate:
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
def values(field: str) -> list[str]:
raw = f.get(field) or []
return [str(v).lower() for v in (raw if isinstance(raw, list) else [raw]) if v]
held = (
any(index["by_md5"].get(v) for v in parse_md5_list(f.get("md5")))
or any(v in db_files for v in values("sha1"))
# rust_dos names its SC-55 ROMs by sha256 alone
or any(v in index["by_sha256"] for v in values("sha256"))
or any(index["by_crc32"].get(v) for v in values("crc32"))
)
return "bios" if held else None
def _resolve_archive_source(
archive_name: str,
by_name: dict[str, list],
@@ -328,7 +197,6 @@ def _cross_reference_profile(
declared: dict[str, set[str]],
report: dict,
index: dict,
standalone_cores: set[str] | None = None,
) -> None:
"""Compare one emulator profile against what the platforms declare.
@@ -339,7 +207,10 @@ def _cross_reference_profile(
"""
by_name = index["by_name"]
by_name_lower = index["by_name_lower"]
by_md5 = index["by_md5"]
by_crc32 = index["by_crc32"]
by_path_suffix = index["by_path_suffix"]
db_files = index["db_files"]
data_names = index["data_names"]
all_declared = index["all_declared"]
emu_files = profile.get("files", [])
@@ -354,11 +225,8 @@ def _cross_reference_profile(
else:
platform_names = set()
for sys_id in systems:
platform_names.update(declared.get(_norm_system_id(sys_id), set()))
platform_names.update(declared.get(sys_id, set()))
# The build a platform runs decides which entries it reads, as in
# find_undeclared_files: without a platform, the libretro build.
is_standalone = runs_standalone(emu_name, profile, standalone_cores or set())
gaps = []
covered = []
unsourceable_list: list[dict] = []
@@ -390,7 +258,26 @@ def _cross_reference_profile(
})
continue
if outside_gap_scope(f, is_standalone):
# Skip pattern placeholders (e.g., <bios>.bin, <user-selected>.bin)
if "<" in fname or ">" in fname or "*" in fname:
continue
# Skip UI-imported files with explicit path: null (not resolvable by pack)
if "path" in f and f["path"] is None:
continue
# Skip standalone-only files
file_mode = f.get("mode", "both")
if file_mode == "standalone":
continue
# Skip files loaded from non-system directories (save_dir, content_dir)
load_from = f.get("load_from", "")
if load_from and load_from != "system_dir":
continue
# Skip filename-agnostic files (handled by agnostic scan)
if f.get("agnostic"):
continue
archive = f.get("archive")
@@ -452,7 +339,44 @@ def _cross_reference_profile(
if storage in ("release", "large_file"):
source = "large_file"
else:
source = entry_source(f, index)
source = _resolve_source(
fname, by_name, by_name_lower, data_names, by_path_suffix,
f, db_files,
)
if source is None:
path_field = f.get("path", "")
if path_field and path_field != fname:
source = _resolve_source(
path_field, by_name, by_name_lower,
data_names, by_path_suffix, f, db_files,
)
# Try the alternate names the emulator accepts, like
# resolve_local_file does
if source is None:
for alias in f.get("aliases") or []:
source = _resolve_source(
alias, by_name, by_name_lower,
data_names, by_path_suffix, f, db_files,
)
if source is not None:
break
# Try MD5 hash match
if source is None:
for md5_val in parse_md5_list(f.get("md5")):
if by_md5.get(md5_val):
source = "bios"
break
# Try SHA1 hash match
if source is None:
raw_sha1 = f.get("sha1", "")
sha1_values = raw_sha1 if isinstance(raw_sha1, list) else [raw_sha1]
if any(value and value in db_files for value in sha1_values):
source = "bios"
# Try CRC32 hash match
if source is None:
crc32 = str(f.get("crc32", "")).lower()
if crc32 and by_crc32.get(crc32):
source = "bios"
if source is None:
source = "missing"
@@ -497,7 +421,6 @@ def cross_reference(
platform_data_dirs: dict[str, set[str]] | None = None,
data_names: set[str] | None = None,
all_declared: set[str] | None = None,
standalone_cores: set[str] | None = None,
) -> dict:
"""Compare emulator profiles against platform declarations.
@@ -519,7 +442,6 @@ def cross_reference(
by_name_lower = {k.lower(): k for k in by_name}
by_md5 = db.get("indexes", {}).get("by_md5", {})
by_crc32 = db.get("indexes", {}).get("by_crc32", {})
by_sha256 = db.get("indexes", {}).get("by_sha256", {})
by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {})
db_files = db.get("files", {})
report = {}
@@ -529,16 +451,14 @@ def cross_reference(
"by_name_lower": by_name_lower,
"by_md5": by_md5,
"by_crc32": by_crc32,
"by_sha256": by_sha256,
"by_path_suffix": by_path_suffix,
"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(
emu_name, profile, declared, report, index, standalone_cores
emu_name, profile, declared, report, index
)
return report
@@ -615,7 +535,6 @@ def main():
if args.target and not args.platform:
parser.error("--target requires --platform")
standalone_cores: set[str] = set()
if args.platform:
from common import load_target_config, resolve_platform_cores
@@ -627,21 +546,17 @@ def main():
config = load_platform_config(args.platform, args.platforms_dir)
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
profiles = {k: v for k, v in profiles.items() if k in relevant}
standalone_cores = set(map(str, config.get("standalone_cores", [])))
if not profiles:
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,
)
report = cross_reference(profiles, declared, db, plat_data_dirs, data_names)
if args.json:
print(json.dumps(report, indent=2))
+13 -46
View File
@@ -38,19 +38,6 @@ def _is_placeholder(name: str) -> bool:
return "<" in name or ">" in name or "*" in name
def _field_sets(entry: dict | None) -> dict[str, set[str]]:
return {f: set(_hash_values(entry or {}, f)) for f in HASH_FIELDS}
def _single_size(value: object) -> int | None:
"""One size, or None: a list names several accepted revisions, which a
single size field cannot state (int() on it stopped the whole export)."""
if isinstance(value, list):
distinct = {int(v) for v in value}
return distinct.pop() if len(distinct) == 1 else None
return int(value)
@dataclass
class NativeFile:
"""One file as the platform will read it, after correction."""
@@ -74,26 +61,19 @@ class NativeFile:
"""Which side's content fields describe the file: one side, or both.
Mixing them field by field wrote the truth's size and crc beside the
platform's sha1, a record no file satisfies. The truth decides alone
only on a contradiction: a hash both sides declare without a value in
common, or two declared sizes. Different fields prove nothing: the
truth's crc32 beside Batocera's md5 for bios7.bin is one dump, and
reading it as a contradiction emptied the md5 the frontend checks.
platform's sha1, a record no file satisfies. Both sides are merged
only where they agree on some hash; where they contradict, the truth
decides alone; where the truth declares none, the platform does.
"""
truth, plat = _field_sets(self.truth), _field_sets(self.platform)
truth = {f: set(_hash_values(self.truth or {}, f)) for f in HASH_FIELDS}
plat = {f: set(_hash_values(self.platform or {}, f)) for f in HASH_FIELDS}
if not any(truth.values()):
return "platform"
if not any(plat.values()):
return "truth"
shared = [f for f in HASH_FIELDS if truth[f] and plat[f]]
if any(truth[f] & plat[f] for f in shared):
if any(truth[f] & plat[f] for f in HASH_FIELDS):
return "both"
return "truth" if shared or self._sizes_differ() else "both"
def _sizes_differ(self) -> bool:
t_size = (self.truth or {}).get("size")
p_size = (self.platform or {}).get("size")
return isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size
return "truth"
def hashes(self, field_name: str) -> list[str]:
"""Accepted values for a hash, truth first when it has an opinion.
@@ -156,7 +136,7 @@ class NativeFile:
order = (self.platform, self.truth) if source == "platform" else (self.truth, self.platform)
for entry in order:
if entry and entry.get("size"):
return _single_size(entry["size"])
return int(entry["size"])
return None
def native(self, key: str, default: object = "") -> object:
@@ -322,27 +302,14 @@ def build_native_model(
if candidate.truth is None
]
def by_destination(candidate: NativeFile, t_dest: str = t_dest) -> bool:
# Dolphin writes dolphin-emu/Sys/GC/USA/IPL.bin for the truth's
# GC/USA/IPL.bin: a destination ending in the path is the file.
def by_destination(candidate: NativeFile) -> bool:
theirs = _match_key(candidate.platform or {})[0]
return bool(t_dest) and (theirs == t_dest or theirs.endswith("/" + t_dest))
return bool(t_dest) and theirs == t_dest
def by_name(
candidate: NativeFile,
t_dest: str = t_dest,
t_name: str = t_name,
truth_entry: dict = truth_entry,
) -> bool:
theirs_dest, theirs = _match_key(candidate.platform or {})
def by_name(candidate: NativeFile) -> bool:
theirs = _match_key(candidate.platform or {})[1]
if not t_name or theirs != t_name:
return False
# Two declared directories that differ name two files: the
# truth's BeebFile/BIOS.rom is not Recalbox's np2kai/bios.rom.
if "/" in t_dest and "/" in theirs_dest and not theirs_dest.endswith(
"/" + t_dest.rsplit("/", 1)[0].rsplit("/", 1)[-1] + "/" + t_name
):
return False
# A shared name with two declared sizes is two files: fbneo's
# 480-byte boot.bin is not RomM's 2 MB Dreamcast boot.bin.
t_size = truth_entry.get("size")
@@ -351,7 +318,7 @@ def build_native_model(
isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size
)
def by_hash(candidate: NativeFile, t_hashes: set = t_hashes) -> bool:
def by_hash(candidate: NativeFile) -> bool:
if not t_hashes:
return False
theirs = {
+10 -55
View File
@@ -41,18 +41,6 @@ class Exporter(BaseExporter):
def carries() -> frozenset[str]:
return frozenset({"md5"})
@classmethod
def writable(cls, fe: NativeFile, require: str = "") -> bool:
"""An addition the format can state whole.
checkBios compares one md5 for a plain file (altmd5 is read only for
a zippedFile): written with the first of four, retrodream's boot.bin
refused the three other dumps the core accepts.
"""
if fe.platform is None and len(set(fe.hashes("md5"))) > 1:
return False
return super().writable(fe, require)
@staticmethod
def native_sources() -> dict[str, str]:
return {"batocera-systems": SOURCE_URL}
@@ -62,29 +50,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 +68,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 +115,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 +232,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 -21
View File
@@ -11,7 +11,7 @@ from __future__ import annotations
import re
from .base_exporter import BaseExporter
from .baseline import NativeFile, NativeSystem, Report, _hash_values
from .baseline import NativeFile, NativeSystem, Report
SOURCE_URL = (
"https://raw.githubusercontent.com/TASEmulators/BizHawk/master"
@@ -60,14 +60,6 @@ class Exporter(BaseExporter):
# hang off exist nowhere else.
return True
def states(self, fe: NativeFile, field_name: str) -> bool:
"""A name that resolves to several SHA1s is left alone, so its
correction is not a change the database receives."""
touchable = getattr(self, "_touchable", None)
if field_name == "sha1" and touchable is not None and fe.name.casefold() not in touchable:
return False
return super().states(fe, field_name)
@staticmethod
def _unambiguous(systems: dict[str, NativeSystem]) -> dict[str, NativeFile]:
"""Files whose name identifies exactly one entry with a SHA1.
@@ -80,17 +72,7 @@ class Exporter(BaseExporter):
seen: dict[str, list[NativeFile]] = {}
for system in systems.values():
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")
):
if fe.hash("sha1"):
seen.setdefault(fe.name.casefold(), []).append(fe)
resolved: dict[str, NativeFile] = {}
for name, entries in seen.items():
@@ -114,7 +96,6 @@ class Exporter(BaseExporter):
)
index = self._unambiguous(systems)
self._touchable = set(index)
def commented_out(text: str, position: int) -> bool:
"""Whether the call sits on a line the compiler never sees.
+2 -31
View File
@@ -90,35 +90,8 @@ class Exporter(BaseExporter):
def states(self, fe: NativeFile, field_name: str) -> bool:
if field_name in fe.filled:
return False
if field_name == "md5" and fe.native_system in self._refused_systems():
# The array was left as EmuDeck wrote it: nothing was corrected.
return False
return super().states(fe, field_name)
def _expected_md5s(
self, systems: dict[str, NativeSystem], name: str, system_id: str
) -> list[str]:
"""What a check must now hold; a refused rewrite keeps EmuDeck's own array."""
if name in getattr(self, "_withdrawn", {}):
return []
return self._md5s(systems, system_id)
def _refused_systems(self) -> set[str]:
withdrawn = getattr(self, "_withdrawn", {})
return {FUNCTION_HASH_MAP[name] for name in withdrawn if name in FUNCTION_HASH_MAP}
@staticmethod
def _corrected_away(systems: dict[str, NativeSystem], system_id: str) -> set[str]:
"""The platform md5 values a correction of ours replaces."""
return {
value
for system in systems.values()
if system.native_id == system_id
for fe in system.files
if "md5" in fe.corrections
for value in _hash_values(fe.platform or {}, "md5")
}
@classmethod
def _md5s(cls, systems: dict[str, NativeSystem], system_id: str) -> list[str]:
"""Every MD5 the system accepts, in a stable order, deduplicated."""
@@ -178,9 +151,7 @@ class Exporter(BaseExporter):
# and our model does not was deleted: a user whose dump matched
# it would stop passing the check. A rewrite that withdraws one
# is refused and reported instead.
# A correction replaces the value it corrects, which is not a
# withdrawal; any other value missing from ours would be.
withdrawn = theirs - set(md5s) - self._corrected_away(systems, system_id)
withdrawn = theirs - set(md5s)
if withdrawn:
self._withdrawn.setdefault(name, set()).update(withdrawn)
elif set(md5s) != theirs:
@@ -208,7 +179,7 @@ class Exporter(BaseExporter):
if name not in defined:
issues.append(f"check absent from the output: {name}")
continue
md5s = self._expected_md5s(systems, name, system_id)
md5s = self._md5s(systems, system_id)
if not md5s:
continue
body = next(
+1 -10
View File
@@ -13,7 +13,7 @@ import zipfile
from collections import OrderedDict
from .base_exporter import BaseExporter
from .baseline import NativeFile, NativeSystem, Report
from .baseline import NativeSystem, Report
SOURCE_URL = (
"https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip"
@@ -57,13 +57,6 @@ class Exporter(BaseExporter):
with zipfile.ZipFile(io.BytesIO(raw)) as archive:
return {_DB_NAME: archive.read(_DB_NAME).decode("utf-8")}
def states(self, fe: NativeFile, field_name: str) -> bool:
"""A correction whose path the database does not hold is not written."""
written = getattr(self, "_written", None)
if written is not None and id(fe) not in written:
return False
return super().states(fe, field_name)
def _by_path(self, systems: dict[str, NativeSystem]) -> dict[str, object]:
indexed: dict[str, object] = {}
for system in systems.values():
@@ -87,13 +80,11 @@ class Exporter(BaseExporter):
)
database = json.loads(original, object_pairs_hook=OrderedDict)
indexed = self._by_path(systems)
self._written = set()
for path, entry in database.get("files", {}).items():
fe = indexed.get(path)
if fe is None:
continue
self._written.add(id(fe))
md5 = fe.hash("md5")
if md5:
entry["hash"] = md5
+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)
+4 -17
View File
@@ -29,9 +29,6 @@ SOURCE_URL = (
class Exporter(BaseExporter):
"""Write RomM's known_bios_files.json, corrected."""
# (system, filename) keys the platform itself declares, set by render.
_platform_keys: frozenset[tuple[str, str]] = frozenset()
@staticmethod
def platform_name() -> str:
return "romm"
@@ -64,20 +61,16 @@ class Exporter(BaseExporter):
"""
return native_id in SLUG_MAP
def writable(self, fe: NativeFile, require: str = "") -> bool: # noqa: ARG002
@classmethod
def writable(cls, fe: NativeFile, require: str = "") -> bool:
"""What RomM already ships stays; the conditions gate additions.
An entry of theirs that could never verify is still theirs, and the
round trip is not the place to decide otherwise. An addition whose
slug:filename key the platform already holds cannot be written: the
fixture is a dict, and the second write replaced RomM's Dreamcast
boot.bin with fbneo's 480-byte file of the same name.
round trip is not the place to decide otherwise.
"""
if fe.platform is not None:
return True
if (fe.native_system, fe.name) in self._platform_keys:
return False
return self._verifiable(fe) and self._known_platform(fe.native_system)
return cls._verifiable(fe) and cls._known_platform(fe.native_system)
def render(
self,
@@ -87,12 +80,6 @@ class Exporter(BaseExporter):
scraped: dict | None = None,
) -> dict[str, str]:
output: OrderedDict[str, dict] = OrderedDict()
self._platform_keys = {
(system.native_id, fe.name)
for system in systems.values()
for fe in system.files
if fe.platform is not None
}
for system in sorted(systems.values(), key=lambda s: s.native_id):
for fe in sorted(system.files, key=lambda f: f.name):
+4 -15
View File
@@ -6,8 +6,6 @@ libretro gives them, matching libretro-database/dat/System.dat.
from __future__ import annotations
import re
import sys
from pathlib import Path
@@ -23,17 +21,9 @@ SOURCE_URL = (
)
_QUOTED_NAME = re.compile(r'rom \( name "([^"]+)"')
def _quote(name: str, quoted: frozenset[str] = frozenset()) -> str:
"""Quote a ROM name the way the original does.
Where it must be, and where the maintainers chose to: the file quotes
246 names it did not need to, and writing them bare put a quoting change
on every one of those lines of the diff.
"""
return f'"{name}"' if name in quoted or any(c in name for c in ' ()') else name
def _quote(name: str) -> str:
"""Quote a ROM name the way the original does: only when it must be."""
return f'"{name}"' if any(c in name for c in ' ()') else name
class Exporter(BaseExporter):
@@ -118,7 +108,6 @@ class Exporter(BaseExporter):
) -> dict[str, str]:
lines = self._header(originals, scraped)
lines.extend(["game (", '\tname "System"', '\tcomment "System"'])
quoted = frozenset(_QUOTED_NAME.findall(originals.get(self.native_filename(), "")))
for system, files in sorted(
self.exportable(systems), key=lambda pair: pair[0].native_id
@@ -127,7 +116,7 @@ class Exporter(BaseExporter):
for fe in files:
if not any(fe.hash(h) for h in ("crc32", "md5", "sha1")):
continue
parts = [f"name {_quote(self._rom_name(fe), quoted)}"]
parts = [f"name {_quote(self._rom_name(fe))}"]
size = fe.size()
if size:
parts.append(f"size {size}")
+2 -23
View File
@@ -15,7 +15,6 @@ import json
import os
import shutil
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
@@ -271,24 +270,6 @@ def _load_gitignored_large_files() -> set[str]:
}
def _restore(cached: str, path: str) -> None:
"""Copy a fetched asset to its registered path, whole or not at all.
An interrupted copy at the final name is a truncated file the next scan
hashes and publishes; a scratch beside it is swapped in only once full.
"""
directory = os.path.dirname(path)
os.makedirs(directory, exist_ok=True)
fd, scratch = tempfile.mkstemp(dir=directory, prefix=".restore-")
os.close(fd)
try:
shutil.copy2(cached, scratch)
os.replace(scratch, path)
finally:
if os.path.exists(scratch):
os.remove(scratch)
def _preserve_large_file_entries(files: dict, db_path: str) -> int:
"""Keep the entries of release assets the checkout does not hold.
@@ -328,7 +309,8 @@ def _preserve_large_file_entries(files: dict, db_path: str) -> int:
expected_md5=entry.get("md5", ""),
)
if cached and not os.path.exists(path):
_restore(cached, path)
os.makedirs(os.path.dirname(path), exist_ok=True)
shutil.copy2(cached, path)
files[sha1] = entry
count += 1
return count
@@ -386,9 +368,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,
}
+140 -255
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,
@@ -75,9 +73,6 @@ from nativemode import (
reads_file_contents,
)
from validation import (
existence_discrepancy,
destination_owners,
validated_choice,
inner_rom_check,
settle_mismatch,
agnostic_substitute,
@@ -206,18 +201,6 @@ def _system_tag(system_filter: list[str] | None) -> str:
return "_" + "_".join(display_parts)
_UNSAFE_IN_NAME = re.compile(r'[/\\:*?"<>|]')
def _name_part(text: str, space: str = "") -> str:
"""A display name made safe to stand in a file name.
'FinalBurn Neo (CPS-1/CPS-2)' named a directory that did not exist; a
Windows user cannot write ':' or '?' either.
"""
return _UNSAFE_IN_NAME.sub("-", text.replace(" ", space))
def _platform_pack_stem(
group_platforms: list[str], representative: str, platforms_dir: str
) -> str:
@@ -228,14 +211,14 @@ def _platform_pack_stem(
"""
rep_cfg = load_platform_config(representative, platforms_dir)
version = rep_cfg.get("version", rep_cfg.get("dat_version", ""))
version_tag = f"_{_name_part(str(version))}" if version else ""
version_tag = f"_{version.replace(' ', '')}" if version else ""
if len(group_platforms) <= 1:
return _name_part(rep_cfg.get("platform", representative), "_") + version_tag
return rep_cfg.get("platform", representative).replace(" ", "_") + version_tag
names = [
load_platform_config(p, platforms_dir).get("platform", p)
for p in group_platforms
]
return "_".join(_name_part(n) for n in names) + version_tag
return "_".join(n.replace(" ", "") for n in names) + version_tag
@@ -579,30 +562,13 @@ def _select_variants(
return region_drops, region_fallbacks, slot_undecidable
def _platform_pack_name(
platform_name: str,
platforms_dir: str,
narrowings: list[tuple[str, str]],
system_filter: list[str] | None,
pack_name: str | None = None,
) -> str:
"""File name of a platform pack: stem, narrowing tags, system tag.
A caller naming the pack itself (a --split part) is taken at its word.
"""
if pack_name:
return pack_name
stem = _platform_pack_stem([platform_name], platform_name, platforms_dir)
tags = "".join(tag for tag, _label in narrowings)
return f"{stem}{tags}_BIOS_Pack{_system_tag(system_filter)}.zip"
def generate_pack(
platform_name: str,
platforms_dir: str,
db: dict,
bios_dir: str,
output_dir: str,
include_extras: bool = False,
emulators_dir: str = "emulators",
zip_contents: dict | None = None,
data_registry: dict | None = None,
@@ -611,21 +577,15 @@ def generate_pack(
required_only: bool = False,
system_filter: list[str] | None = None,
precomputed_extras: list[dict] | None = None,
extras_only: bool = False,
source: str = "full",
flatten: bool = True,
regions: list[str] | None = None,
target_name: str | None = None,
one_per_slot: bool = False,
offline: bool | None = None,
pack_name: str | None = None,
) -> str | None:
"""Generate a ZIP pack for a platform.
``pack_name`` replaces the derived file name: a split part is named after
its group, and a name built from every system of a hundred-system group
is longer than a file name can be.
Returns the path to the generated ZIP, or None on failure.
"""
config = load_platform_config(platform_name, platforms_dir)
@@ -643,12 +603,10 @@ def generate_pack(
narrowings = _narrowings(
source, regions, target_name, one_per_slot, required_only
)
zip_path = os.path.join(
output_dir,
_platform_pack_name(
platform_name, platforms_dir, narrowings, system_filter, pack_name
),
)
narrow_tags = "".join(tag for tag, _label in narrowings)
stem = _platform_pack_stem([platform_name], platform_name, platforms_dir)
zip_name = f"{stem}{narrow_tags}_BIOS_Pack{_system_tag(system_filter)}.zip"
zip_path = os.path.join(output_dir, zip_name)
os.makedirs(output_dir, exist_ok=True)
# Case-insensitive dedup only for platforms targeting Windows/macOS.
@@ -685,7 +643,6 @@ def generate_pack(
for name in resolve_platform_cores(config, emu_profiles)
}
validation_index = _build_validation_index(platform_profiles)
validation_owners = destination_owners(platform_profiles)
slot_overrides = slots.pack_overrides(
config, emu_profiles or {}, db, zip_contents, data_registry
)
@@ -718,20 +675,13 @@ def generate_pack(
)
return None
pack_systems = filtered
if extras_only:
# The part of a split that carries the core extras no platform system
# owns: the platform's own files and trees went to the other parts.
pack_systems = {}
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,
)
@@ -901,12 +851,8 @@ def generate_pack(
file_reasons[dedup_key] = "not found"
continue
# Batocera hashes the member, never the ZIP: a zipped_file entry
# is checked inside the archive whatever the outer resolution
# said, as verify does. An archive without the member passed.
inner_checked = bool(file_entry.get("zipped_file") and local_path)
if hash_mismatch_excludes_file(verification_mode) and (
status == "hash_mismatch" or inner_checked
if status == "hash_mismatch" and hash_mismatch_excludes_file(
verification_mode
):
zf_name = file_entry.get("zipped_file")
if zf_name and local_path:
@@ -942,9 +888,15 @@ def generate_pack(
# their code never reads the bytes. An upstream hash that
# contradicts a local dump is reported, not acted on.
if status == "hash_mismatch" and local_path:
declared = file_entry.get("md5", "") or file_entry.get(
"sha1", ""
)
actual = compute_hashes(local_path)
file_reasons.setdefault(
dedup_key,
existence_discrepancy(file_entry, local_path, platform_display),
f"packed per {platform_display} existence check; "
f"declared hash {declared}, file md5 "
f"{actual['md5']} sha1 {actual['sha1']}",
)
file_status.setdefault(dedup_key, "ok")
@@ -952,15 +904,33 @@ def generate_pack(
# Platform verification (existence/md5) is the authority for pack status.
# Emulator checks are supplementary -logged but don't downgrade.
# When a discrepancy is found, try to find a file satisfying both.
if file_status.get(dedup_key) == "ok":
local_path, disagreement = validated_choice(
file_entry, local_path, db, validation_index, bios_dir,
digest_algorithm(verification_mode), dest, validation_owners,
if (
file_status.get(dedup_key) == "ok"
and local_path
and validation_index
):
fname = file_entry.get("name", "")
check = check_file_validation(
local_path, fname, validation_index, bios_dir
)
if disagreement:
file_reasons.setdefault(
dedup_key, f"{platform_display} says OK but {disagreement}"
if check:
reason, emus_list = check
better = find_validated_variant(
file_entry,
db,
local_path,
validation_index,
bios_dir,
platform_digest=digest_algorithm(verification_mode),
)
if better:
local_path = better
else:
emus = ", ".join(emus_list)
file_reasons.setdefault(
dedup_key,
f"{platform_display} says OK but {emus} says {reason}",
)
if already_packed:
continue
@@ -1252,38 +1222,6 @@ def _archive_is_optional(archive_entry: dict, archive_name: str, files: list[dic
)
def _pack_data_tree(
zf: zipfile.ZipFile,
local_cache: str,
dd_dest: str,
seen_destinations: set[str],
seen_parents: set[str],
seen_lower: set[str],
) -> int:
"""Write one data directory into an emulator pack, under its shared lock.
A refresh swaps the tree by rename; without the lock a walk half done
read the old tree and wrote files of the new one.
"""
added = 0
with cache_lock(local_cache, shared=True):
for root, _dirs, filenames in os.walk(local_cache):
for fname in filenames:
src = os.path.join(root, fname)
rel = os.path.relpath(src, local_cache)
full = f"{dd_dest}/{rel}" if dd_dest else rel
if full.lower() in seen_lower or _has_path_conflict(
full, seen_destinations, seen_parents
):
continue
seen_destinations.add(full)
_register_path(full, seen_destinations, seen_parents)
seen_lower.add(full.lower())
_add_pack_member(zf, src, full)
added += 1
return added
def generate_emulator_pack(
profile_names: list[str],
emulators_dir: str,
@@ -1295,7 +1233,6 @@ def generate_emulator_pack(
required_only: bool = False,
regions: list[str] | None = None,
offline: bool | None = None,
platforms_dir: str | None = None,
) -> str | None:
"""Generate a ZIP pack for specific emulator profiles."""
all_profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
@@ -1309,7 +1246,7 @@ def generate_emulator_pack(
return None
# ZIP naming
display_names = [_name_part(p.get("emulator", n)) for n, p in selected]
display_names = [p.get("emulator", n).replace(" ", "") for n, p in selected]
validation_index = _build_validation_index(dict(selected))
narrow_tags = "".join(
tag
@@ -1322,9 +1259,7 @@ def generate_emulator_pack(
os.makedirs(output_dir, exist_ok=True)
region_drops: set[str] = (
emulator_region_drops(selected, standalone, regions, required_only)
if regions
else set()
emulator_region_drops(selected, standalone, regions) if regions else set()
)
total_files = 0
@@ -1337,7 +1272,7 @@ def generate_emulator_pack(
seen_hashes: set[str] = set() # SHA1 dedup for same file, different path
data_dir_notices: list[str] = []
data_registry = load_data_dir_registry(
platforms_dir or os.path.join(os.path.dirname(__file__), "..", "platforms")
os.path.join(os.path.dirname(__file__), "..", "platforms")
)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
@@ -1368,9 +1303,20 @@ def generate_emulator_pack(
prefix = pack_structure.get(mode_key, "")
if prefix:
dd_dest = f"{prefix}/{dd_dest}" if dd_dest else prefix
total_files += _pack_data_tree(
zf, local_cache, dd_dest, seen_destinations, seen_parents, seen_lower
)
for root, _dirs, filenames in os.walk(local_cache):
for fname in filenames:
src = os.path.join(root, fname)
rel = os.path.relpath(src, local_cache)
full = f"{dd_dest}/{rel}" if dd_dest else rel
if full.lower() in seen_lower:
continue
if _has_path_conflict(full, seen_destinations, seen_parents):
continue
seen_destinations.add(full)
_register_path(full, seen_destinations, seen_parents)
seen_lower.add(full.lower())
_add_pack_member(zf, src, full)
total_files += 1
if not files:
print(f" No files needed for {profile.get('emulator', emu_name)}")
@@ -1557,11 +1503,19 @@ def generate_system_pack(
required_only: bool = False,
regions: list[str] | None = None,
offline: bool | None = None,
platforms_dir: str | None = None,
) -> 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,42 +1537,38 @@ 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)
# 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)
scratch = tempfile.mkdtemp(prefix=".system-", dir=output_dir)
try:
result = generate_emulator_pack(
matching,
emulators_dir,
db,
bios_dir,
scratch,
standalone,
zip_contents,
required_only=required_only,
regions=regions,
offline=offline,
platforms_dir=platforms_dir,
)
if not result:
return None
# Every dimension goes through the one list: keeping only the region
# tag made --system X --required-only overwrite the pack built without it.
# Use system-based ZIP name
sys_display = "_".join(
"_".join(w.title() for w in sid.split("-")) for sid in system_ids
)
result = generate_emulator_pack(
matching,
emulators_dir,
db,
bios_dir,
output_dir,
standalone,
zip_contents,
required_only=required_only,
regions=regions,
offline=offline,
)
if result:
# Rename to system-based name. Every dimension goes through the one
# list: keeping only the region tag made --system X --required-only
# overwrite the pack built without it.
tags = "".join(
tag
for tag, _label in _narrowings(
"full", regions, None, False, required_only, standalone=standalone
)
)
new_path = os.path.join(output_dir, f"{sys_display}{tags}_BIOS_Pack.zip")
os.replace(result, new_path)
return new_path
finally:
shutil.rmtree(scratch, ignore_errors=True)
new_name = f"{sys_display}{tags}_BIOS_Pack.zip"
new_path = os.path.join(output_dir, new_name)
if new_path != result:
os.rename(result, new_path)
result = new_path
return result
def list_platforms(platforms_dir: str) -> list[str]:
@@ -1703,13 +1653,10 @@ def generate_split_packs(
# Two groupings write different files; without the tag they accumulate in
# one directory under one SHA256SUMS.txt.
group_tag = "" if group_by == "system" else f"_By{group_by.title()}"
# The parts carry the version, so the directory does too: a rescrape that
# moves the version would otherwise leave the old parts beside the new
# under one SHA256SUMS.txt.
version = config.get("version", config.get("dat_version", ""))
ver_tag = f"_{version.replace(' ', '')}" if version else ""
part_prefix = f"{platform_display.replace(' ', '_')}{ver_tag}{split_tags}"
split_dir = os.path.join(output_dir, f"{part_prefix}{group_tag}_Split")
split_dir = os.path.join(
output_dir,
f"{platform_display.replace(' ', '_')}{split_tags}{group_tag}_Split",
)
os.makedirs(split_dir, exist_ok=True)
systems = config.get("systems", {})
@@ -1755,14 +1702,25 @@ def generate_split_packs(
target_name=target_name,
one_per_slot=one_per_slot,
offline=offline,
pack_name=f"{part_prefix}_{_name_part(group_name, '_')}_BIOS_Pack.zip",
)
if zip_path:
version = config.get("version", config.get("dat_version", ""))
ver_tag = f"_{version.replace(' ', '')}" if version else ""
narrow_tags = "".join(
tag
for tag, _label in _narrowings(
source, regions, target_name, one_per_slot, required_only
)
)
safe_group = group_name.replace(" ", "_")
new_name = f"{platform_display.replace(' ', '_')}{ver_tag}{narrow_tags}_{safe_group}_BIOS_Pack.zip"
new_path = os.path.join(split_dir, new_name)
if new_path != zip_path:
os.rename(zip_path, new_path)
zip_path = new_path
results.append(zip_path)
# Extras whose system the platform does not declare, or that name none,
# belong to no group: they go in a part of their own, or the parts would
# not add up to the full pack (4507 of RetroArch's 5430 were left out).
# Warn about extras that couldn't be distributed (emulators without systems: field)
distributed = {
id(fe)
for fe in _extras_for_systems(
@@ -1771,17 +1729,12 @@ def generate_split_packs(
}
undistributed = [fe for fe in all_extras if id(fe) not in distributed]
if undistributed:
zip_path = generate_pack(
platform_name, platforms_dir, db, bios_dir, split_dir,
emulators_dir=emulators_dir, zip_contents=zip_contents,
data_registry=data_registry, emu_profiles=emu_profiles,
target_cores=target_cores, required_only=required_only,
precomputed_extras=undistributed, extras_only=True, source=source,
regions=regions, target_name=target_name, one_per_slot=one_per_slot,
offline=offline, pack_name=f"{part_prefix}_Other_Cores_BIOS_Pack.zip",
emus = sorted({fe.get("source_emulator", "?") for fe in undistributed})
print(
f" NOTE: {len(undistributed)} core extras from {len(emus)} emulators "
f"not in split packs (missing systems: field in profiles: "
f"{', '.join(emus[:5])}{'...' if len(emus) > 5 else ''})"
)
if zip_path:
results.append(zip_path)
return results
@@ -1821,16 +1774,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", []):
@@ -1848,7 +1792,7 @@ def generate_md5_pack(
)
)
zip_name = (
f"{_name_part(context_name, '_')}_Custom{custom_tags}_BIOS_Pack.zip"
f"{context_name.replace(' ', '_')}_Custom{custom_tags}_BIOS_Pack.zip"
)
zip_path = os.path.join(output_dir, zip_name)
os.makedirs(output_dir, exist_ok=True)
@@ -2003,7 +1947,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 +2051,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 +2245,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 +2279,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:
@@ -2369,6 +2309,7 @@ def _run_platform_packs(
db,
args.bios_dir,
args.output_dir,
include_extras=args.include_extras,
emulators_dir=args.emulators_dir,
zip_contents=zip_contents,
data_registry=data_registry,
@@ -2407,12 +2348,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)
@@ -2476,31 +2411,6 @@ def _refuse_for_all_variants(args, parser) -> None:
parser.error(f"{flag} is incompatible with --verify-packs")
def _refuse_refresh_data(args, parser) -> None:
"""Only the platform pack path refreshes the data directories.
Elsewhere the cache on disk was packed as it stood, and --offline wins
over the refresh without a word.
"""
if not args.refresh_data:
return
for flag, on in (
("--offline", args.offline),
("--emulator", args.emulator),
("--system", args.system and not (args.platform or args.all)),
("--from-md5", args.from_md5 or args.from_md5_file),
):
if on:
parser.error(f"--refresh-data is incompatible with {flag}")
def _refuse_include_archived(args, parser) -> None:
"""Only --all chooses among registered platforms; every other mode names
its own platform, emulator, system or hash."""
if args.include_archived and not args.all:
parser.error("--include-archived requires --all")
def _refuse_unapplied_flags(args, parser) -> None:
"""Refuse every flag the requested mode would not apply.
@@ -2508,7 +2418,6 @@ def _refuse_unapplied_flags(args, parser) -> None:
about an artifact the caller did not name. Run before any quick-exit
mode, since --verify-packs and --manifest-targets return early.
"""
_refuse_include_archived(args, parser)
# Parsed before the quick-exit modes: --verify-packs returns early and
# still needs the region priority list to narrow its expectation.
args.regions = []
@@ -2521,7 +2430,6 @@ def _refuse_unapplied_flags(args, parser) -> None:
parser.error("--region is incompatible with --manifest-targets")
if args.one_per_slot and args.manifest_targets:
parser.error("--one-per-slot is incompatible with --manifest-targets")
_refuse_refresh_data(args, parser)
_refuse_for_all_variants(args, parser)
mode = next(
@@ -2615,6 +2523,11 @@ def main():
parser.add_argument("--db", default=DEFAULT_DB_FILE, help="Path to database.json")
parser.add_argument("--bios-dir", default=DEFAULT_BIOS_DIR)
parser.add_argument("--output-dir", "-o", default=DEFAULT_OUTPUT_DIR)
parser.add_argument(
"--include-extras",
action="store_true",
help="(no-op) Core requirements are always included",
)
parser.add_argument("--emulators-dir", default="emulators")
parser.add_argument(
"--offline",
@@ -2787,7 +2700,6 @@ def main():
required_only=args.required_only,
regions=getattr(args, "regions", None),
offline=args.offline,
platforms_dir=args.platforms_dir,
)
if not built:
sys.exit(1)
@@ -2808,7 +2720,6 @@ def main():
required_only=args.required_only,
regions=getattr(args, "regions", None),
offline=args.offline,
platforms_dir=args.platforms_dir,
)
if not built:
sys.exit(1)
@@ -3092,18 +3003,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,
@@ -3140,8 +3039,6 @@ def generate_manifest(
name: emu_profiles[name]
for name in _platform_cores(config, emu_profiles)
}
manifest_validation = _build_validation_index(manifest_profiles)
manifest_owners = destination_owners(manifest_profiles)
# Load registry for install metadata
registry: dict = {}
@@ -3213,10 +3110,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()):
@@ -3282,12 +3179,6 @@ def generate_manifest(
):
record_omission(full_dest, file_entry, sys_id, status, None)
continue
# The variant the pack ships when an emulator rejects the
# resolved file: the installer must place the same bytes.
local_path, _disagreement = validated_choice(
file_entry, local_path, db, manifest_validation, bios_dir,
digest_algorithm(verification_mode), dest, manifest_owners,
)
# Get SHA1 and size. The installer fetches by hash, so record
# the copy this repo holds: an upstream hash carried by no
@@ -3345,7 +3236,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 +3246,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,
@@ -3466,15 +3354,12 @@ def _target_cores_for(
) -> set[str] | None:
"""Cores a target leaves available, or None when no target was asked for.
None means no narrowing, which is what an unfiltered pack expects. Read
the way --all builds: a platform without a target file was packed
unnarrowed under the target's name, and checking that pack must not
raise where building it did not.
None means no narrowing, which is what an unfiltered pack expects.
"""
if not target_name:
return None
cache, _kept = build_target_cores_cache(
[platform_name], target_name, platforms_dir, is_all=True
[platform_name], target_name, platforms_dir
)
return cache.get(platform_name)
+24 -69
View File
@@ -15,7 +15,6 @@ import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import (
@@ -34,24 +33,17 @@ import release_record
from verify import verify_platform
# The repository's profiles wherever the script is run from: a bare
# "emulators" read the working directory, and a run elsewhere counted 0 cores.
_EMULATORS_DIR = str(Path(__file__).resolve().parent.parent / "emulators")
def compute_coverage(
platform_name: str,
platforms_dir: str,
db: dict,
data_registry: dict | None = None,
supplemental_names: set[str] | None = None,
emulators_dir: str = _EMULATORS_DIR,
) -> dict:
config = load_platform_config(platform_name, platforms_dir)
result = verify_platform(
config,
db,
emulators_dir,
data_dir_registry=data_registry,
supplemental_names=supplemental_names,
)
@@ -67,7 +59,7 @@ def compute_coverage(
core_missing = len(undeclared) - core_present
# Files a profile marks unsourceable never reach the undeclared list, so
# the gap they represent has to be counted back in.
profiles = load_emulator_profiles(emulators_dir)
profiles = load_emulator_profiles("emulators")
unsourceable_names = {
f.get("name", "")
for emu in resolve_platform_cores(config, profiles)
@@ -118,44 +110,6 @@ def release_totals(platform_name: str, record: dict) -> tuple[int | None, int |
return pack["files"], pack["extracted_size"]
def extract_targets(platforms_dir: str) -> list[tuple[str, str]]:
"""(display name, folder) per registered platform, where its pack extracts.
Read from the registry so the README, the site and the pack guide answer
from one place. Sorted by display name.
"""
rows = [
(load_platform_config(key, platforms_dir).get("platform", key), entry["extract_to"])
for key, entry in load_platform_registry(platforms_dir).items()
if entry.get("extract_to")
]
return sorted(rows, key=lambda row: row[0].casefold())
def extract_notes(platforms_dir: str) -> list[str]:
"""A pack whose entries carry their own root extracts above the BIOS folder."""
folders = dict(extract_targets(platforms_dir))
notes = []
for key in sorted(load_platform_registry(platforms_dir)):
config = load_platform_config(key, platforms_dir)
display = config.get("platform", key)
if config.get("base_destination") == "" and display in folders:
notes.append(
f"The {display} pack already carries its own `bios/` folder, so it"
f" extracts into `{folders[display]}` rather than into the BIOS folder."
)
return notes
def _extract_cells(platforms_dir: str) -> dict[str, str]:
"""The download table's "Extract to" cell, by display name."""
return {display: f"`{folder}`" for display, folder in extract_targets(platforms_dir)}
def _paragraphs(texts: list[str]) -> list[str]:
return [line for text in texts for line in ("", text)]
def download_table(
coverages: dict,
archived: set[str],
@@ -402,7 +356,20 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
# Where the pack itself is extracted, which is not always the BIOS folder:
# a pack whose entries already carry their own root (RetroDECK) extracts
# one level above it.
extract_paths = _extract_cells(platforms_dir)
extract_paths = {
"RetroArch": "`system/`",
"Lakka": "`/storage/system/`",
"Batocera": "`/userdata/bios/`",
"BizHawk": "`Firmware/`",
"Recalbox": "`/recalbox/share/bios/`",
"RetroBat": "`bios/`",
"RetroPie": "`~/RetroPie/BIOS/`",
"RetroDECK": "`~/retrodeck/`",
"EmuDeck": "`~/Emulation/bios/`",
"RomM": "`bios/{platform_slug}/`",
"ROCKNIX": "`/storage/roms/bios/`",
"MiSTer FPGA": "`/media/fat/games/`",
}
archived = {
name
for name, entry in load_platform_registry(platforms_dir).items()
@@ -415,10 +382,12 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
)
)
lines.extend(_paragraphs(extract_notes(platforms_dir)))
if archived:
lines.extend(
[
"",
"The RetroDECK pack already contains its own `bios/` folder, so it"
" extracts into `~/retrodeck/` rather than into the BIOS folder.",
"",
"\\* Archived: the configuration is kept and packs are still built,"
" but upstream is no longer scraped on a schedule.",
@@ -672,14 +641,8 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
return "\n".join(lines) + "\n"
def contributing_text(link, title: str, site_words: str) -> str:
"""The contribution guide, one text for the repository and the site.
Two copies drifted apart: four steps to add a platform here, five on the
site. *link* renders a site page (absolute URL for the README, relative
.md for MkDocs); *site_words* names the site from where the text is read.
"""
return f"""{title}
def generate_contributing() -> str:
return f"""# Contributing to RetroBIOS
## Add a BIOS file
@@ -688,7 +651,7 @@ def contributing_text(link, title: str, site_words: str) -> str:
3. Variants (alternate hashes for the same file): `bios/Manufacturer/Console/.variants/`
4. Open a Pull Request - hashes are verified automatically and reported as a comment
The [dump provenance]({link("provenance")}) page lists catalogued dumps still
The [dump provenance]({SITE_URL}provenance/) page lists catalogued dumps still
missing from the collection, with their hashes. A file matching one of those is
the most useful contribution.
@@ -699,7 +662,7 @@ the most useful contribution.
3. Register it in `platforms/_registry.yml`
4. Generate the platform YAML and test: `python scripts/verify.py --platform <name>`
Full walkthrough: [adding a platform]({link("wiki/adding-a-platform")}).
Full walkthrough: [adding a platform]({SITE_URL}wiki/adding-a-platform/).
## Add an emulator profile
@@ -709,7 +672,7 @@ Full walkthrough: [adding a platform]({link("wiki/adding-a-platform")}).
4. Write the YAML to `emulators/<name>.yml`
5. Test: `python scripts/cross_reference.py --emulator <name>`
Full walkthrough: [profiling guide]({link("wiki/profiling")}).
Full walkthrough: [profiling guide]({SITE_URL}wiki/profiling/).
## File conventions
@@ -734,18 +697,10 @@ configs, validates the YAML against the schemas, runs the test suite, and posts
a report on the PR.
Contributors who add platform support are credited in the README,
{site_words}, and in the BIOS packs.
on the documentation site, and in the BIOS packs.
"""
def generate_contributing() -> str:
return contributing_text(
lambda page: f"{SITE_URL}{page}/",
"# Contributing to RetroBIOS",
"on the documentation site",
)
def main():
parser = argparse.ArgumentParser(description="Generate slim README.md")
parser.add_argument("--db", default="database.json")
+233 -138
View File
@@ -31,20 +31,20 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import (
build_zip_contents_index,
compute_composition,
count_catalog_matched,
list_registered_platforms,
load_database,
load_emulator_profiles,
load_provenance_snapshots,
parse_md5_list,
preferred_profile,
require_yaml,
resolve_platform_cores,
unique_emulator_profiles,
upstream_profile_index,
write_if_changed as _write_artifact,
yaml_load,
)
from cross_reference import FileConsumers
from nativemode import reads_file_contents
@@ -53,13 +53,7 @@ from nativemode import reads_file_contents
yaml = require_yaml()
from generate_readme import (
compute_coverage,
contributing_text,
extract_notes,
extract_targets,
release_totals,
)
from generate_readme import compute_coverage, release_totals
from profile_sync import source_ref_values, split_source_ref
from provenance_report import build_report
import release_record
@@ -207,22 +201,8 @@ def _timestamp() -> str:
# Home page
def _extract_table(platforms_dir: str) -> list[str]:
"""The home page's extraction table, indented for its admonition."""
rows = [
f" | {display} | `{folder}` |"
for display, folder in extract_targets(platforms_dir)
]
notes = [f" {note}" for note in extract_notes(platforms_dir)]
return [" | Platform | Extract to |", " |----------|-----------|", *rows, "", *notes]
def generate_home(
db: dict,
coverages: dict,
profiles: dict,
registry: dict | None = None,
platforms_dir: str = "platforms",
db: dict, coverages: dict, profiles: dict, registry: dict | None = None
) -> str:
total_files = db.get("total_files", 0)
total_size = db.get("total_size", 0)
@@ -338,9 +318,24 @@ def generate_home(
"",
'??? info "Where to extract"',
"",
*_extract_table(platforms_dir),
" Every other pack extracts straight into the BIOS folder."
" [Full instructions per setup](which-pack.md).",
" | Platform | Extract to |",
" |----------|-----------|",
" | RetroArch | `system/` |",
" | Batocera | `/userdata/bios/` |",
" | BizHawk | `Firmware/` |",
" | EmuDeck | `~/Emulation/bios/` |",
" | Lakka | `/storage/system/` |",
" | MiSTer FPGA | `/media/fat/games/` |",
" | ROCKNIX | `/storage/roms/bios/` |",
" | Recalbox | `/recalbox/share/bios/` |",
" | RetroBat | `bios/` |",
" | RetroDECK | `~/retrodeck/` |",
" | RetroPie | `~/RetroPie/BIOS/` |",
" | RomM | `bios/{platform_slug}/` |",
"",
" The RetroDECK pack already carries its own `bios/` folder, so it "
"extracts one level above it. Every other pack extracts straight into "
"the BIOS folder. [Full instructions per setup](which-pack.md).",
"",
]
)
@@ -530,7 +525,7 @@ def build_emulator_gap_report(
The gap analysis page and the published gaps export must not compute this
twice and drift; both read this one report.
"""
from common import _norm_system_id, expand_platform_declared_names
from common import expand_platform_declared_names
from cross_reference import cross_reference as run_cross_reference
all_declared: set[str] = set()
@@ -543,7 +538,7 @@ def build_emulator_gap_report(
for fe in system.get("files", []):
fname = fe.get("name", "")
if fname:
declared.setdefault(_norm_system_id(sys_id), set()).add(fname)
declared.setdefault(sys_id, set()).add(fname)
unique_profiles = {
k: v
@@ -1652,7 +1647,8 @@ def generate_provenance_page(db: dict, report: dict) -> str:
def generate_system_page(
manufacturer: str,
consoles: dict[str, list],
consumers: FileConsumers,
platform_files: dict[str, set],
emulator_files: dict[str, dict],
) -> str:
manufacturer.lower().replace(" ", "-")
lines = [
@@ -1675,9 +1671,13 @@ def generate_system_page(
md5_full = f.get("md5", "unknown")
size = _fmt_size(f.get("size", 0))
# Who reads this file, not who reads a file of this name
plats = sorted(consumers.platforms.get(sha1_full, ()))
emus = sorted(consumers.emulators.get(sha1_full, ()))
# Cross-reference
plats = sorted(p for p, names in platform_files.items() if name in names)
emus = sorted(
e
for e, data in emulator_files.items()
if name in data.get("files", set())
)
# Truncated hashes for readability
sha1_short = sha1_full[:12] if sha1_full != "unknown" else "-"
@@ -1873,18 +1873,14 @@ def _file_badges(f: dict, in_repo: bool) -> list[str]:
badges.append(
'<span class="rb-badge rb-badge-muted">optional</span>'
)
if in_repo:
badges.append(
'<span class="rb-badge rb-badge-success">in repo</span>'
)
elif f.get("unsourceable"):
badges.append(
'<span class="rb-badge rb-badge-muted">unsourceable</span>'
)
else:
if not in_repo:
badges.append(
'<span class="rb-badge rb-badge-warning">missing</span>'
)
elif in_repo:
badges.append(
'<span class="rb-badge rb-badge-success">in repo</span>'
)
if hle:
badges.append(
'<span class="rb-badge rb-badge-info">HLE fallback</span>'
@@ -1935,7 +1931,7 @@ def _file_badges(f: dict, in_repo: bool) -> list[str]:
def _render_emulator_file(
f: dict,
profile: dict,
consumers: FileConsumers | None,
platform_files: dict | None,
files: list,
_file_available,
) -> list[str]:
@@ -2049,9 +2045,10 @@ def _render_emulator_file(
details.append(
f"Source: {_source_ref_markdown(profile, source_ref)}"
)
if consumers:
sha1 = consumers.sha1_of(f, f.get("path") or "")
plats = sorted(consumers.platforms.get(sha1, ())) if sha1 else []
if platform_files:
plats = sorted(
p for p, names in platform_files.items() if fname in names
)
if plats:
plat_links = [_platform_link(p, p, "../") for p in plats]
details.append(f"Platforms: {', '.join(plat_links)}")
@@ -2220,75 +2217,55 @@ def _availability_check(db: dict, data_names):
missing while the gap report called it held would describe a
different collection on two pages of the same site.
"""
from cross_reference import entry_source
from cross_reference import _resolve_source
by_name = db.get("indexes", {}).get("by_name", {})
by_name_lower = {k.lower(): k for k in by_name}
by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {})
index = {
"by_name": by_name,
"by_name_lower": by_name_lower,
"by_path_suffix": by_path_suffix,
"by_md5": db.get("indexes", {}).get("by_md5", {}),
"by_sha256": db.get("indexes", {}).get("by_sha256", {}),
"by_crc32": db.get("indexes", {}).get("by_crc32", {}),
"db_files": db.get("files", {}),
"data_names": data_names,
}
by_md5 = db.get("indexes", {}).get("by_md5", {})
db_files = db.get("files", {})
def _file_available(f: dict) -> bool:
"""Check if a file is available using the same resolution as cross_reference."""
# cross_reference sets these aside before resolving: the file found
# under the name belongs to another game or release.
if not f.get("name") or f.get("unsourceable"):
fname = f.get("name", "")
if not fname:
return False
if f.get("storage", "") in ("release", "large_file"):
storage = f.get("storage", "")
if storage in ("release", "large_file"):
return True
return entry_source(f, index) is not None
src = _resolve_source(
fname, by_name, by_name_lower, data_names, by_path_suffix,
f, db_files,
)
if src is not None:
return True
path_field = f.get("path", "")
if path_field and path_field != fname:
src = _resolve_source(
path_field, by_name, by_name_lower, data_names,
by_path_suffix, f, db_files,
)
if src is not None:
return True
md5_raw = f.get("md5", "")
if md5_raw:
for md5_val in parse_md5_list(md5_raw):
if by_md5.get(md5_val):
return True
# A profile lists several sha1 when the code accepts several dumps.
sha1 = f.get("sha1") or []
if any(value in db_files for value in ([sha1] if isinstance(sha1, str) else sha1)):
return True
return False
return _file_available
def _emulator_file_summary(files: list[dict], available) -> list[str]:
"""The counts above an emulator's file table, and its categories."""
in_repo_count = sum(1 for f in files if available(f))
unsourceable_count = sum(1 for f in files if f.get("unsourceable"))
missing_count = len(files) - in_repo_count - unsourceable_count
req_count = sum(1 for f in files if f.get("required"))
hle_count = sum(1 for f in files if f.get("hle_fallback"))
held = f"{in_repo_count} in repo, {missing_count} missing"
if unsourceable_count:
held += f", {unsourceable_count} unsourceable"
parts = [
f"**{len(files)} files**",
f"{req_count} required, {len(files) - req_count} optional",
held,
]
if hle_count:
parts.append(f"{hle_count} with HLE fallback")
lines = [" | ".join(parts)]
categories = [f.get("category", "bios") for f in files]
if "game_data" in categories or "bios_zip" in categories:
cats = [
f"{categories.count(key)} {label}"
for key, label in (
("bios", "BIOS"), ("game_data", "game data"), ("bios_zip", "BIOS ZIPs")
)
if categories.count(key)
]
lines.append(f"Categories: {', '.join(cats)}")
lines.append("")
return lines
def generate_emulator_page(
name: str,
profile: dict,
db: dict,
consumers: FileConsumers | None = None,
platform_files: dict | None = None,
data_names: set[str] | None = None,
) -> str:
if profile.get("type") == "alias":
@@ -2400,13 +2377,40 @@ def generate_emulator_page(
else:
_file_available = _availability_check(db, data_names)
lines.extend(_emulator_file_summary(files, _file_available))
# Stats by category
bios_files = [f for f in files if f.get("category", "bios") == "bios"]
game_data = [f for f in files if f.get("category") == "game_data"]
bios_zips = [f for f in files if f.get("category") == "bios_zip"]
in_repo_count = sum(1 for f in files if _file_available(f))
missing_count = len(files) - in_repo_count
req_count = sum(1 for f in files if f.get("required"))
opt_count = len(files) - req_count
hle_count = sum(1 for f in files if f.get("hle_fallback"))
parts = [f"**{len(files)} files**"]
parts.append(f"{req_count} required, {opt_count} optional")
parts.append(f"{in_repo_count} in repo, {missing_count} missing")
if hle_count:
parts.append(f"{hle_count} with HLE fallback")
lines.append(" | ".join(parts))
if game_data or bios_zips:
cats = []
if bios_files:
cats.append(f"{len(bios_files)} BIOS")
if game_data:
cats.append(f"{len(game_data)} game data")
if bios_zips:
cats.append(f"{len(bios_zips)} BIOS ZIPs")
lines.append(f"Categories: {', '.join(cats)}")
lines.append("")
# File table
for f in files:
lines.extend(
_render_emulator_file(
f, profile, consumers, files, _file_available
f, profile, platform_files, files, _file_available
)
)
@@ -2937,7 +2941,7 @@ def generate_gap_analysis(
def _render_xref_by_platform(
coverages: dict, unique: dict
coverages: dict, unique: dict, core_to_profile: dict
) -> list[str]:
"""Which cores each platform pulls in, and what they need."""
lines: list[str] = []
@@ -2956,14 +2960,26 @@ def _render_xref_by_platform(
)
lines.append("")
# The cores the builder and verify resolve: a hand copy here dropped
# the libretro set a list naming retroarch pulls in, and the system
# fallback EmuDeck relies on.
matched = {
key: unique[key]
for key in resolve_platform_cores(config, unique)
if key in unique
}
# Resolve which profiles this platform uses
if platform_cores == "all_libretro":
matched = {
k: v for k, v in unique.items() if "libretro" in v.get("type", "")
}
elif isinstance(platform_cores, list):
matched = {}
for cname in platform_cores:
cname_str = str(cname)
if cname_str in unique:
matched[cname_str] = unique[cname_str]
elif cname_str in core_to_profile:
pkey = core_to_profile[cname_str]
matched[pkey] = unique[pkey]
else:
# Fallback: system intersection
psystems = set(config.get("systems", {}).keys())
matched = {
k: v for k, v in unique.items() if set(v.get("systems", [])) & psystems
}
if platform_cores == "all_libretro":
lines.append(f" **{len(matched)} cores** (all libretro)")
@@ -3027,7 +3043,7 @@ def _render_xref_by_platform(
def _render_xref_by_upstream(
coverages: dict, unique: dict
coverages: dict, unique: dict, core_to_profile: dict
) -> list[str]:
"""The same relation read the other way: one row per upstream."""
lines: list[str] = []
@@ -3052,8 +3068,20 @@ def _render_xref_by_upstream(
# Build platform membership per core
platform_membership: dict[str, set[str]] = {}
for pname, cov in coverages.items():
for key in resolve_platform_cores(cov["config"], unique):
platform_membership.setdefault(key, set()).add(pname)
config = cov["config"]
pcores = config.get("cores", [])
if pcores == "all_libretro":
for k, v in unique.items():
if "libretro" in v.get("type", ""):
platform_membership.setdefault(k, set()).add(pname)
elif isinstance(pcores, list):
for cname in pcores:
cname_str = str(cname)
if cname_str in unique:
platform_membership.setdefault(cname_str, set()).add(pname)
elif cname_str in core_to_profile:
pkey = core_to_profile[cname_str]
platform_membership.setdefault(pkey, set()).add(pname)
for upstream_url in sorted(by_upstream.keys()):
cores = by_upstream[upstream_url]
@@ -3090,6 +3118,12 @@ def generate_cross_reference(
k: v for k, v in profiles.items() if v.get("type") not in ("alias", "test")
}
# Core name -> the profile it designates, the rule target filtering uses.
index = upstream_profile_index(unique)
core_to_profile: dict[str, str] = {
core: preferred_profile(index, core) for core in index
}
total_cores = len(unique)
total_upstreams = len({
p.get("upstream", p.get("source", ""))
@@ -3113,17 +3147,72 @@ def generate_cross_reference(
"",
]
lines.extend(_render_xref_by_platform(coverages, unique))
lines.extend(_render_xref_by_upstream(coverages, unique))
lines.extend(_render_xref_by_platform(coverages, unique, core_to_profile))
lines.extend(_render_xref_by_upstream(coverages, unique, core_to_profile))
lines.extend(["", f"*Generated on {_timestamp()}*"])
return "\n".join(lines) + "\n"
def generate_contributing() -> str:
return contributing_text(
lambda page: f"{page}.md", "# Contributing - RetroBIOS", "on this site"
)
return """# Contributing - RetroBIOS
## Add a BIOS file
1. Fork this repository
2. Place the file in `bios/Manufacturer/Console/filename`
3. Variants (alternate hashes for the same file): place in `bios/Manufacturer/Console/.variants/`
4. Open a Pull Request - hashes are verified automatically and reported as a comment
The [dump provenance](provenance.md) page lists catalogued dumps still missing
from the collection, with their hashes. A file matching one of those is the
most useful contribution.
## Add a platform
1. Create a scraper in `scripts/scraper/` (inherit `BaseScraper`)
2. Read the platform's upstream source to determine how it checks BIOS files
3. Add an entry to `platforms/_registry.yml`
4. Generate the platform YAML config
5. Test: `python scripts/verify.py --platform <name>`
Full walkthrough: [adding a platform](wiki/adding-a-platform.md).
## Add an emulator profile
1. Clone the emulator's source code, upstream and libretro port
2. Trace the file loading from the entry point, not from a keyword grep
3. Document every file the code loads, with a `source_ref` line reference
4. Write the YAML to `emulators/<name>.yml`
5. Test: `python scripts/cross_reference.py --emulator <name>`
Full walkthrough: [profiling guide](wiki/profiling.md).
## File conventions
- `bios/Manufacturer/Console/filename` for canonical files
- `bios/Manufacturer/Console/.variants/filename.sha1prefix` for alternate versions
- Files >50 MB go in GitHub release assets (`large-files` release)
- RPG Maker and ScummVM directories are excluded from deduplication
- Two paths differing only by case break clones on Windows and macOS;
`tests/test_no_case_collisions.py` enforces this
## Before opening a PR
```bash
python -m unittest discover tests
python scripts/pipeline.py --offline
```
## PR validation
CI computes SHA1/MD5/CRC32 for every new file, checks them against the platform
configs, validates the YAML against the schemas, runs the test suite, and posts
a report on the PR.
Contributors who add platform support are credited in the README, on this site,
and in the BIOS packs.
"""
# Wiki pages
@@ -3249,21 +3338,30 @@ def generate_wiki_data_model(db: dict, profiles: dict) -> str:
# Build cross-reference indexes
def _build_file_consumers(db: dict, coverages: dict, profiles: dict) -> FileConsumers:
"""Which platforms and emulators read each collected file."""
consumers = FileConsumers(db, build_zip_contents_index(db))
consumers.add_platforms({name: cov["config"] for name, cov in coverages.items()})
consumers.add_emulators(profiles)
return consumers
def _build_platform_file_index(coverages: dict) -> dict[str, set]:
"""Map platform_name -> set of declared file names."""
index = {}
for name, cov in coverages.items():
names = set()
config = cov["config"]
for system in config.get("systems", {}).values():
for fe in system.get("files", []):
names.add(fe.get("name", ""))
index[name] = names
return index
def _build_emulator_file_index(profiles: dict) -> dict[str, dict]:
"""Map emulator_name -> {systems: set}, for the platform pages."""
return {
name: {"systems": set(profile.get("systems", []))}
for name, profile in profiles.items()
if profile.get("type") != "alias"
}
"""Map emulator_name -> {files: set, systems: set} for cross-reference."""
index = {}
for name, profile in profiles.items():
if profile.get("type") == "alias":
continue
index[name] = {
"files": {f.get("name", "") for f in profile.get("files", [])},
"systems": set(profile.get("systems", [])),
}
return index
# mkdocs.yml nav generator
@@ -3589,8 +3687,7 @@ def main():
for name in sorted(platform_names):
try:
cov = compute_coverage(
name, args.platforms_dir, db, data_registry, suppl_names,
emulators_dir=args.emulators_dir,
name, args.platforms_dir, db, data_registry, suppl_names
)
coverages[name] = cov
print(
@@ -3607,15 +3704,13 @@ def main():
)
# Build cross-reference indexes
consumers = _build_file_consumers(db, coverages, profiles)
platform_files = _build_platform_file_index(coverages)
emulator_files = _build_emulator_file_index(profiles)
# Generate home
print("Generating home page...")
write_if_changed(
str(docs / "index.md"), generate_home(
db, coverages, profiles, registry, args.platforms_dir
)
str(docs / "index.md"), generate_home(db, coverages, profiles, registry)
)
stats = compute_stats(db, coverages, profiles)
@@ -3664,7 +3759,7 @@ def main():
)
for mfr, consoles in manufacturers.items():
slug = mfr.lower().replace(" ", "-")
page = generate_system_page(mfr, consoles, consumers)
page = generate_system_page(mfr, consoles, platform_files, emulator_files)
write_if_changed(str(docs / "systems" / f"{slug}.md"), page)
# Generate emulator pages
@@ -3678,7 +3773,7 @@ def main():
if profile.get("type") not in ("alias", "test")
}
for name, profile in public_profiles.items():
page = generate_emulator_page(name, profile, db, consumers, suppl_names)
page = generate_emulator_page(name, profile, db, platform_files, suppl_names)
write_if_changed(str(docs / "emulators" / f"{name}.md"), page)
# Generate cross-reference page
+16 -59
View File
@@ -163,15 +163,11 @@ def _fetch_asset(
hashes, expected_sha1, expected_md5
):
return cached
elif not _replaced_on_release(name, cached, offline):
# A copy of this asset that answers another hash: the caller
# wants a different revision under the same name, and the
# release still serves this one. Keeping it is what lets the
# next caller for the primary find it.
else:
# A verified copy of this asset that answers another hash: the
# caller wants a different revision under the same name. Keeping
# it is what lets the next caller for the primary find it.
return None
# Otherwise the release was re-uploaded since this copy was cached
# (gh release upload --clobber): only a download can answer, and
# it replaces the copy only if it verifies.
if offline:
return None
@@ -183,9 +179,20 @@ def _fetch_asset(
dir=dest_dir, prefix=os.path.basename(cached) + ".", suffix=".tmp"
)
os.close(tmp_fd)
# GitHub rewrites spaces to dots in release asset names, so a file whose
# name contains spaces is published under a dotted name.
candidates = [name]
if " " in name:
candidates.append(name.replace(" ", "."))
try:
downloaded = False
for candidate, url in _asset_urls(name):
for candidate in candidates:
encoded_name = urllib.parse.quote(candidate)
url = (
f"https://github.com/{LARGE_FILES_REPO}/releases/download/"
f"{LARGE_FILES_RELEASE}/{encoded_name}"
)
try:
req = urllib.request.Request(url, headers={"User-Agent": "retrobios/1.0"})
with urllib.request.urlopen(req, timeout=300) as resp:
@@ -213,56 +220,6 @@ def _fetch_asset(
_drop(tmp_path)
def _asset_urls(name: str) -> list[tuple[str, str]]:
"""Release URLs an asset may be published under, with their names.
GitHub rewrites spaces to dots in release asset names, so a file whose
name contains spaces is published under a dotted name.
"""
candidates = [name]
if " " in name:
candidates.append(name.replace(" ", "."))
return [
(
candidate,
(
f"https://github.com/{LARGE_FILES_REPO}/releases/download/"
f"{LARGE_FILES_RELEASE}/{urllib.parse.quote(candidate)}"
),
)
for candidate in candidates
]
def _replaced_on_release(name: str, cached: str, offline: bool) -> bool:
"""Whether the release now serves other bytes than the cached copy.
Sizes are compared, as check_release_assets does: a request per asset,
no download. Offline or unreadable, the copy is assumed current.
"""
if offline:
return False
served = _served_size(name)
return served is not None and served != os.path.getsize(cached)
def _served_size(name: str) -> int | None:
"""Size of the asset the release serves now, or None when unreadable."""
for candidate, url in _asset_urls(name):
try:
req = urllib.request.Request(
url, method="HEAD", headers={"User-Agent": "retrobios/1.0"}
)
with urllib.request.urlopen(req, timeout=60) as resp:
length = resp.headers.get("Content-Length")
except (urllib.error.URLError, OSError, http.client.HTTPException) as exc:
print(f" large file {candidate}: {exc}", file=sys.stderr)
continue
if length and length.isdigit():
return int(length)
return None
def _matches(hashes: dict, expected_sha1: str, expected_md5: str) -> bool:
"""Whether computed hashes answer the caller's declaration."""
if expected_sha1 and hashes["sha1"].lower() != expected_sha1.lower():
+20 -64
View File
@@ -14,7 +14,6 @@ from common import resolve_local_file
from common import runs_standalone
from common import sanitize_pack_path
from common import size_fits
from validation import read_from_system_dir
def _emulator_systems_index(emu_profiles: dict | None) -> dict[str, list[str]]:
"""Map both the profile key and its display name to the profile's systems.
@@ -134,42 +133,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 +411,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 +471,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)
@@ -603,7 +564,9 @@ def _collect_emulator_extras(
continue
if file_mode == "libretro" and is_standalone:
continue
if not read_from_system_dir(f):
# Skip files loaded from non-system directories (save_dir, content_dir)
load_from = f.get("load_from", "")
if load_from and load_from != "system_dir":
continue
if is_standalone:
raw = f.get("standalone_path") or f.get("path") or fname
@@ -719,7 +682,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", "")))
@@ -747,10 +709,7 @@ def platform_region_groups(
return groups, extra_dests
def emulator_region_drops(
selected: list[tuple[str, dict]],
standalone: bool,
regions: list[str],
required_only: bool = False,
selected: list[tuple[str, dict]], standalone: bool, regions: list[str]
) -> set[str]:
"""Destinations an emulator-mode `--region` withdraws.
@@ -767,10 +726,7 @@ def emulator_region_drops(
region_groups: dict[str, list[tuple[str, str]]] = {}
for emu_name, profile in sorted(selected):
structure = profile.get("pack_structure")
# Decided over what the pack keeps, as on the platform side: an
# optional winner removed afterwards left its slot empty.
entries = filter_files_by_mode(profile.get("files", []), standalone)
for fe in _kept(entries, required_only):
for fe in filter_files_by_mode(profile.get("files", []), standalone):
dest = _resolve_destination(fe, structure, standalone)
if dest:
group_id = _emulator_region_group(emu_name, profile, fe)
+18 -19
View File
@@ -26,7 +26,6 @@ Usage:
from __future__ import annotations
import argparse
import contextlib
import re
import subprocess
import sys
@@ -34,7 +33,7 @@ import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from common import ArtifactLockBusy, artifact_lock, hold_artifact_lock
from common import ArtifactLockBusy, artifact_lock
def run(cmd: list[str], label: str) -> tuple[bool, str]:
@@ -120,11 +119,11 @@ def parse_pack_exclusions(output: str) -> dict[str, int]:
for line in output.splitlines():
label_match = re.match(r"Generating (?:shared )?pack for (.+)\.\.\.", line)
if label_match:
# The same reading as parse_pack_counts: a reduced variant must
# not overwrite the full pack's count under the shared label.
current_label = _full_pack_label(label_match.group(1).strip())
current_label = re.sub(
r"\s+\[source=[^\]]+\]$", "", label_match.group(1).strip()
)
continue
if not current_label or "files packed" not in line:
if "files packed" not in line:
continue
excluded_match = re.search(r"(\d+) unsafe excluded", line)
exclusions[current_label] = (
@@ -198,13 +197,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
@@ -322,6 +317,12 @@ def main():
parser.add_argument(
"--output-dir", default="dist", help="Pack output directory (default: dist/)"
)
# --include-extras is now a no-op: core requirements are always included
parser.add_argument(
"--include-extras",
action="store_true",
help="(no-op) Core requirements are always included",
)
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
parser.add_argument("--source", choices=["platform", "truth", "full"], default="full")
parser.add_argument("--all-variants", action="store_true")
@@ -345,14 +346,11 @@ def main():
# A second run on the same output directory is refused before any work:
# the database rebuild alone takes minutes, and the reader holding the
# directory would otherwise see the answer only after all of it. The
# hold lasts until this process exits: taken step by step, it let another
# run purge the packs between the build and the integrity check, which
# then reported every platform SKIP and passed.
held = contextlib.ExitStack()
if not args.skip_packs:
# directory would otherwise see the answer only after all of it.
if not args.skip_packs and Path(args.output_dir).is_dir():
try:
held.enter_context(hold_artifact_lock(args.output_dir))
with artifact_lock(args.output_dir):
pass
except ArtifactLockBusy as exc:
print(f"ERROR: {exc}")
sys.exit(1)
@@ -527,6 +525,8 @@ def main():
pack_cmd.append("--include-archived")
if args.offline:
pack_cmd.append("--offline")
if args.include_extras:
pack_cmd.append("--include-extras")
if args.target:
pack_cmd.extend(["--target", args.target])
if args.source != "full":
@@ -667,7 +667,6 @@ def main():
print(f"{'=' * 60}")
print(f" Pipeline {'COMPLETE' if all_ok else 'FINISHED WITH ERRORS'}")
print(f"{'=' * 60}")
held.close()
sys.exit(0 if all_ok else 1)
+67 -187
View File
@@ -71,11 +71,6 @@ class RefPart:
end: int | None
raw: str = ""
@property
def last(self) -> int | None:
"""Last cited line: the end of a range, or the single line."""
return self.end or self.start
@dataclass(frozen=True)
class AnchorResult:
@@ -232,24 +227,6 @@ def collect_tokens(entry: dict) -> list[str]:
return tokens
_VERSION_NOTE_RE = re.compile(r"\(\s*v?(\d+(?:\.\d+)+)\s*\)")
def cites_another_version(part: RefPart, core_version: object) -> str | None:
"""The version a part's annotation names, when it is not the documented one.
A profile may cite two programs side by side, `main.cpp:407-411 (v0.0.11)`
beside its v0.16.0 pin. The annotation was dropped as prose and the part
compared with the pin, where it fell on unrelated code yet read ANCHORED.
"""
match = _VERSION_NOTE_RE.search(part.raw or "")
if not match:
return None
declared = core_version.values() if isinstance(core_version, dict) else [core_version]
documented = {str(v or "").strip().lstrip("vV") for v in declared}
return None if match.group(1) in documented else match.group(1)
def is_external_citation(path: str) -> bool:
"""True when a ref names a project rather than a path in a known repo.
@@ -680,8 +657,8 @@ def anchor_block(
pin = _normalize(pin_lines)
head = _normalize(head_lines)
lo, hi = start - 1, end
if lo < 0 or hi > len(pin):
return AnchorResult("GONE", None, None, [], "cited range ends past the end of the file")
if lo < 0 or lo >= len(pin):
return AnchorResult("GONE", None, None, [])
if not any(pin[lo:hi]):
return AnchorResult("CHANGED", None, None, [], "cited range is blank")
@@ -915,22 +892,17 @@ def anchor_part(
reason = "written against HEAD, pin names an older revision"
return PartResult(part, "GONE", None, None, None, [], reason, slug, url)
end = part.last
if end > len(pin_lines):
if part.start > len(pin_lines) and _written_against_head(part, head_lines):
# The file is there and the line is not yet: the pinned revision is
# shorter than the one the ref was written against. nestopia cited
# the palette and database loads at 2041 and 2063, which is where
# HEAD carries them, over a pin four hundred lines shorter. A range
# that only ends past the file is no better: dolphin cited 239-323 in
# a 317-line NANDImporter.cpp, and the block found above the end
# anchored it.
reason = (
"written against HEAD, pin names an older revision"
if _written_against_head(part, head_lines)
else "cited range ends past the end of the file"
# HEAD carries them, over a pin four hundred lines shorter.
return PartResult(
part, "GONE", None, None, None, [],
"written against HEAD, pin names an older revision", slug, url,
)
return PartResult(part, "GONE", None, None, None, [], reason, slug, url)
end = part.end or part.start
anchored = anchor_block(pin_lines, head_lines, part.start, end)
note = anchored.reason
if anchored.status in REVIEW_STATUSES:
@@ -1178,7 +1150,7 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
)
if part.start is None:
return PartResult(part, "ANCHORED", None, None, None, [])
if part.last > len(pin_lines):
if part.start > len(pin_lines):
return PartResult(
part, "GONE", None, None, None, [], "beyond the end of the file"
)
@@ -1195,7 +1167,7 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
# per line, so the window reaches forward as far as the entry has members.
reach = SELF_CHECK_CONTEXT + len(tokens)
lo = max(0, part.start - 1 - SELF_CHECK_CONTEXT)
hi = min(len(pin_lines), part.last + reach)
hi = min(len(pin_lines), (part.end or part.start) + reach)
window = "\n".join(pin_lines[lo:hi]).lower()
if any(token in window for token in tokens):
return PartResult(part, "ANCHORED", None, None, None, [])
@@ -1211,8 +1183,8 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
{
index
for index, line in enumerate(pin_lines, 1)
for token in hash_tokens
if token.lower() in line.lower()
for token in tokens
if token in line.lower()
}
)
if not elsewhere:
@@ -1698,26 +1670,12 @@ def build_report(
view, _ = resolve_path(part.path, part.start, tuple(tokens))
return view.pin == view.head
def judge(part, flag, tokens, hashes) -> PartResult:
other = cites_another_version(part, profile.get("core_version"))
if other:
# ti99sim cites v0.0.11 beside its v0.16.0 pin: no revision of
# the pinned repository holds that program, so it cannot anchor.
return PartResult(
part, "EXTERNAL", None, None, None, [],
f"annotated for version {other}, not the documented one",
)
if flag:
return verify_at_pin(
part, fetch(PIN, part.path, part.start, tokens), tokens, hashes
)
return anchor_across_views(part, tokens)
staged = []
for entry_name, ref, tokens, hashes, ref_parts, citation in refs:
flags = [self_checked(part, tokens) for part in ref_parts]
judged = [
judge(part, flag, tokens, hashes)
verify_at_pin(part, fetch(PIN, part.path, part.start, tokens), tokens, hashes)
if flag else anchor_across_views(part, tokens)
for part, flag in zip(ref_parts, flags)
]
staged.append((entry_name, ref, citation, reconcile_self_check(judged, flags)))
@@ -2501,15 +2459,6 @@ def bump_commit(
if pending_recale(report, accept_changed, text):
return False
document = yaml.safe_load(text)
if any(isinstance(document.get(f), dict) for f in ("source_commit", "upstream_commit")):
# A pin per build mode names one revision per repository, and the
# report heads only the primary one: rewriting str(dict) was a
# refused write on mesence, supermodel and xemu.
print(
f"{path.stem}: pins keyed by build mode are advanced by hand",
file=sys.stderr,
)
return False
upstream_moves = bool(
report.upstream_head and report.upstream_pin != report.upstream_head
)
@@ -2517,6 +2466,9 @@ def bump_commit(
# Rewriting the pin to the value it already holds is not a bump, and
# announcing it buries the profiles that did move.
return False
if isinstance(document.get("upstream_commit"), dict):
# A pin per build mode names no single upstream revision to advance.
return False
expected = dict(document)
expected["source_commit"] = report.head
new_text = _set_pin(text, document, "source_commit", report.head, "profiled_date")
@@ -2670,14 +2622,10 @@ def _realign_part(
def _writing_pairs(
document: dict, repos: list, revisions: list[tuple[str, dict]], intro_sha: str
) -> tuple[list[list], list[str]]:
"""Every reading of where the text was written, and the ambiguities.
Each reading lists (repo, writing pin, current pin) per declared
repository. A repository whose text lived under several pins gives one
reading per pin; the caller settles them when they all agree.
"""
options: list[list] = []
) -> tuple[list, list[str]]:
"""(repo, writing pin, current pin) per declared repository, and the
repositories whose writing pin the history cannot single out."""
pairs = []
ambiguous: list[str] = []
for pin_field, repo in repos:
current = document.get(f"{pin_field}_commit")
@@ -2686,58 +2634,9 @@ def _writing_pairs(
written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit")
if len(written) > 1:
ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}")
# The current pin among the candidates reads as "no move" (None) for
# that repository, which the other candidates must then agree with.
options.append([
(repo, pin, current) if pin != current else None for pin in written
])
readings = [
[pair for pair in combo if pair is not None]
for combo in itertools.product(*options)
]
return readings, ambiguous
def _realign_citation(
citation: Citation,
readings: list[list],
ambiguous: list[str],
cache_dir: str,
offline: bool,
) -> tuple[dict[int, tuple[int, int, str | None]], list[str]]:
"""The moves one prose run needs, and what blocks them.
Several pins under one text: the history cannot say which the text
describes, but when the cited range lands in the same place from every
one of them the question does not matter. When the readings disagree,
moving from the wrong one rewrites a correct citation onto someone
else's code, with nothing to show it happened.
"""
moves: dict[int, tuple[int, int, str | None]] = {}
blocked: list[str] = []
for index, part in enumerate(citation.parts):
outcomes = {
repr(result): result
for result in (
_realign_part(part, pairs, cache_dir, offline) if pairs else None
for pairs in readings
)
}
if len(outcomes) > 1:
blocked.extend(
f"pin moved under this text: {item}; the readings disagree"
for item in ambiguous
)
continue
outcome = next(iter(outcomes.values()))
if outcome is None:
continue
state, payload = outcome
if state == "ok":
moves[index] = (payload[0], payload[1], None)
else:
blocked.append(payload)
return moves, blocked
elif written and written[0] != current:
pairs.append((repo, written[0], current))
return pairs, ambiguous
def realign_prose(
@@ -2798,10 +2697,30 @@ def realign_prose(
if intro_sha is None:
# The scalar is not committed yet: written now, under this pin.
continue
readings, ambiguous = _writing_pairs(document, repos, revisions, intro_sha)
if not any(readings):
pairs, ambiguous = _writing_pairs(document, repos, revisions, intro_sha)
if ambiguous:
# Moving from the wrong one rewrites a correct citation onto
# someone else's code, with nothing to show it happened.
messages.extend(
f"read again: {citation.field}: {citation.ref} (pin moved under "
f"this text: {item}; the history does not say which it describes)"
for item in ambiguous
)
continue
moves, blocked = _realign_citation(citation, readings, ambiguous, cache_dir, offline)
if not pairs:
continue
moves: dict[int, tuple[int, int, str | None]] = {}
blocked: list[str] = []
for index, part in enumerate(citation.parts):
outcome = _realign_part(part, pairs, cache_dir, offline)
if outcome is None:
continue
state, payload = outcome
if state == "ok":
start, end = payload
moves[index] = (start, end, None)
else:
blocked.append(payload)
if blocked:
# Half a run must not move: the untouched ranges would read as
# already realigned when they were never even located.
@@ -2828,39 +2747,14 @@ def realign_prose(
return messages
def _refuse_dirty_tree(emulators_dir: str) -> None:
"""Writes need a clean profile tree to roll back to."""
try:
dirty = emulators_dir_is_dirty(emulators_dir)
except RuntimeError as e:
print(f"{e}. Pass --force to write anyway.", file=sys.stderr)
raise SystemExit(1) from e
if dirty:
print(
f"{emulators_dir} carries uncommitted changes. "
"Commit them first or pass --force.",
file=sys.stderr,
)
raise SystemExit(1)
def emulators_dir_is_dirty(emulators_dir: str) -> bool:
"""True when the profile directory carries uncommitted changes.
Git is asked from inside the directory, so a profile tree that lives in
another repository answers for itself. A directory git cannot read is
never reported clean: the writes have nothing to roll back to.
"""
"""True when the profile directory carries uncommitted changes."""
result = subprocess.run(
["git", "-C", emulators_dir, "status", "--porcelain", "--", "."],
["git", "status", "--porcelain", "--", emulators_dir],
capture_output=True,
text=True,
check=False,
)
if result.returncode:
raise RuntimeError(
f"cannot read the git status of {emulators_dir}: {result.stderr.strip()}"
)
return bool(result.stdout.strip())
@@ -2962,40 +2856,16 @@ def _check_quota(count: int, offline: bool) -> None:
raise SystemExit(1)
def _stale_prose_blocks(name: str, path: Path, args) -> bool:
"""Whether prose written under an older pin has to move first.
Anchored from the current pin, a recale or a bump follows someone else's
code (boom3 FileSystem.cpp:2125, written at b810234e, an unrelated
OpenFileRead at 132dfddb). It moves first, from the pin it was written at.
"""
stale = realign_prose(path, args.cache_dir, args.offline, dry_run=True)
if stale:
print(
f"{name}: {len(stale)} prose citation(s) written under an older pin; "
"run --realign-prose or read them before the pin moves",
file=sys.stderr,
)
return bool(stale)
def _backfill(args, name: str, profile: dict, report: ProfileReport, path: Path) -> None:
"""Write the pin a profile without source_commit was written at."""
if not report.pin or profile.get("source_commit"):
return
if args.dry_run:
print(f"{name}: would write source_commit {report.pin[:7]}")
elif backfill_commit(path, report.pin):
print(f"{name}: source_commit {report.pin[:7]}")
def _apply_writes(args, name: str, profile: dict, report: ProfileReport) -> None:
path = Path(args.emulators_dir) / f"{name}.yml"
if not path.is_file():
return
if args.backfill_commits:
_backfill(args, name, profile, report, path)
if not (args.rebase_refs or args.bump_commit) or _stale_prose_blocks(name, path, args):
if args.backfill_commits and report.pin and not profile.get("source_commit"):
if args.dry_run:
print(f"{name}: would write source_commit {report.pin[:7]}")
elif backfill_commit(path, report.pin):
print(f"{name}: source_commit {report.pin[:7]}")
if not (args.rebase_refs or args.bump_commit):
return
with tempfile.TemporaryDirectory() as scratch:
# Always work on a copy. A dry run reports what it left there; a real
@@ -3253,8 +3123,18 @@ def main() -> None:
args.backfill_commits or args.rebase_refs or args.bump_commit
or args.realign_prose
)
if writes and not args.dry_run and not args.force:
_refuse_dirty_tree(args.emulators_dir)
if (
writes
and not args.dry_run
and not args.force
and emulators_dir_is_dirty(args.emulators_dir)
):
print(
f"{args.emulators_dir} carries uncommitted changes. "
"Commit them first or pass --force.",
file=sys.stderr,
)
raise SystemExit(1)
if args.realign_prose:
for name in selected:
-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
+5 -14
View File
@@ -255,25 +255,16 @@ def fallback_groups(
index: dict[str, dict],
requested: list[str],
) -> list[str]:
"""Group IDs where no candidate matched, so the whole group was kept.
A world file, or one that declares no region at all, answers every
region: the C64 kernal VICE loads by default is untagged, and a group
holding it beside a Japanese and a Swedish kernal does have a BIOS for
North America.
"""
"""Group IDs where no candidate matched, so the whole group was kept."""
if not requested:
return []
out: list[str] = []
for group_id, members in groups.items():
ranked = _competing_ranks(members, index, requested)
served = any(
not regions or WORLD in regions
for regions in (
lookup_regions(index, destination, name)
for destination, name in members
)
has_world = any(
WORLD in lookup_regions(index, destination, name)
for destination, name in members
)
if ranked and not served and min(r for r, _ in ranked) == len(requested):
if ranked and not has_world and min(r for r, _ in ranked) == len(requested):
out.append(group_id)
return sorted(out)
+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")
+13 -30
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import json
import os
import sys
import urllib.error
import urllib.request
@@ -226,38 +225,22 @@ class BaseScraper(ABC):
...
def github_headers() -> dict[str, str]:
"""Headers for api.github.com, authenticated when a token is set.
Anonymous calls share a quota of 60 per hour, so a scrape run after a
few others gets 403 for every request.
"""
headers = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
def fetch_github_latest_version(repo: str) -> str:
"""Return the tag of the latest release of a GitHub repo.
Raises instead of returning a fallback: a platform YAML written without
the release it was read from cannot be patched back by the exporter.
"""
def fetch_github_latest_version(repo: str) -> str | None:
"""Fetch the latest release version tag from a GitHub repo."""
url = f"https://api.github.com/repos/{repo}/releases/latest"
try:
req = urllib.request.Request(url, headers=github_headers())
req = urllib.request.Request(
url,
headers={
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
},
)
with urllib.request.urlopen(req, timeout=15) as resp:
tag = json.loads(resp.read()).get("tag_name", "")
except (urllib.error.URLError, json.JSONDecodeError) as e:
raise RuntimeError(f"cannot read the latest release of {repo}: {e}") from e
if not tag:
raise RuntimeError(f"the latest release of {repo} names no tag")
return tag
data = json.loads(resp.read())
return data.get("tag_name", "")
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError):
return None
class _PlatformDumper(yaml.SafeDumper):
+41 -28
View File
@@ -19,12 +19,7 @@ from pathlib import Path
from common import yaml_load
from .base_scraper import (
BaseScraper,
BiosRequirement,
github_headers,
requirement_entry,
)
from .base_scraper import BaseScraper, BiosRequirement, requirement_entry
PLATFORM_NAME = "batocera"
@@ -43,32 +38,39 @@ def pick_stable_tag(names: list[str]) -> str | None:
return max(stable)[1] if stable else None
def fetch_stable_tag() -> str:
"""Return the newest stable batocera-N(.M) tag name.
def fetch_stable_tag() -> str | None:
"""Return the newest stable batocera-N(.M) tag name."""
import json
import os
import urllib.error
import urllib.request
Raises rather than falling back to master: the YAML records the tag
it was read from, and the exporter patches that revision.
"""
url = "https://api.github.com/repos/batocera-linux/batocera.linux/tags?per_page=100"
headers = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
try:
req = urllib.request.Request(url, headers=github_headers())
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=15) as resp:
tags = json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError) as e:
raise RuntimeError(f"cannot list batocera tags: {e}") from e
tag = pick_stable_tag([tag["name"] for tag in tags])
if not tag:
raise RuntimeError("no stable batocera tag among the latest 100")
return tag
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError):
return None
return pick_stable_tag([tag["name"] for tag in tags])
_STABLE_TAG = fetch_stable_tag() or "master"
SOURCE_URL = (
_RAW_BASE + "/{tag}"
f"{_RAW_BASE}/{_STABLE_TAG}"
"/package/batocera/core/batocera-scripts/scripts/batocera-systems"
)
CONFIGGEN_DEFAULTS_URL = (
_RAW_BASE + "/{tag}"
f"{_RAW_BASE}/{_STABLE_TAG}"
"/package/batocera/core/batocera-configgen/configs/"
"configgen-defaults.yml"
)
@@ -184,9 +186,8 @@ def _resolve_truncated_md5(md5: str, md5_index: dict[str, str]) -> str:
class Scraper(BaseScraper):
"""Scraper for batocera-systems Python dict."""
def __init__(self):
self.tag = fetch_stable_tag()
super().__init__(url=SOURCE_URL.format(tag=self.tag))
def __init__(self, url: str = SOURCE_URL):
super().__init__(url=url)
def _fetch_cores(self) -> tuple[list[str], list[str]]:
"""Extract core names and standalone cores from configgen-defaults.yml.
@@ -194,17 +195,16 @@ class Scraper(BaseScraper):
Returns (all_cores, standalone_cores) where standalone_cores are
those with emulator != "libretro".
"""
url = CONFIGGEN_DEFAULTS_URL.format(tag=self.tag)
try:
req = urllib.request.Request(
url,
CONFIGGEN_DEFAULTS_URL,
headers={"User-Agent": "retrobios-scraper/1.0"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read().decode("utf-8")
except urllib.error.URLError as e:
raise ConnectionError(
f"Failed to fetch {url}: {e}"
f"Failed to fetch {CONFIGGEN_DEFAULTS_URL}: {e}"
) from e
data = yaml_load(raw)
cores: set[str] = set()
@@ -372,12 +372,25 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req))
batocera_version = ""
if _STABLE_TAG != "master":
batocera_version = _STABLE_TAG.removeprefix("batocera-")
if not batocera_version:
# Preserve existing version when fetch fails (offline mode)
existing = (
Path(__file__).resolve().parents[2] / "platforms" / "batocera.yml"
)
if existing.exists():
with open(existing) as f:
old = yaml_load(f) or {}
batocera_version = str(old.get("version", ""))
cores, standalone = self._fetch_cores()
result = {
"platform": "Batocera",
"version": self.tag.removeprefix("batocera-"),
"version": batocera_version or "",
"homepage": "https://batocera.org",
"source": self.url,
"source": SOURCE_URL,
"base_destination": "bios",
"hash_type": "md5",
"verification_mode": "md5",
+8 -5
View File
@@ -41,8 +41,10 @@ PLATFORM_NAME = "bizhawk"
GITHUB_REPO = "TASEmulators/BizHawk"
_STABLE_TAG = fetch_github_latest_version(GITHUB_REPO) or "master"
SOURCE_URL = (
"https://raw.githubusercontent.com/TASEmulators/BizHawk/{tag}"
f"https://raw.githubusercontent.com/TASEmulators/BizHawk/{_STABLE_TAG}"
"/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs"
)
@@ -330,8 +332,7 @@ class Scraper(BaseScraper):
"""BizHawk firmware database scraper."""
def __init__(self):
self.tag = fetch_github_latest_version(GITHUB_REPO)
super().__init__(url=SOURCE_URL.format(tag=self.tag))
super().__init__(url=SOURCE_URL)
def validate_format(self, raw_data: str) -> bool:
return "FirmwareDatabase" in raw_data and "FirmwareAndOption" in raw_data
@@ -371,11 +372,13 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req))
version = _STABLE_TAG if _STABLE_TAG != "master" else ""
return {
"platform": "BizHawk",
"version": self.tag,
"version": version,
"homepage": "https://tasvideos.org/BizHawk",
"source": self.url,
"source": SOURCE_URL,
"base_destination": "Firmware",
"hash_type": "sha1",
"verification_mode": "sha1",
+1 -1
View File
@@ -295,7 +295,7 @@ class Scraper(BaseScraper):
def fetch_metadata(self) -> dict:
"""Fetch version info from GitHub."""
version = fetch_github_latest_version("libretro/libretro-core-info")
return {"version": version}
return {"version": version or ""}
def main():
+15 -11
View File
@@ -22,7 +22,6 @@ try:
BaseScraper,
BiosRequirement,
fetch_github_latest_version,
github_headers,
requirement_entry,
)
except ImportError:
@@ -30,7 +29,6 @@ except ImportError:
BaseScraper,
BiosRequirement,
fetch_github_latest_version,
github_headers,
requirement_entry,
)
@@ -428,7 +426,13 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req))
version = fetch_github_latest_version("dragoonDorise/EmuDeck")
version = ""
try:
v = fetch_github_latest_version("dragoonDorise/EmuDeck")
if v:
version = v
except (ConnectionError, ValueError, OSError):
pass
cores = self._fetch_installed_emulators()
@@ -464,15 +468,17 @@ class Scraper(BaseScraper):
skip = {"retroarch_maincfg", "retroarch"}
try:
req = urllib.request.Request(api_url, headers=github_headers())
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError) as e:
raise RuntimeError(f"cannot list EmuDeck EmuScripts: {e}") from e
req = urllib.request.Request(
api_url,
headers={"User-Agent": "retrobios-scraper/1.0"},
)
data = json.loads(urllib.request.urlopen(req, timeout=30).read())
except (urllib.error.URLError, OSError):
return []
cores: list[str] = []
seen: set[str] = set()
for entry in data if isinstance(data, list) else []:
for entry in data:
name = entry.get("name", "")
if not name.endswith(".sh"):
continue
@@ -487,8 +493,6 @@ class Scraper(BaseScraper):
if core not in seen:
seen.add(core)
cores.append(core)
if not cores:
raise RuntimeError(f"no emulator script in {api_url}")
return sorted(cores)
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff. Show more