94 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
140 changed files with 11210 additions and 4116 deletions

No files matched your search

+40 -5
View File
@@ -8,11 +8,47 @@ on:
- "emulators/**"
- "provenance/**"
- "wiki/**"
- "scripts/generate_site.py"
- "scripts/generate_readme.py"
- "scripts/validate_site.py"
- "scripts/verify.py"
# Every script the build runs or imports (tests/test_workflow_paths.py).
- "scripts/artifacts.py"
- "scripts/auto_fetch.py"
- "scripts/common.py"
- "scripts/cross_reference.py"
- "scripts/crypto_verify.py"
- "scripts/deterministic_zip.py"
- "scripts/dumpcatalog.py"
- "scripts/generate_pack.py"
- "scripts/generate_readme.py"
- "scripts/generate_site.py"
- "scripts/hashing.py"
- "scripts/largefiles.py"
- "scripts/list_platforms.py"
- "scripts/nativemode.py"
- "scripts/packextras.py"
- "scripts/packpaths.py"
- "scripts/packreadme.py"
- "scripts/packresolve.py"
- "scripts/packverify.py"
- "scripts/profile_sync.py"
- "scripts/provenance_report.py"
- "scripts/refresh_data_dirs.py"
- "scripts/region.py"
- "scripts/release_record.py"
- "scripts/restore_large_files.py"
- "scripts/safeparse.py"
- "scripts/sect233r1.py"
- "scripts/siterender.py"
- "scripts/sitesources.py"
- "scripts/sitewrite.py"
- "scripts/slot.py"
- "scripts/slots.py"
- "scripts/split_pack.py"
- "scripts/truth.py"
- "scripts/upstream.py"
- "scripts/validate_schemas.py"
- "scripts/validate_site.py"
- "scripts/validation.py"
- "scripts/verify.py"
- "scripts/ziptools.py"
- "schemas/**"
- "tests/**"
- "docs_assets/**"
@@ -20,7 +56,6 @@ on:
- "install.py"
- "database.json"
- "release.json"
- "scripts/release_record.py"
- "mkdocs.yml"
- ".github/workflows/deploy-site.yml"
workflow_dispatch:
+9 -9
View File
@@ -72,9 +72,9 @@ Each file is checked the way your platform checks it. Most compare a checksum, t
- **12 platforms** supported with platform-specific verification
- **506 emulators** profiled from source (RetroArch cores + standalone)
- **546 systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **18,988 files in the collection**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 5,258 system files, 2,810 arcade ROM sets, 10,920 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,258 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index
- **16670 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,16 +86,16 @@ 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,274/12,704 | MD5 hash |
| 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,700/5,730 | MD5 hash |
| Recalbox | 351/351 | 2,727/2,735 | 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,748/12,178 | MD5 hash |
| RetroDECK | 2,008/2,008 | 5,280/5,330 | MD5 hash |
| 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 |
@@ -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-05T23:10:58Z*
*Auto-generated on 2026-10-06T03:31:49Z*
File renamed without changes.
File diff suppressed because it is too large. Load diff
Binary file not shown.
+913 -52
View File
File diff suppressed because it is too large. Load diff
+10 -8
View File
@@ -110,7 +110,8 @@ files:
md5: 5daed103918277e2065dd7e8d23e57a5
sha1: 7e3c536baeae84d6498a14e8405319e01ee78232
max_size: 131072
source_ref: "Src/RomConfigFile.cpp:45, Src/BeebMem.cpp:1322-1356"
note: "Paged ROM slot, read by the BeebReadRoms bank loop (Src/BeebMem.cpp:1322-1356)."
source_ref: "Src/RomConfigFile.cpp:45"
- name: OS12.map
path: BeebFile/BBC/OS12.map
@@ -293,8 +294,8 @@ files:
md5: 9087f772447038a00178488b3591347b
sha1: 04d5876999f93fc61a18f172b1fbb3b75db733ac
validation: [size]
note: "OS ROM slot for Model::Master128."
source_ref: "Src/RomConfigFile.cpp:103, Src/BeebMem.cpp:1291-1308"
note: "OS ROM slot for Model::Master128, read into 0xC000 by BeebReadRoms (Src/BeebMem.cpp:1291-1308)."
source_ref: "Src/RomConfigFile.cpp:103"
- name: Terminal.rom
path: BeebFile/M128/Terminal.rom
@@ -550,8 +551,8 @@ files:
validation: [size]
note: >
512 KB on-board flash carrying the system software and BASIC. Rejected as
InvalidROM unless the file is exactly 0x80000 bytes.
source_ref: "Src/SprowCoPro.cpp:51-90, Src/SprowCoPro.h:49, Src/BeebWin.cpp:1058-1088"
InvalidROM unless the file is exactly 0x80000 bytes (Src/SprowCoPro.h:49).
source_ref: "Src/SprowCoPro.cpp:51-90, Src/BeebWin.cpp:1058-1088"
# -- Program directory files --
@@ -685,8 +686,8 @@ files:
size: 6716
md5: 07b0e1d4cb329185a8a52c48db00bcf9
sha1: 5817277bcb110b747432b8716a0ffbe0c1e13de2
note: "Read at ApplyPreferences. The return value is ignored, leaving the built-in map."
source_ref: "Src/BeebWin.cpp:560-563, Src/KeyMap.cpp:65-78"
note: "Read at ApplyPreferences by ReadKeyMap (Src/KeyMap.cpp:65-123). The return value is ignored, leaving the built-in map."
source_ref: "Src/BeebWin.cpp:560-563"
- name: Default.kmap
description: "Default keyboard mapping"
@@ -696,7 +697,8 @@ files:
size: 6716
md5: 199de64bdb59a176d102f03cca4eadf9
sha1: 63c72e11f6ce8cd9dc5ebb96b4938010f4b2e4f0
source_ref: "Src/BeebWin.cpp:565-567, Src/KeyMap.cpp:65-78"
note: "Parsed by ReadKeyMap (Src/KeyMap.cpp:65-123)."
source_ref: "Src/BeebWin.cpp:565-567"
- name: DefaultUser.kmap
description: "Fallback user keyboard mapping"
-6
View File
@@ -23,7 +23,6 @@ notes: >
files:
- name: "syscard3.pce"
description: "CD-ROM System Card 3.0 (Japan)"
region: [japan]
required: true
sha1: "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c"
md5: "38179df8f4ac870017db21ebcbf53114"
@@ -32,7 +31,6 @@ files:
- name: "syscard3u.pce"
description: "CD-ROM System Card 3.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false
sha1: "d02611d99921986147c753df14c7349b31d71950"
md5: "0754f903b52e3b3342202bdafb13efa5"
@@ -41,7 +39,6 @@ files:
- name: "syscard2.pce"
description: "CD-ROM System Card 2.0 (Japan)"
region: [japan]
required: false
sha1: "88da02e2503f7c32810f5d93a34849d470742b6d"
md5: "3cdd6614a918616bfc41c862e889dd79"
@@ -50,7 +47,6 @@ files:
- name: "syscard2u.pce"
description: "CD-ROM System Card 2.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false
sha1: "2bea3dac98f84b2f2f469fa77ea720b8770d598d"
md5: "94279f315e8b52904f65ab3108542afe"
@@ -59,7 +55,6 @@ files:
- name: "syscard1.pce"
description: "CD-ROM System Card 1.0 (Japan)"
region: [japan]
required: false
sha1: "a39a66da7de6ba94ab84d04eef7afeec7d4ee66a"
md5: "2b7ccb3d86baa18f6402c176f3065082"
@@ -68,7 +63,6 @@ files:
- name: "gexpress.pce"
description: "Games Express CD Card"
region: [japan]
required: false
sha1: "014881a959e045e00f4db8f52955200865d40280"
md5: "6d2cb14fc3e1f65ceb135633d1694122"
+1 -1
View File
@@ -81,7 +81,7 @@ files:
- name: "scph5502.bin"
description: "SCPH-5502, 5552 (v3.0 01-06-97 E)"
region: [europe]
region: [europe, oceania]
required: true
hle_fallback: true
size: 524288
+1 -1
View File
@@ -90,7 +90,7 @@ files:
md5: 25629dfe870d097469c217b95fdc1c95
sha1: 39102c8e9cb55fcc0b9b62098780ed4a3cb6a4bb
note: "AMSDOS disc ROM, shipped cap32.cfg sets slot07=DEFAULT, skipped on the 464; missing file clears the slot"
source_ref: "src/cap32.cpp:1170-1178, src/cap32.cpp:1245-1249, cap32.cfg:215, rom/amsdos.rom"
source_ref: "src/cap32.cpp:1170-1178, src/cap32.cpp:1245-1249, cap32.cfg:205-206, 215, rom/amsdos.rom"
- name: system.cpr
system: amstrad-cpc
required: false
-5
View File
@@ -169,7 +169,6 @@ files:
- name: CPC6128.ROM.UK
path: "ROM/National/CPC6128.ROM.UK"
system: amstrad-cpc
region: [uk]
required: false
min_size: 32768
validation: [size, signature]
@@ -183,7 +182,6 @@ files:
- name: CPC6128.ROM.FR
path: "ROM/National/CPC6128.ROM.FR"
system: amstrad-cpc
region: [france]
required: false
min_size: 32768
validation: [size, signature]
@@ -195,7 +193,6 @@ files:
- name: CPC6128.ROM.ES
path: "ROM/National/CPC6128.ROM.ES"
system: amstrad-cpc
region: [spain]
required: false
min_size: 32768
validation: [size, signature]
@@ -207,7 +204,6 @@ files:
- name: CPC6128.ROM.DK
path: "ROM/National/CPC6128.ROM.DK"
system: amstrad-cpc
region: [denmark]
required: false
min_size: 32768
validation: [size, signature]
@@ -219,7 +215,6 @@ files:
- name: CPC6128.ROM.SE
path: "ROM/National/CPC6128.ROM.SE"
system: amstrad-cpc
region: [sweden]
required: false
min_size: 32768
validation: [size, signature]
+3 -2
View File
@@ -84,7 +84,8 @@ files:
sha1: "69b3544d992a23e8b736c61837914a4d1b4e7bb5"
description: "Gemei A330 firmware update archive, V1.1 build3 of 2011-02-13"
note: >-
Mounted when an A330 package loads: the content's directory is
Mounted when an A330 package loads
(crates/dingooemu-core/src/a330/runtime.rs:415-418): the content's directory is
scanned and the first entry whose extension is hxf, in any case, is
read whole. The file must start with WADF0100 and declare at offset
20 a size within its own length; each record is a u32 name length,
@@ -98,4 +99,4 @@ files:
goes on. The image itself is never hashed, and the hashes here name
the last firmware Gemei published; the V1.10 image of 2011-01-25
indexes the same way.
source_ref: "crates/dingooemu-core/src/a330/runtime.rs:415-418, crates/dingooemu-core/src/a330/firmware_archive.rs:16-50, 54-58, 63-96, 117-124, crates/dingooemu-core/src/a330/runtime/sdk_hle/files.rs:108-133"
source_ref: "crates/dingooemu-core/src/a330/firmware_archive.rs:16-50, 54-58, 63-96, 117-124, crates/dingooemu-core/src/a330/runtime/sdk_hle/files.rs:108-133"
+1 -1
View File
@@ -188,7 +188,7 @@ files:
adler32_byteswap: true
description: "DSP coefficient ROM"
note: "Read by DSP LLE alongside the instruction ROM, and by the AX ucode under HLE for polyphase resampling, which drops any file that is not 0x1000 bytes and plays on without it."
source_ref: "Source/Core/Common/CommonPaths.h:121, Source/Core/Core/HW/DSPLLE/DSPLLE.cpp:87-105, Source/Core/Core/HW/DSPLLE/DSPLLE.cpp:107-120, Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp:42-69, Source/Core/Core/DSP/DSPCore.h:58"
source_ref: "Source/Core/Common/CommonPaths.h:121, Source/Core/Core/HW/DSPLLE/DSPLLE.cpp:87-120, Source/Core/Core/HW/DSPHLE/UCodes/AX.cpp:42-69, Source/Core/Core/DSP/DSPCore.h:58"
- name: codehandler.bin
path: null
+30 -30
View File
@@ -67,7 +67,7 @@ files:
- name: "scph1002a.bin"
description: "SCPH-1002, DTL-H1002 (v2.0 05-10-95 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "54847e693405ffeb0359c6287434cbef"
region_check: false
@@ -77,7 +77,7 @@ files:
- name: "scph1002b.bin"
description: "SCPH-1002, DTL-H1102 (v2.1 07-17-95 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "417b34706319da7cf001e76e40136c23"
region_check: false
@@ -87,7 +87,7 @@ files:
- name: "scph1002c.bin"
description: "SCPH-1002, DTL-H1202, H3002 (v2.2 12-04-95 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "e2110b8a2b97a8e0b857a45d32f7e187"
region_check: false
@@ -177,7 +177,7 @@ files:
- name: "scph5502.bin"
description: "SCPH-5502, 5552 (v3.0 01-06-97 E)"
region: [europe]
region: [europe, oceania]
required: true
md5: "32736f17079d0b2b7024407c39bd3050"
region_check: false
@@ -217,7 +217,7 @@ files:
- name: "scph7002.bin"
description: "SCPH-7002, 7502, 9002 (v4.1 12-16-97 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "b9d9a0286c33dc6b7237bb13cd46fdee"
region_check: false
@@ -257,7 +257,7 @@ files:
- name: "scph102_v44.bin"
description: "SCPH-102 (v4.4 03-24-00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "b10f5e0e3d9eb60e5159690680b1e774"
region_check: true
@@ -267,7 +267,7 @@ files:
- name: "scph102_v45.bin"
description: "SCPH-102 (v4.5 05-25-00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "de93caec13d1a141a40a79f5c86168d6"
region_check: true
@@ -301,7 +301,7 @@ files:
- name: "ps2_scph30003.bin"
description: "PS2, SCPH-30003 (v5.0 09-02-00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "71f50ef4f4e17c163c78908e16244f7d"
region_check: true
@@ -391,7 +391,7 @@ files:
- name: "ps2_dtlh30002.bin"
description: "PS2, DTL-H30002 (v5.0 09/02/00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "91c87cb2f2eb6ce529a2360f80ce2457"
region_check: true
@@ -401,7 +401,7 @@ files:
- name: "ps2_dtlh30102_0902.bin"
description: "PS2, DTL-H30102 (v5.0 09/02/00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "3016b3dd42148a67e2c048595ca4d7ce"
region_check: true
@@ -411,7 +411,7 @@ files:
- name: "ps2_scph30002_0902.bin"
description: "PS2, SCPH-30002/SCPH-30003/SCPH-30004 (v5.0 09/02/00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "b7fa11e87d51752a98b38e3e691cbf17"
region_check: true
@@ -461,7 +461,7 @@ files:
- name: "ps2_dtlh30102_1228.bin"
description: "PS2, DTL-H30102 (v5.0 12/28/00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "6f9a6feb749f0533aaae2cc45090b0ed"
region_check: true
@@ -471,7 +471,7 @@ files:
- name: "ps2_scph30002_1228.bin"
description: "PS2, SCPH-30002/SCPH-30003/SCPH-30004/SCHP-35002/SCPH-35003/SCPH-35004 (v5.0 12/28/00 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "838544f12de9b0abc90811279ee223c8"
region_check: true
@@ -531,7 +531,7 @@ files:
- name: "ps2_scph30002r_0704.bin"
description: "PS2, SCPH-30002R/SCPH-30003R/SCPH-30004R (v5.0 07/04/01 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "491209dd815ceee9de02dbbc408c06d6"
region_check: true
@@ -551,7 +551,7 @@ files:
- name: "ps2_scph30002r_1004.bin"
description: "PS2, SCPH-30002R/SCPH-30003R/SCPH-30004R (v5.0 10/04/01 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "8359638e857c8bc18c3c18ac17d9cc3c"
region_check: true
@@ -581,7 +581,7 @@ files:
- name: "ps2_scph39002.bin"
description: "PS2, SCPH-39002/SCPH-39003/SCPH-39004 (v5.0 03/19/02 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "0d2228e6fd4fb639c9c39d077a9ec10c"
region_check: true
@@ -601,7 +601,7 @@ files:
- name: "ps2_scph39008.bin"
description: "PS2, SCPH-39008 (v5.0 04/26/02 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "5b1f47fbeb277c6be2fccdd6344ff2fd"
region_check: true
@@ -641,7 +641,7 @@ files:
- name: "ps2_dtlh50002.bin"
description: "PS2, DTL-H50002 (v5.0 02/27/03 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "666018ffec65c5c7e04796081295c6c7"
region_check: true
@@ -651,7 +651,7 @@ files:
- name: "ps2_scph50002_0227.bin"
description: "PS2, SCPH-50002/SCPH-50003/SCPH-50004 (v5.0 02/27/03 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "6e69920fa6eef8522a1d688a11e41bc6"
region_check: true
@@ -731,7 +731,7 @@ files:
- name: "ps2_scph50002_0623.bin"
description: "PS2, SCPH-50002/SCPH-50003/SCPH-50004 (v5.0 06/23/03 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "bd6415094e1ce9e05daabe85de807666"
region_check: true
@@ -761,7 +761,7 @@ files:
- name: "ps2_scph50008.bin"
description: "PS2, SCPH-50008 (v5.0 06/23/03 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "b53d51edc7fc086685e31b811dc32aad"
region_check: true
@@ -791,7 +791,7 @@ files:
- name: "ps2_scph50004_0822.bin"
description: "PS2, SCPH-50004 (v5.0 08/22/03 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "afde410bd026c16be605a1ae4bd651fd"
region_check: true
@@ -831,7 +831,7 @@ files:
- name: "ps2_scph70002.bin"
description: "PS2, SCPH-70002/SCPH-70003/SCPH-70004/SCPH-70008 (v5.0 06/14/04 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "dc752f160044f2ed5fc1f4964db2a095"
region_check: true
@@ -841,7 +841,7 @@ files:
- name: "ps2_scph70002_alt.bin"
description: "PS2, SCPH-70002 (v5.0 06/14/04 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "7ebb4fc5eab6f79a27d76ac9aad392b2"
region_check: true
@@ -851,7 +851,7 @@ files:
- name: "ps2_dtlh70002.bin"
description: "PS2, DTL-H70002 (v5.0 06/14/04 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "63ead1d74893bf7f36880af81f68a82d"
region_check: true
@@ -921,7 +921,7 @@ files:
- name: "ps2_scph75002.bin"
description: "PS2, SCPH-75002/SCPH-75003/SCPH-75004/SCPH-75008 (v5.0 06/20/05 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "573f7d4a430c32b3cc0fd0c41e104bbd"
region_check: true
@@ -961,7 +961,7 @@ files:
- name: "ps2_scph77002.bin"
description: "PS2, SCPH-77002/SCPH-77003/SCPH-77004/SCPH-77008 (v5.0 02/10/06 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "af60e6d1a939019d55e5b330d24b1c25"
region_check: true
@@ -1021,7 +1021,7 @@ files:
- name: "ps2_scph79002_90002.bin"
description: "PS2, SCPH-79002/SCPH-79003/SCPH-79004/SCPH-79008/SCPH-90002/SCPH-90003/SCPH-90004 (v5.0 09/05/06 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "80bbb237a6af9c611df43b16b930b683"
region_check: true
@@ -1061,7 +1061,7 @@ files:
- name: "ps2_scph90002_0220.bin"
description: "PS2, SCPH-90002/SCPH-90003/SCPH-90004/SCPH-90008 (v5.0 02/20/08 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "dc69f0643a3030aaa4797501b483d6c4"
region_check: true
@@ -1081,7 +1081,7 @@ files:
- name: "ps2_kdl22px300.bin"
description: "PS2, KDL-22PX300 (v5.0 04/15/10 E)"
region: [europe]
region: [europe, oceania]
required: false
md5: "93ea3bcee4252627919175ff1b16a1d9"
region_check: true
+4
View File
@@ -701,6 +701,10 @@ files:
mode: standalone
required: false
bundled: true
size: 408885
sha1: "8c272ae624c56d48d788fafe0397fab2e33d838f"
md5: "b1586eb09b91f65c37a2f5e7c6ef7980"
note: "src/external/SDL_GameControllerDB is a submodule pinned at a113e7fa"
description: "SDL2 controller mapping database handed to SDL_GameControllerAddMappingsFromFile"
source_ref: "src/emu/drivers/src/input/backend/emu_controller_sdl2.cpp:143-145, src/emu/qt/CMakeLists.txt:277"
+2 -2
View File
@@ -5,9 +5,9 @@ source: "https://github.com/sashkinbro/EmuCoreX"
upstream: "https://github.com/PCSX2/pcsx2"
author: "Oleksandr (sashkinbro)"
profiled_date: "2026-10-04"
source_commit: "ed8d7dd721dc8da958f3149acdc4c72d77d5a685"
source_commit: "9884c9f9a0c5d5df2f0c0c064ec41029a4528d72"
upstream_commit: "c10a5c9ad951c517dc48b722b5b84eb9bf7fdb42"
core_version: "0.4.6"
core_version: "0.4.7"
display_name: "Sony - PlayStation 2 (EmuCoreX)"
cores:
- emucorex
+2 -2
View File
@@ -143,7 +143,7 @@ files:
has_builtin: true
size: 32768
note: "EXDOS 1.4 + IS-DOS 1.0 (UK). Used for IS-DOS disk mode instead of exdos13.rom."
source_ref: "core/core.cpp:255, roms/roms.hpp:30104 (built-in data, MD5 verified)"
source_ref: "core/core.cpp:294-298, roms/roms.hpp:30104 (built-in data, MD5 verified)"
# ============================================================
# Enterprise 64/128 - file I/O and extensions
@@ -193,7 +193,7 @@ files:
has_builtin: true
size: 32768
note: "EP-DOS 1.6f. Provides HFONT and CLKOFF for Hungarian locale. 32K across segments 0x06-0x07."
source_ref: "core/core.cpp:238-239, roms/roms.hpp:43780 (built-in data, MD5 verified)"
source_ref: "core/core.cpp:252-256, roms/roms.hpp:43780 (built-in data, MD5 verified)"
- name: brd.rom
path: ep128emu/roms/brd.rom
-2
View File
@@ -221,7 +221,6 @@ files:
source_ref: "fMSX/ROMs/CYRILLIC.FNT, libretro.c:1547"
- name: KOREAN.FNT
region: [south-korea]
required: false
bundled: true
md5: 73291e76638a2f5aa2eceb843df468f4
@@ -229,7 +228,6 @@ files:
source_ref: "fMSX/ROMs/KOREAN.FNT, libretro.c:1547"
- name: JAPANESE.FNT
region: [japan]
required: false
bundled: true
md5: aa28f0703b951a6be8df3e28e20d263d
+7 -7
View File
@@ -5,7 +5,7 @@ source: "https://github.com/libretro/geolith-libretro"
upstream: "https://gitlab.com/jgemu/geolith"
profiled_date: "2026-08-07"
source_commit: "194024931935eff2092e36fc4f8e53e62ed11097"
upstream_commit: "7ed2ef95a446ffb9bf17b6536d24c108310c94da"
upstream_commit: "1bb5a84fbd87dc1f1c510cb3ff81332573e22452"
core_version: "0.4.1"
display_name: "SNK - Neo Geo AES/MVS (Geolith)"
cores:
@@ -52,7 +52,7 @@ notes: |
detection and the optional CD speed hack patches use byte patterns and
apply only when the 68K BIOS is at least 512 KiB (src/geo_cd.c:1888-1910,
1904-1924). Upstream declares the expected archive MD5s through the Jolly
Good API BIOS list (jg.c:228-239); the md5 fields on the archives below
Good API BIOS list (jg.c:220-231); the md5 fields on the archives below
are copied from it.
files:
@@ -64,35 +64,35 @@ files:
required: true
md5: "ad9585c72130c56f04ae26aae87c289d"
note: "Neo Geo AES System ROMs. Opened when System Type is AES (default)."
source_ref: "libretro/libretro.c:1222-1225, jg.c:229-230"
source_ref: "libretro/libretro.c:1222-1225, jg.c:221-222"
- name: "neogeo.zip"
system: snk-neogeo-mvs
required: true
md5: "00dad01abdbf8ea9e79ad2fe11bdb182"
note: "Neo Geo MVS System ROMs. Opened for MVS and Universe BIOS modes."
source_ref: "libretro/libretro.c:1222-1225, jg.c:231-232"
source_ref: "libretro/libretro.c:1222-1225, jg.c:223-224"
- name: "neocd.zip"
system: snk-neogeo-cd
required: false
md5: "006a881532f2caffc3fe9fb5593f2ed5"
note: "Neo Geo CD Front/Top Loader System ROMs. Opened only when CD System is front or top loader."
source_ref: "libretro/libretro.c:1213-1220, jg.c:233-234"
source_ref: "libretro/libretro.c:1213-1220, jg.c:225-226"
- name: "neocdz.zip"
system: snk-neogeo-cd
required: true
md5: "b31a246b69493c35194fe0bac120dc37"
note: "Neo Geo CDZ System ROMs. Opened for CDZ (default) and CD Universe BIOS; also supplies 000-lo.lo to Front/Top Loader mode."
source_ref: "libretro/libretro.c:1213-1239, jg.c:235-236"
source_ref: "libretro/libretro.c:1213-1239, jg.c:227-228"
- name: "irrmaze.zip"
system: snk-neogeo-mvs
required: false
md5: "0e369df30a70e45547cef9e4078dc511"
note: "The Irritating Maze auxiliary BIOS. Loaded in MVS mode for trackball support; on failure the game runs without trackball."
source_ref: "libretro/libretro.c:1281-1292, jg.c:237-238"
source_ref: "libretro/libretro.c:1281-1292, jg.c:229-230"
# -----------------------------------------------------------
# aes.zip members
+9 -7
View File
@@ -238,9 +238,11 @@ files:
description: "SoundFont for the MIDI devices"
note: >-
Collected by scanning a `soundfonts` directory added to the search paths of
the config file; every .sf2 and zipped GUS patch set found there becomes a
menu entry.
source_ref: "Q3E/src/main/jni/zdoom/src/gameconfigfile.cpp:220, 258, Q3E/src/main/jni/zdoom/src/common/audio/music/i_soundfont.cpp:335-370"
the config file (Q3E/src/main/jni/zdoom/src/gameconfigfile.cpp:220, 258);
every .sf2 and zipped GUS patch set found there becomes a menu entry.
fluid_patchset defaults to GAMENAMELOWERCASE, "uzdoom", which
FindSoundFont matches against the file base name.
source_ref: "Q3E/src/main/jni/zdoom/src/version.h:110, Q3E/src/main/jni/zdoom/src/common/audio/music/music_config.cpp:119, Q3E/src/main/jni/zdoom/src/common/audio/music/i_soundfont.cpp:335-370, 458-468"
- name: GENMIDI.GS.wopl
required: false
@@ -280,12 +282,12 @@ files:
description: "ECWolf engine resource archive"
note: >-
Named by MAIN_PK3 and passed to the game selector with the program
directory; failing to open it ends the process with "Could not open
ecwolf.pk3!". It holds the IWADINFO rules that identify which Wolfenstein
directory (Q3E/src/main/jni/ecwolf/src/wl_main.cpp:1294-1307); failing
to open it ends the process with "Could not open ecwolf.pk3!". It holds the IWADINFO rules that identify which Wolfenstein
data set is present, the palettes and the actor definitions. This build
reads it from disk after extraction, unlike the libretro core which
compiles it in.
source_ref: "Q3E/src/main/jni/ecwolf/src/version.h:12, Q3E/src/main/jni/ecwolf/src/wl_iwad.cpp:604, Q3E/src/main/jni/ecwolf/src/wl_main.cpp:1294-1307, Q3E/src/main/java/com/n0n3m4/q3e/Q3EPatchResourceManager.java:186-195"
source_ref: "Q3E/src/main/jni/ecwolf/src/version.h:12, Q3E/src/main/jni/ecwolf/src/wl_iwad.cpp:604, Q3E/src/main/java/com/n0n3m4/q3e/Q3EPatchResourceManager.java:186-195"
- name: glprogs.pk4
required: true
@@ -403,7 +405,7 @@ files:
Unpacked into `files/` under the Source standalone directory. It carries
the DejaVu, Droid Sans Fallback, Itim and Liberation faces the engine
substitutes for the Windows fonts, and extras_dir.vpk.
source_ref: "Q3E/src/main/java/com/n0n3m4/q3e/Q3EPatchResourceManager.java:164-173, Q3E/src/main/java/com/n0n3m4/q3e/Q3EGameHelper.java:938-961, Q3E/src/main/java/com/n0n3m4/q3e/Q3EGameConstants.java:396"
source_ref: "Q3E/src/main/java/com/n0n3m4/q3e/Q3EPatchResourceManager.java:164-173, Q3E/src/main/java/com/n0n3m4/q3e/Q3EGameHelper.java:938-961, Q3E/src/main/java/com/n0n3m4/q3e/Q3EGameConstants.java:383, 396"
- name: d3_sabot_a7.pk4
required: false
+29 -29
View File
@@ -4,7 +4,7 @@ core_classification: game_engine
source: "https://github.com/deathkiller/jazz2-native"
upstream: "https://github.com/deathkiller/jazz2-native"
profiled_date: "2026-10-04"
source_commit: "cc06bdac1b957a069e1d05761c7437cd59886552"
source_commit: "1e20466795d5074570fb057b41f56baefd24c629"
core_version: "3.8.0"
display_name: "Jazz Jackrabbit 2 (Jazz² Resurrection)"
cores: [jazz2]
@@ -15,18 +15,18 @@ notes: |
ships its own Content/ tree (animations, metadata, translations) that
sits next to the executable in a portable copy, or under
<prefix>/share/<package>/ once installed.
ref: Sources/Jazz2/ContentResolver.cpp:187-238 (GetContentPath per
platform), Sources/Jazz2/ContentResolver.cpp:409-485 (Unix layouts)
ref: Sources/Jazz2/ContentResolver.cpp:191-243 (GetContentPath per
platform), Sources/Jazz2/ContentResolver.cpp:415-491 (Unix layouts)
Original Jazz Jackrabbit 2 data is read from Source/ and converted
on first launch into the engine's own formats under Cache/: Anims.j2a
and Data.j2d into a package, then every .j2e, .j2l and the tilesets
the levels name. Music and cinematics are read from Source/ as they
are.
ref: Sources/Jazz2/ContentResolver.cpp:279-309 (GetSourcePath),
Sources/Main.cpp:2224-2288 (RefreshCache),
ref: Sources/Jazz2/ContentResolver.cpp:285-314 (GetSourcePath),
Sources/Main.cpp:2226-2290 (RefreshCache),
Sources/Jazz2/Compatibility/AssetConverter.cpp:152-168,
Sources/Jazz2/Compatibility/AssetConverter.cpp:400-516
Sources/Jazz2/Compatibility/AssetConverter.cpp:400-520
Content/ is the engine runtime, shipped with the release package.
Source/ holds the user's own copy of the 1998 game data. Without
@@ -51,7 +51,7 @@ files:
standalone_path: "Content/Animations/"
bundled: true
required: true
source_ref: "Sources/Jazz2/ContentResolver.cpp:1093, 1223, 2734-2735"
source_ref: "Sources/Jazz2/ContentResolver.cpp:1164, 1294, 2837-2838"
note: "Native graphics, sounds and the UI fonts, opened through OpenContentFile."
- name: "Metadata/"
@@ -61,7 +61,7 @@ files:
standalone_path: "Content/Metadata/"
bundled: true
required: true
source_ref: "Sources/Jazz2/ContentResolver.cpp:839"
source_ref: "Sources/Jazz2/ContentResolver.cpp:910"
note: "Actor .res descriptors (Common, Enemy, Player, UI, Boss, ...)."
- name: "Translations/"
@@ -71,7 +71,7 @@ files:
standalone_path: "Content/Translations/"
bundled: true
required: false
source_ref: "Sources/Main.cpp:2019-2025, Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:82-88"
source_ref: "Sources/Main.cpp:2021-2027, Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:82-88"
note: "Language .mo bundles, searched in Cache/Translations/ first. Built-in English is used when none is loaded."
# ================================================================
@@ -85,7 +85,7 @@ files:
path: "jazz2/Source/Anims.j2a"
standalone_path: "Source/Anims.j2a"
required: true
source_ref: "Sources/Main.cpp:2224-2240, Sources/Jazz2/Compatibility/AssetConverter.cpp:156-159"
source_ref: "Sources/Main.cpp:2226-2242, Sources/Jazz2/Compatibility/AssetConverter.cpp:156-159"
note: "Main animation archive. The cache cannot be created without it and the game is not flagged playable."
- name: "AnimsSw.j2a"
@@ -94,7 +94,7 @@ files:
path: "jazz2/Source/AnimsSw.j2a"
standalone_path: "Source/AnimsSw.j2a"
required: false
source_ref: "Sources/Main.cpp:2227-2229"
source_ref: "Sources/Main.cpp:2229-2231"
note: "Animation archive of the shareware demo, read when Anims.j2a is absent."
- name: "Data.j2d"
@@ -108,7 +108,7 @@ files:
# Episode descriptors (.j2e) and level files (.j2l) are optional per
# episode. Every one present in Source/ is converted.
# ref: Sources/Jazz2/Compatibility/AssetConverter.cpp:400-516
# ref: Sources/Jazz2/Compatibility/AssetConverter.cpp:400-520
- name: "prince.j2e"
category: game_data
@@ -252,8 +252,8 @@ files:
# Tilesets. Only those a converted level names are converted. The name
# is read from the level, lowercased and looked up in Source/ without
# regard to case.
# ref: Sources/Jazz2/Compatibility/JJ2Level.cpp:117, 724-727,
# Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 575-596
# ref: Sources/Jazz2/Compatibility/JJ2Level.cpp:124, 772-775,
# Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 579-600
- name: "labrat1n.j2t"
category: game_data
@@ -261,7 +261,7 @@ files:
path: "jazz2/Source/labrat1n.j2t"
standalone_path: "Source/labrat1n.j2t"
required: false
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:117, 724-727, Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 575-596"
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:124, 772-775, Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 579-600"
note: "Tileset named by share1.j2l, sharect2.j2l and sharetrs.j2l."
- name: "psych2.j2t"
@@ -270,7 +270,7 @@ files:
path: "jazz2/Source/psych2.j2t"
standalone_path: "Source/psych2.j2t"
required: false
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:117, 724-727, Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 575-596"
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:124, 772-775, Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 579-600"
note: "Tileset named by share2.j2l."
- name: "diam2.j2t"
@@ -279,11 +279,11 @@ files:
path: "jazz2/Source/diam2.j2t"
standalone_path: "Source/diam2.j2t"
required: false
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:117, 724-727, Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 575-596"
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:124, 772-775, Sources/Jazz2/Compatibility/AssetConverter.cpp:492, 579-600"
note: "Tileset named by share3.j2l and sharectf.j2l."
# Music, read from Content/Music/ then from Source/
# ref: Sources/Jazz2/ContentResolver.cpp:2681-2691
# ref: Sources/Jazz2/ContentResolver.cpp:2784-2794
- name: "menu.j2b"
category: game_data
@@ -291,7 +291,7 @@ files:
path: "jazz2/Source/menu.j2b"
standalone_path: "Source/menu.j2b"
required: false
source_ref: "Sources/Jazz2/UI/Menu/MainMenu.cpp:509-511, Sources/Jazz2/Compatibility/AssetConverter.cpp:535-538"
source_ref: "Sources/Jazz2/UI/Menu/MainMenu.cpp:509-511, Sources/Jazz2/Compatibility/AssetConverter.cpp:539-542"
note: "Main menu music."
- name: "bonus2.j2b"
@@ -300,7 +300,7 @@ files:
path: "jazz2/Source/bonus2.j2b"
standalone_path: "Source/bonus2.j2b"
required: false
source_ref: "Sources/Jazz2/UI/Menu/MainMenu.cpp:503-504, Sources/Jazz2/Compatibility/AssetConverter.cpp:535-538"
source_ref: "Sources/Jazz2/UI/Menu/MainMenu.cpp:503-504, Sources/Jazz2/Compatibility/AssetConverter.cpp:539-542"
note: "Menu music of the reforged main menu, picked at random with bonus3.j2b."
- name: "bonus3.j2b"
@@ -309,7 +309,7 @@ files:
path: "jazz2/Source/bonus3.j2b"
standalone_path: "Source/bonus3.j2b"
required: false
source_ref: "Sources/Jazz2/UI/Menu/MainMenu.cpp:503-504, Sources/Jazz2/Compatibility/AssetConverter.cpp:535-538"
source_ref: "Sources/Jazz2/UI/Menu/MainMenu.cpp:503-504, Sources/Jazz2/Compatibility/AssetConverter.cpp:539-542"
note: "Menu music of the reforged main menu, picked at random with bonus2.j2b."
- name: "intro.j2b"
@@ -318,7 +318,7 @@ files:
path: "jazz2/Source/intro.j2b"
standalone_path: "Source/intro.j2b"
required: false
source_ref: "Sources/Jazz2/UI/Cinematics.cpp:255, Sources/Main.cpp:558, Sources/Jazz2/Compatibility/AssetConverter.cpp:535-538"
source_ref: "Sources/Jazz2/UI/Cinematics.cpp:255, Sources/Main.cpp:558, Sources/Jazz2/Compatibility/AssetConverter.cpp:539-542"
note: "Music of the intro cinematic."
- name: "ending.j2b"
@@ -327,12 +327,12 @@ files:
path: "jazz2/Source/ending.j2b"
standalone_path: "Source/ending.j2b"
required: false
source_ref: "Sources/Jazz2/UI/Cinematics.cpp:255, Sources/Main.cpp:964, Sources/Jazz2/Compatibility/AssetConverter.cpp:535-538"
source_ref: "Sources/Jazz2/UI/Cinematics.cpp:255, Sources/Main.cpp:964, Sources/Jazz2/Compatibility/AssetConverter.cpp:539-542"
note: "Music of the ending cinematic."
# Level music. The name is read from the level, lowercased and given
# the .j2b extension when it has none.
# ref: Sources/Jazz2/Compatibility/JJ2Level.cpp:121, 736-741
# ref: Sources/Jazz2/Compatibility/JJ2Level.cpp:128, 784-789
- name: "labrat.j2b"
category: game_data
@@ -340,7 +340,7 @@ files:
path: "jazz2/Source/labrat.j2b"
standalone_path: "Source/labrat.j2b"
required: false
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:121, 736-741, Sources/Jazz2/LevelHandler.cpp:629-635, Sources/Jazz2/ContentResolver.cpp:2681-2685"
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:128, 784-789, Sources/Jazz2/LevelHandler.cpp:629-635, Sources/Jazz2/ContentResolver.cpp:2784-2788"
note: "Music named by share1.j2l, sharect2.j2l and sharetrs.j2l."
- name: "funkyg.j2b"
@@ -349,7 +349,7 @@ files:
path: "jazz2/Source/funkyg.j2b"
standalone_path: "Source/funkyg.j2b"
required: false
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:121, 736-741, Sources/Jazz2/LevelHandler.cpp:629-635, Sources/Jazz2/ContentResolver.cpp:2681-2685"
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:128, 784-789, Sources/Jazz2/LevelHandler.cpp:629-635, Sources/Jazz2/ContentResolver.cpp:2784-2788"
note: "Music named by share2.j2l."
- name: "diamond.j2b"
@@ -358,7 +358,7 @@ files:
path: "jazz2/Source/diamond.j2b"
standalone_path: "Source/diamond.j2b"
required: false
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:121, 736-741, Sources/Jazz2/LevelHandler.cpp:629-635, Sources/Jazz2/ContentResolver.cpp:2681-2685"
source_ref: "Sources/Jazz2/Compatibility/JJ2Level.cpp:128, 784-789, Sources/Jazz2/LevelHandler.cpp:629-635, Sources/Jazz2/ContentResolver.cpp:2784-2788"
note: "Music named by share3.j2l and sharectf.j2l."
- name: "boss2.j2b"
@@ -367,8 +367,8 @@ files:
path: "jazz2/Source/boss2.j2b"
standalone_path: "Source/boss2.j2b"
required: false
source_ref: "Sources/Jazz2/Compatibility/EventConverter.cpp:356-362, Sources/Jazz2/LevelHandler.cpp:1397-1400, Sources/Jazz2/ContentResolver.cpp:2681-2685"
note: "Boss music. The boss activation event carries a music index that is converted to boss1.j2b or boss2.j2b; share3.j2l carries index 1."
source_ref: "Sources/Jazz2/Compatibility/EventConverter.cpp:360-370, Sources/Jazz2/Compatibility/AssetConverter.cpp:505-508, Sources/Jazz2/LevelHandler.cpp:1397-1400, Sources/Jazz2/ContentResolver.cpp:2784-2788"
note: "Boss music. The boss activation event carries a music index that is converted to boss1.j2b or boss2.j2b and recorded as music the level uses; share3.j2l carries index 1."
# Cinematics, read from Content/Cinematics/ then from Source/
-2
View File
@@ -60,7 +60,6 @@ files:
# -----------------------------------------------------------
- name: "sega_101.bin"
system: sega-saturn
region: [japan]
priority: 2
required: false
size: 524288
@@ -73,7 +72,6 @@ files:
# -----------------------------------------------------------
- name: "mpr-17933.bin"
system: sega-saturn
region: [north-america, europe]
priority: 3
required: false
size: 524288
+35 -29
View File
@@ -4,7 +4,7 @@ core_classification: community_fork
source: "https://github.com/linappleii/linapple"
upstream: "https://github.com/AppleWin/AppleWin"
profiled_date: "2026-10-04"
source_commit: "02f948d92b0c4df3272ff2ccb0dfcc4daac3e430"
source_commit: "42e4321d60dc066470151f76c1cd3fe5e88a695c"
core_version: "3.0.0"
display_name: "Apple - Apple II (LinApple)"
cores:
@@ -16,24 +16,25 @@ mode: standalone
notes: |
SDL port of AppleWin for Linux. The SourceForge project was abandoned and
its forks were gathered under the linappleii organization
(README.md:163-170). Five frontends build from one core: sdl1, sdl2, sdl3,
(README.md:166-173). Five frontends build from one core: sdl1, sdl2, sdl3,
tui and headless (src/frontends/).
The ROMs the emulator runs are compiled into the binary.
scripts/generate_roms.sh turns res/roms/*.rom into EmbeddedRoms.h and
EmbeddedRoms.cpp, once at configure time and again at build time
(CMakeLists.txt:308-346), each image behind an ENABLE_ROM_* macro
(scripts/generate_roms.sh:97-119). Nineteen images go through the generator:
ten system ROMs, four character generator ROMs and five card ROMs. The
printer card and the clock card carry their firmware as arrays in their own
source files (src/apple2/peripherals/printer/Printer.cpp:30,
(CMakeLists.txt:301-338), each image behind an ENABLE_ROM_* macro
(scripts/generate_roms.sh:97-118). Eighteen images go through the generator:
ten system ROMs, four character generator ROMs and four card ROMs. The
printer card, the Super Serial Card and the clock card carry their firmware
as arrays in their own source files
(src/apple2/peripherals/printer/Printer.cpp:30,
src/apple2/peripherals/super_serial_card/SuperSerialRom.h:16,
src/apple2/peripherals/clock/ClockCard.cpp:20), and the Mockingboard
registers no ROM.
What a macro is set to decides whether a machine model runs at all.
CMakeLists.txt:57-145 leaves the Apple ][, ][+, J-Plus, //e and Enhanced //e
system ROMs ON, along with the Disk II, Super Serial Card and mouse
firmware. The three clone options are OFF: ENABLE_ROM_CLONE_BASE64A,
CMakeLists.txt:57-131 leaves the Apple ][, ][+, J-Plus, //e and Enhanced //e
system ROMs ON, along with the Disk II and mouse firmware. The three clone options are OFF: ENABLE_ROM_CLONE_BASE64A,
ENABLE_ROM_CLONE_PRAVETS and ENABLE_ROM_CLONE_TK3000E
(CMakeLists.txt:95-97). Selecting one of those machine types on a default
build reaches mem_initialize with no rom_data, which prints the model name
@@ -45,14 +46,14 @@ notes: |
A custom ROM takes precedence over anything embedded. load_custom_rom opens
the path as given, then through Path::find_data_file, and refuses a file
below 12288 or above 65536 bytes, which stops the start
(src/frontends/common/AppController.cpp:104-148,228-230). mem_initialize
(src/frontends/common/AppController.cpp:127-171,251-253). mem_initialize
accepts the buffer at exactly 16384 or 12288 bytes, or larger, taking the
trailing 16384 for the //e-class types and the trailing 12288 otherwise
(src/apple2/Memory.cpp:1038-1059). No hash or name is checked; the file is
whatever the user points at.
Character generators are res/charset40*.png, converted to XPM by CMake
(CMakeLists.txt:277-306) and included at src/apple2/Video.cpp:39-43. One of
(CMakeLists.txt:270-299) and included at src/apple2/Video.cpp:39-43. One of
five is chosen from the machine type and the keyboard language
(src/apple2/Video.cpp:1228-1252). The status font and the splash screen are
XPM as well (src/core/Asset.cpp:15-16,57,63). The four *_Video.rom images
@@ -60,7 +61,7 @@ notes: |
Master.dsk is inserted into slot 6 drive 1 at startup unless the command
line names a disk or the configuration autoloads one
(src/frontends/common/AppController.cpp:171-192). asset_find_master_disk
(src/frontends/common/AppController.cpp:194-215). asset_find_master_disk
resolves it through Path::find_data_file (src/core/Asset.cpp:25-39), which
walks the user data and config directories, the executable directory and its
res/ neighbours, the share and etc prefixes beside it, the compiled-in
@@ -80,8 +81,9 @@ notes: |
debugger running without its symbols (src/Debugger/Debugger_Symbols.cpp:495).
Peripheral cards are linked into the executable by default. With
BUILD_SHARED_PERIPHERALS, OFF at CMakeLists.txt:43, each card builds as a
shared object, and peripheral_plugins_init opens every *.so found in the
BUILD_SHARED_PERIPHERALS, OFF at CMakeLists.txt:43, each card that
add_peripheral does not force built-in builds as a shared object
(CMakeLists.txt:554-555,662-666), and peripheral_plugins_init opens every *.so found in the
plugin search paths in either build
(src/apple2/peripherals/Peripheral_Internal.cpp:192-219,
src/core/Util_Path.h:102-114).
@@ -149,7 +151,7 @@ files:
size: 49152
md5: 352fdbcae14c6a8c0398822c7a273309
sha1: f1c779af480b5bda2bdd03950c1201a5c5143bea
source_ref: "CMakeLists.txt:95,99-103; scripts/generate_roms.sh:107; src/frontends/common/AppController.cpp:125-136; src/apple2/Memory.cpp:99-100,1038-1059,1124-1134"
source_ref: "CMakeLists.txt:95,99-103; scripts/generate_roms.sh:107; src/frontends/common/AppController.cpp:148-159; src/apple2/Memory.cpp:99-100,1038-1059,1124-1134"
note: >
Larger than a system ROM: mem_initialize takes the trailing 12288 bytes
for this machine type.
@@ -163,7 +165,7 @@ files:
size: 12288
md5: c619b7edfcf021f3b8fd20d9386676ba
sha1: 8bd7ced83e209a15e9b19a05a5ef4b8e996dc1a5
source_ref: "CMakeLists.txt:96,105-109; scripts/generate_roms.sh:109; src/frontends/common/AppController.cpp:125-136; src/apple2/Memory.cpp:101-102,1038-1059,1124-1134"
source_ref: "CMakeLists.txt:96,105-109; scripts/generate_roms.sh:109; src/frontends/common/AppController.cpp:148-159; src/apple2/Memory.cpp:101-102,1038-1059,1124-1134"
- name: PRAVETS8M.ROM
required: false
@@ -174,7 +176,7 @@ files:
size: 12288
md5: 76c79535f7cf77973e2c711aa977a124
sha1: f1b4ff018acf95f3355f0d575b66a188d728b687
source_ref: "CMakeLists.txt:96,105-109; scripts/generate_roms.sh:111; src/frontends/common/AppController.cpp:125-136; src/apple2/Memory.cpp:103-104,1038-1059,1124-1134"
source_ref: "CMakeLists.txt:96,105-109; scripts/generate_roms.sh:111; src/frontends/common/AppController.cpp:148-159; src/apple2/Memory.cpp:103-104,1038-1059,1124-1134"
- name: PRAVETS8C.ROM
required: false
@@ -185,7 +187,7 @@ files:
size: 16384
md5: 1896d7b98dd940c89f7c8ea899b71006
sha1: c976fe56e57706c161f513a82ba3e0d8cda83904
source_ref: "CMakeLists.txt:96,105-109; scripts/generate_roms.sh:110; src/frontends/common/AppController.cpp:125-136; src/apple2/Memory.cpp:105-106,1038-1059,1124-1134"
source_ref: "CMakeLists.txt:96,105-109; scripts/generate_roms.sh:110; src/frontends/common/AppController.cpp:148-159; src/apple2/Memory.cpp:105-106,1038-1059,1124-1134"
note: >
A //e-class type: mem_initialize takes the trailing 16384 bytes when the
file is larger.
@@ -199,7 +201,7 @@ files:
size: 16384
md5: 8d84b614b545d4eec8799d3f325332cb
sha1: f163e5753c18ff0e812a448e8da406f102600edf
source_ref: "CMakeLists.txt:97,111-115; scripts/generate_roms.sh:112; src/frontends/common/AppController.cpp:125-136; src/apple2/Memory.cpp:107-108,1038-1059,1124-1134"
source_ref: "CMakeLists.txt:97,111-115; scripts/generate_roms.sh:112; src/frontends/common/AppController.cpp:148-159; src/apple2/Memory.cpp:107-108,1038-1059,1124-1134"
note: >
A //e-class type: mem_initialize takes the trailing 16384 bytes when the
file is larger.
@@ -209,7 +211,7 @@ analysis:
mechanism: >
res/roms/*.rom compiled into EmbeddedRoms.cpp by scripts/generate_roms.sh
at configure and build time, each behind an ENABLE_ROM_* macro
source_ref: "CMakeLists.txt:57-145,308-346; scripts/generate_roms.sh:97-119"
source_ref: "CMakeLists.txt:57-131,301-338; scripts/generate_roms.sh:97-118"
extraction: "hashes computed from the res/roms images the generator reads"
system_roms:
@@ -356,7 +358,7 @@ analysis:
image: res/roms/DISK2.rom
cmake_option: ENABLE_ROM_DISK2
default: on
source_ref: "CMakeLists.txt:118,121-125; scripts/generate_roms.sh:115; src/apple2/peripherals/disk/Disk.cpp:1125-1127"
source_ref: "CMakeLists.txt:118,120-124; scripts/generate_roms.sh:115; src/apple2/peripherals/disk/Disk.cpp:1125-1127"
size: 256
md5: 2020aa1413ff77fe29353f3ee72dc295
sha1: d4181c9f046aafc3fb326b381baac809d9e38d16
@@ -367,7 +369,7 @@ analysis:
image: res/roms/DISK2-P6.rom
cmake_option: ENABLE_ROM_DISK2
default: on
source_ref: "scripts/generate_roms.sh:117; src/apple2/peripherals/disk/Disk.cpp:618-624; CMakeLists.txt:942-955"
source_ref: "scripts/generate_roms.sh:117; src/apple2/peripherals/disk/Disk.cpp:618-624; CMakeLists.txt:957-970"
size: 256
md5: 5f1be0c1cdff26f5956eef9643911886
sha1: bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4
@@ -389,14 +391,18 @@ analysis:
- device: "Super Serial Card"
slot: 2
symbol: g_rom_ssc
symbol: super_serial_rom
image: res/roms/SSC.rom
cmake_option: ENABLE_ROM_SSC
default: on
source_ref: "CMakeLists.txt:119,127-131; scripts/generate_roms.sh:118; src/apple2/peripherals/super_serial_card/SuperSerial.cpp:250-254"
source_ref: "src/apple2/peripherals/super_serial_card/SuperSerialRom.h:8-16; src/apple2/peripherals/super_serial_card/SuperSerial.cpp:356-358; CMakeLists.txt:884-887"
size: 2048
md5: 67c0d61ab0911183faf05270f881a97e
sha1: 6dab633470c6bc4cb3e81d09fda46597caf8ee57
note: >
Not generated and behind no cmake option: the card carries the 2048
bytes as an array in SuperSerialRom.h, the last page mapped at $Cn00
and the whole image at $C800. The generator does not read
res/roms/SSC.rom; a build test pins its SHA-1 as the reference of
that transcription.
- device: "Mouse interface card"
slot: 4
@@ -404,7 +410,7 @@ analysis:
image: res/roms/MouseInterface.rom
cmake_option: ENABLE_ROM_MOUSE
default: on
source_ref: "CMakeLists.txt:133,135-139; scripts/generate_roms.sh:119; src/apple2/peripherals/mouse/Mouse.cpp:537-541"
source_ref: "CMakeLists.txt:126,128-132; scripts/generate_roms.sh:118; src/apple2/peripherals/mouse/Mouse.cpp:537-541"
size: 2048
md5: 22563ab338024dac7b03510371d49c57
sha1: 3a9d881a8a8d30f55b9719aceebbcf717f829d6f
@@ -413,7 +419,7 @@ analysis:
slot: 1
symbol: printer_rom
image: res/roms/Parallel.rom
source_ref: "src/apple2/peripherals/printer/Printer.cpp:30-53,215; CMakeLists.txt:881-883"
source_ref: "src/apple2/peripherals/printer/Printer.cpp:30-53,215; CMakeLists.txt:872-874"
size: 256
md5: 5902996f16dc78fc013f6e1db14805b3
sha1: c75c751589cbcc1ad4ab4f63676af7abadd86f5b
-6
View File
@@ -25,28 +25,24 @@ notes: >
files:
- name: "syscard3.pce"
description: "CD-ROM System Card 3.0 (Japan)"
region: [japan]
required: true
source_ref: "libretro.cpp:1051-1052, mednafen/settings.cpp:40"
note: "Default value for pce_cdbios option."
- name: "syscard2.pce"
description: "CD-ROM System Card 2.0 (Japan)"
region: [japan]
required: false
source_ref: "libretro.cpp:1053-1054"
note: "pce_cdbios = 'System Card 2'."
- name: "syscard1.pce"
description: "CD-ROM System Card 1.0 (Japan)"
region: [japan]
required: false
source_ref: "libretro.cpp:1055-1056"
note: "pce_cdbios = 'System Card 1'."
- name: "gexpress.pce"
description: "Games Express CD Card"
region: [japan]
required: false
aliases: ["gecard.pce"]
source_ref: "libretro.cpp:1057-1058, mednafen/settings.cpp:41, mednafen/pce/pce.cpp:373-411"
@@ -54,14 +50,12 @@ files:
- name: "syscard3u.pce"
description: "CD-ROM System Card 3.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false
source_ref: "libretro.cpp:1059-1060"
note: "pce_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.cpp:1061-1062"
note: "pce_cdbios = 'System Card 2 US'. Not in .info."
+7 -5
View File
@@ -254,7 +254,7 @@ files:
size: 408
sha1: "483b2c34fcb3ddcf650314e45104f358ef74c98b"
description: "MSX1 machine definition"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/system.ccm:75-76, MSX.emu/src/main/EmuMenuViews.cc:32-97"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/EmuMenuViews.cc:32-97"
- name: "config.ini"
path: "Machines/MSX2/config.ini"
@@ -264,7 +264,8 @@ files:
size: 649
sha1: "460ded0dbfc0285490da87a446b0321b6496aa5b"
description: "MSX2 machine definition, the default MSX machine"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/system.ccm:75-76, MSX.emu/src/main/EmuMenuViews.cc:32-97"
note: "Default of the machine name option (MSX.emu/src/main/system.ccm:75)."
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/EmuMenuViews.cc:32-97"
- name: "config.ini"
path: "Machines/MSX2+/config.ini"
@@ -274,7 +275,7 @@ files:
size: 773
sha1: "cb7d14a17c4f4632f17ba5a64160cdd876dd6010"
description: "MSX2+ machine definition"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/system.ccm:75-76, MSX.emu/src/main/EmuMenuViews.cc:32-97"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/EmuMenuViews.cc:32-97"
- name: "config.ini"
path: "Machines/MSXturboR/config.ini"
@@ -284,7 +285,7 @@ files:
size: 941
sha1: "9836d109ec7879374dcd044da149f1b5aafd4664"
description: "MSX turbo R machine definition"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/system.ccm:75-76, MSX.emu/src/main/EmuMenuViews.cc:32-97"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/EmuMenuViews.cc:32-97"
- name: "config.ini"
path: "Machines/COL - ColecoVision/config.ini"
@@ -294,7 +295,8 @@ files:
size: 347
sha1: "0f0b7b73ba6f8677dfc5c824bb3edf6df2ff653e"
description: "ColecoVision machine definition, the default Coleco machine"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/system.ccm:75-76, MSX.emu/src/main/EmuMenuViews.cc:32-97"
note: "Default of the machine name option (MSX.emu/src/main/system.ccm:76)."
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468, MSX.emu/src/main/RomLoader.cc:135-144, MSX.emu/src/main/EmuMenuViews.cc:32-97"
- name: "MSX.rom"
path: "Machines/Shared Roms/MSX.rom"
+3 -2
View File
@@ -261,8 +261,9 @@ files:
Named in the audio BIOS region by irrmaze, neogeo, svcboot, svcplus,
svcplusa and svcsplus, and reached in this archive by the parent lookup of
irrmaze. The region is exempt from the fatal path and the buffer it lands
in is never read by the emulation.
source_ref: "NEO.emu/res/romsets/irrmaze.drv, NEO.emu/src/gngeo/roms.c:1034-1039,1413-1418,1440-1461, NEO.emu/src/gngeo/roms.h:63"
in is never read by the emulation
(NEO.emu/src/gngeo/roms.c:1034-1039,1413-1418,1440-1461).
source_ref: "NEO.emu/res/romsets/irrmaze.drv, NEO.emu/src/gngeo/roms.h:9,62"
- name: "gngeo_data.zip"
system: snk-neogeo-mvs
+3 -2
View File
@@ -61,9 +61,10 @@ files:
md5: "23bf80c259f182f582beeb368aba98f5"
required: true
bundled: true
source_ref: "src/libretro.cpp:332-371, src/NuonEnvironment.cpp:595, src/NuonEnvironment.cpp:617-619, src/bios.cpp:566-582, src/coff.cpp:47-70"
source_ref: "src/libretro.cpp:332-371, src/NuonEnvironment.cpp:595, src/NuonEnvironment.cpp:617-619, src/bios.cpp:566-582"
note: >
Loaded into MPE3 by InitBios. The 2007 build shipped in the release
Loaded into MPE3 by InitBios through LoadCoffFile, which rejects a file
whose magic is not 0x0120 (src/coff.cpp:47-71). The 2007 build shipped in the release
archives (19410 bytes, sha1 4f42ed8f5e171cc79112d1c1e201381e83b1fc0d)
loads the same way.
-4
View File
@@ -34,7 +34,6 @@ notes: |
files:
- name: "o2rom.bin"
system: odyssey2
region: [north-america]
required: true
size: 1024
md5: "562d5ebf9e030a40d6fabfc2f33139fd"
@@ -46,7 +45,6 @@ files:
- name: "c52.bin"
system: videopac
region: [europe]
required: true
size: 1024
md5: "f1071cdb0b6b10dde94d3bc8a6146387"
@@ -58,7 +56,6 @@ files:
- name: "g7400.bin"
system: videopac
region: [europe]
required: true
size: 1024
md5: "c500ff71236068e0dc0d0603d265ae76"
@@ -70,7 +67,6 @@ files:
- name: "jopac.bin"
system: videopac
region: [france]
required: true
size: 1024
md5: "279008e4a0db2dc5f1c048853b033828"
-13
View File
@@ -41,7 +41,6 @@ files:
# -------------------------------------------------------
- name: "panafz1.bin"
system: 3do
region: [north-america]
required: true
size: 1048576 # 1 MB
md5: "f47264dd47fe30f73ab3c010015c155b"
@@ -54,7 +53,6 @@ files:
# -------------------------------------------------------
- name: "panafz1j.bin"
system: 3do
region: [japan]
required: true
size: 1048576 # 1 MB
md5: "a496cfdded3da562759be3561317b605"
@@ -67,7 +65,6 @@ files:
# -------------------------------------------------------
- name: "panafz1j-norsa.bin"
system: 3do
region: [japan]
required: false
size: 1048576 # 1 MB
md5: "f6c71de7470d16abe4f71b1444883dc8"
@@ -80,7 +77,6 @@ files:
# -------------------------------------------------------
- name: "panafz10.bin"
system: 3do
region: [north-america]
required: true
size: 1048576 # 1 MB
md5: "51f2f43ae2f3508a14d9f56597e2d3ce"
@@ -93,7 +89,6 @@ files:
# -------------------------------------------------------
- name: "panafz10-norsa.bin"
system: 3do
region: [north-america]
required: false
size: 1048576 # 1 MB
md5: "1477bda80dc33731a65468c1f5bcbee9"
@@ -106,7 +101,6 @@ files:
# -------------------------------------------------------
- name: "panafz10e-anvil.bin"
system: 3do
region: [europe]
required: true
size: 1048576 # 1 MB
md5: "a48e6746bd7edec0f40cff078f0bb19f"
@@ -119,7 +113,6 @@ files:
# -------------------------------------------------------
- name: "panafz10e-anvil-norsa.bin"
system: 3do
region: [europe]
required: false
size: 1048576 # 1 MB
md5: "cf11bbb5a16d7af9875cca9de9a15e09"
@@ -132,7 +125,6 @@ files:
# -------------------------------------------------------
- name: "goldstar.bin"
system: 3do
region: [north-america]
required: true
size: 1048576 # 1 MB
md5: "8639fd5e549bd6238cfee79e3e749114"
@@ -145,7 +137,6 @@ files:
# -------------------------------------------------------
- name: "sanyotry.bin"
system: 3do
region: [japan]
required: true
size: 1048576 # 1 MB
md5: "35fa1a1ebaaeea286dc5cd15487c13ea"
@@ -158,7 +149,6 @@ files:
# -------------------------------------------------------
- name: "3do_arcade_saot.bin"
system: 3do
region: [north-america]
required: false
size: 524288 # 512 KB
md5: "8970fc987ab89a7f64da9f8a8c4333ff"
@@ -171,7 +161,6 @@ files:
# -------------------------------------------------------
- name: "panafz1-kanji.bin"
system: 3do
region: [japan]
required: false
size: 933636
md5: "b8dc97f778a6245c58e064b0312e8281"
@@ -184,7 +173,6 @@ files:
# -------------------------------------------------------
- name: "panafz1j-kanji.bin"
system: 3do
region: [japan]
required: false
size: 1048576 # 1 MB
md5: "c23fb5d5e6bb1c240d02cf968972be37"
@@ -197,7 +185,6 @@ files:
# -------------------------------------------------------
- name: "panafz10ja-anvil-kanji.bin"
system: 3do
region: [japan]
required: false
size: 1048576 # 1 MB
md5: "428577250f43edc902ea239c50d2240d"
+21 -3
View File
@@ -78,7 +78,7 @@ files:
note: "Any file in the BIOS folder whose romdir holds RESET and ROMVER is accepted, whatever its name or size. Naming convention ps2-VVVVr-YYYYMMDD.bin carries version, region and date."
source_ref: "pcsx2/ps2/BiosTools.cpp:261-323, pcsx2/ps2/BiosTools.cpp:74-167"
- name: rom1.bin
- name: ps2-0230a-20080220.rom1
system: sony-playstation-2
required: false
max_size: 262144
@@ -86,7 +86,7 @@ files:
note: "Tried as {bios}.rom1 then {biosbase}.rom1. Read into the 256 KB ROM1 region and truncated to it. Logged and skipped when absent."
source_ref: "pcsx2/ps2/BiosTools.cpp:190-229, pcsx2/ps2/BiosTools.cpp:298, pcsx2/MemoryTypes.h:22"
- name: ROM2.BIN
- name: ps2-0230a-20080220.rom2
system: sony-playstation-2
required: false
max_size: 524288
@@ -94,7 +94,7 @@ files:
note: "Tried as {bios}.rom2 then {biosbase}.rom2. Read into the 512 KB ROM2 region and truncated to it. Present on Chinese region consoles."
source_ref: "pcsx2/ps2/BiosTools.cpp:190-229, pcsx2/ps2/BiosTools.cpp:299, pcsx2/MemoryTypes.h:23"
- name: EROM.BIN
- name: ps2-0230a-20080220.erom
system: sony-playstation-2
required: false
max_size: 1835008
@@ -102,6 +102,24 @@ files:
note: "Tried as {bios}.erom then {biosbase}.erom. Read into the 1.75 MB EROM region and truncated to it."
source_ref: "pcsx2/ps2/BiosTools.cpp:190-229, pcsx2/ps2/BiosTools.cpp:300, pcsx2/MemoryTypes.h:24"
- name: ps2-0230a-20080220.nvm
system: sony-playstation-2
required: false
hle_fallback: true
min_size: 1024
description: "Console NVRAM"
note: "Read at the BIOS path with the extension set to nvm. A substitute is written when the file is shorter than 1024 bytes."
source_ref: "pcsx2/CDVD/CDVD.cpp:138-149"
- name: ps2-0230a-20080220.mec
system: sony-playstation-2
required: false
hle_fallback: true
size: 4
description: "Mechacon version"
note: "Read at the BIOS path with the extension set to mec. A 4-byte substitute (03 06 02 00) is written when the file is shorter."
source_ref: "pcsx2/CDVD/CDVD.cpp:93-122"
- name: eeprom.dat
system: sony-playstation-2
required: false
+23 -11
View File
@@ -15,7 +15,7 @@ systems: [sony-playstation-2]
notes: |
Filename-agnostic BIOS detection. Scans bios/ for any file between 4-8 MB
with valid romdir structure (RESET + ROMVER entries). No hash validation.
Companion files (.rom1, .rom2, .nvm, .mec) derive paths from selected BIOS.
Companion files (.rom1, .rom2, .nvm, .mec) derive their names from the selected BIOS.
ROM1 (DVD player) and ROM2 (Chinese extension) silently skipped if missing.
NVM and MEC auto-created with defaults if missing.
@@ -28,23 +28,35 @@ files:
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: rom1.bin
- name: ps2-0230a-20080220.rom1
required: false
max_size: 4194304
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241,366"
note: "DVD player ROM. Tries {biospath}.rom1 then {biosbase}.rom1. Silently skipped if missing."
description: "DVD player ROM"
note: "Tried as {bios}.rom1 then {biosbase}.rom1, opened case-insensitively. Copied to the ROM1 region. Logged and skipped when absent."
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241, pcsx2/ps2/BiosTools.cpp:366"
- name: ROM2.BIN
- name: ps2-0230a-20080220.rom2
required: false
max_size: 4194304
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241,367"
note: "Chinese ROM extension. Same naming convention as rom1. Only present on Chinese region consoles."
description: "Chinese ROM extension"
note: "Same two-step naming as rom1. Only Chinese region consoles carry one."
source_ref: "pcsx2/ps2/BiosTools.cpp:214-241, pcsx2/ps2/BiosTools.cpp:367"
- name: EROM.BIN
- name: ps2-0230a-20080220.nvm
required: false
source_ref: "pcsx2/ps2/BiosTools.cpp"
note: "Extended ROM. Present in some BIOS dumps but not loaded by PCSX2 code via LoadExtraRom."
path: null
hle_fallback: true
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:160-188"
- name: ps2-0230a-20080220.mec
required: false
hle_fallback: true
size: 4
description: "Mechacon version"
note: "Read at the BIOS path with the extension replaced by mec. Defaults to 0x00020603 and is written back when it cannot be read."
source_ref: "pcsx2/CDVD/CDVD.cpp:190-204"
- name: eeprom.dat
required: false
+8 -8
View File
@@ -29,7 +29,7 @@ files:
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[0])"
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[0])"
note: "Preferred JP BIOS, searched first by exact filename."
# -- Region: North America (PSX_REGION_US = 0) --
@@ -41,19 +41,19 @@ files:
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[1])"
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[1])"
note: "Preferred US BIOS, searched first by exact filename."
# -- Region: Europe (PSX_REGION_EU = 2) --
- name: "scph5502.bin"
description: "SCPH-5502 (v3.0 01-06-97 E)"
region: [europe]
region: [europe, oceania]
size: 524288
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[2])"
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[2])"
note: "Preferred EU BIOS, searched first by exact filename."
# -- Fallback BIOS filenames (searched in order after scph550x) --
@@ -65,7 +65,7 @@ files:
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[3])"
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"
@@ -75,7 +75,7 @@ files:
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[4])"
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[4])"
note: "PSone slim model."
- name: "scph7001.bin"
@@ -85,7 +85,7 @@ files:
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[5])"
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[5])"
- name: "scph1001.bin"
description: "SCPH-1001 (v2.2 12-04-95 A)"
@@ -94,7 +94,7 @@ files:
required: false
hle_fallback: true
validation: [size]
source_ref: "frontend/libretro.c:3737-3740 (listed_bios[6])"
source_ref: "frontend/libretro.c:3864-3867 (listed_bios[6])"
note: "Original US model. Last in the explicit filename search list."
# -- Dynamic scan fallback --
-2
View File
@@ -34,7 +34,6 @@ notes: |
files:
- name: "7800 BIOS (U).rom"
system: atari-7800
region: [north-america]
required: false
hle_fallback: true
size: 4096
@@ -43,7 +42,6 @@ files:
- name: "7800 BIOS (E).rom"
system: atari-7800
region: [europe]
required: false
hle_fallback: true
size: 16384
+7 -7
View File
@@ -50,7 +50,7 @@ files:
- name: "scph5502.bin"
description: "SCPH-5502 (v3.0 E)"
region: [europe]
region: [europe, oceania]
required: true
size: 524288
validation: [size, sha256]
@@ -183,7 +183,7 @@ files:
# Europe alternatives
- name: "scph1002.bin"
description: "SCPH-1002 (v2.0 E)"
region: [europe]
region: [europe, oceania]
required: false
size: 524288
validation: [size, sha256]
@@ -192,7 +192,7 @@ files:
- name: "scph1002b.bin"
description: "SCPH-1002 (v2.1 E)"
region: [europe]
region: [europe, oceania]
required: false
size: 524288
validation: [size, sha256]
@@ -201,7 +201,7 @@ files:
- name: "scph1002c.bin"
description: "SCPH-1002 (v2.2 E)"
region: [europe]
region: [europe, oceania]
required: false
size: 524288
validation: [size, sha256]
@@ -210,7 +210,7 @@ files:
- name: "scph7002.bin"
description: "SCPH-7002 (v4.1 E)"
region: [europe]
region: [europe, oceania]
required: false
size: 524288
validation: [size, sha256]
@@ -219,7 +219,7 @@ files:
- name: "scph102.bin"
description: "SCPH-102 (v4.4 E)"
region: [europe]
region: [europe, oceania]
required: false
size: 524288
validation: [size, sha256]
@@ -228,7 +228,7 @@ files:
- name: "scph102_v45.bin"
description: "v4.5 E"
region: [europe]
region: [europe, oceania]
required: false
size: 524288
validation: [size, sha256]
+4
View File
@@ -57,6 +57,10 @@ files:
system: solarus
category: game_data
required: false
bundled: true
size: 600521
sha1: "ab035b7804089690018318e4a05f9a8fc59421ff"
md5: "1e70da4de91be79b9ccf47209a1fc73f"
description: "SDL gamepad mappings applied on top of the built-in table"
source_ref: "src/core/System.cpp:71-99, cmake/AddDefines.cmake:44-49, cmake/AddInstallTargets.cmake:23-28"
note: >-
+3 -3
View File
@@ -132,11 +132,11 @@ files:
name and crc32 of every file that fills them, the stepping, the PCI bridge, the
encryption key, the drive board, net board and MPEG board the machine carries, the
patches to apply and the parent set to fall back on. Handed to the GameLoader
constructor, parsed as XML, and a game load is refused outright when the file is
not there. The Kotlin launcher parses the same copy out of the assets for its game
constructor, parsed as XML (Src/GameLoader.cpp:492-502, 930-933), and a game
load is refused outright when the file is not there. The Kotlin launcher parses the same copy out of the assets for its game
list and its per-game input hints. Byte identical to the copy in the repository
root Config directory.
source_ref: "android/app/src/main/cpp/native-lib.cpp:568-580, 1096-1114, Src/GameLoader.cpp:492-502, 930-933, Src/Util/ConfigBuilders.cpp:60-73, android/app/src/main/java/com/izzy2lost/super3/GameXml.kt:23-24, android/app/src/main/java/com/izzy2lost/super3/MainActivity.kt:1211"
source_ref: "android/app/src/main/cpp/native-lib.cpp:568-580, 1096-1114, Src/Util/ConfigBuilders.cpp:60-73, android/app/src/main/java/com/izzy2lost/super3/GameXml.kt:23-24, android/app/src/main/java/com/izzy2lost/super3/MainActivity.kt:1211"
- name: Supermodel.ini
path: "Config/Supermodel.ini"
+4 -4
View File
@@ -93,8 +93,8 @@ files:
sha1: "6871a47de90ca67f9ff8e4e280416851a8164563"
md5: "0c7cfc0cba2f2cd674d94e093936f9f8"
description: "ROM set definitions"
note: "Parsed at startup into the game database. Every ROM region, file name and CRC32 the loader matches against comes from here, so an unreadable file leaves no set to resolve and the launch returns before the emulator is built. The path is a config key, so -game-xml-file moves it. Nothing about the file is verified beyond parsing; the hash is that of the copy the pinned revision ships."
source_ref: "Src/OSD/SDL/Main.cpp:117, Src/OSD/SDL/Main.cpp:1500, Src/OSD/SDL/Main.cpp:1957, Src/OSD/SDL/Main.cpp:2319-2329, Src/GameLoader.cpp:549-559, Src/GameLoader.cpp:1014-1017"
note: "Parsed at startup into the game database (Src/GameLoader.cpp:549-559, 1014-1017). Every ROM region, file name and CRC32 the loader matches against comes from here, so an unreadable file leaves no set to resolve and the launch returns before the emulator is built. The path is a config key, so -game-xml-file moves it. Nothing about the file is verified beyond parsing; the hash is that of the copy the pinned revision ships."
source_ref: "Src/OSD/SDL/Main.cpp:117, Src/OSD/SDL/Main.cpp:1500, Src/OSD/SDL/Main.cpp:1957, Src/OSD/SDL/Main.cpp:2319-2329"
- name: Games.xml
path: "supermodel/Games.xml"
@@ -110,8 +110,8 @@ files:
- "2d16307ceb658636a05d34c7ba935b48"
- "29fd5bc40cea222e040c1a1cfd60ea2e"
description: "ROM set definitions, libretro copy"
note: "Read through the VFS from {system}/supermodel/Games.xml, or Config/Games.xml under it, and parsed from memory. When the read yields nothing the bytes compiled in from Config/Games.xml are parsed instead, so the core identifies every set without a file on disk; a file that is present takes precedence. The second hash is the embedded array, which predates the indentation cleanup of the repository copy by one tab."
source_ref: "Src/OSD/libretro/LibretroWrapper.cpp:187-206, Src/OSD/libretro/LibretroWrapper.cpp:221-222, Src/OSD/libretro/LibretroWrapper.cpp:822, Src/OSD/libretro/LibretroWrapper.cpp:834-859, Src/GameLoader.cpp:1019-1030, Makefile:651-653"
note: "Read through the VFS from {system}/supermodel/Games.xml, or Config/Games.xml under it, and parsed from memory (Src/GameLoader.cpp:1019-1030). When the read yields nothing the bytes compiled in from Config/Games.xml are parsed instead, so the core identifies every set without a file on disk; a file that is present takes precedence. The second hash is the embedded array, which predates the indentation cleanup of the repository copy by one tab."
source_ref: "Src/OSD/libretro/LibretroWrapper.cpp:187-206, Src/OSD/libretro/LibretroWrapper.cpp:221-222, Src/OSD/libretro/LibretroWrapper.cpp:822, Src/OSD/libretro/LibretroWrapper.cpp:834-859, Makefile:651-653"
- name: Supermodel.ini
path: "supermodel/Supermodel.ini"
+1 -1
View File
@@ -55,7 +55,7 @@ files:
- name: "scph5502.bin"
description: "SCPH-5502, 5552 (v3.0 01-06-97 E)"
region: [europe]
region: [europe, oceania]
required: false
hle_fallback: true
md5: "32736f17079d0b2b7024407c39bd3050"
+41 -23
View File
@@ -4,7 +4,7 @@ core_classification: game_engine
source: "https://github.com/vpinball/vpinball"
upstream: "https://github.com/vpinball/vpinball"
profiled_date: "2026-10-04"
source_commit: "a31565f1589c17afd0502a8c6a690f408ede14b2"
source_commit: "8a9f36d227a0e71be33c36418e4e3ffc8c5c2723"
core_version: "10.8.1"
display_name: "Visual Pinball X"
cores:
@@ -17,13 +17,14 @@ mode: standalone
notes: |
Pinball table simulator. The table is the content: .vpx and .vpt files are
OLE Compound Documents holding the geometry, textures, sounds and a VBScript
program, read through POLE (src/parts/pintable.cpp:1424-1427). Every build
program, read through POLE (src/parts/pintable.cpp:1427-1430). Every build
other than the Windows editor compiles the shared source set with
__STANDALONE__ and links libwinevbs, Wine's VBScript engine with its COM and
ATL headers, beside SDL3, SDL3_ttf, FreeImage and hidapi; PinMAME, AltSound,
ATL headers, beside SDL3, SDL3_ttf, FreeImage, hidapi, libspatialaudio and
libmysofa with zlib; PinMAME, AltSound,
DMDUtil, DOF, FlexDMD, B2S, PUP, Serum, VNI and ScoreView are plugins staged
in plugins/ next to the binary (make/CMakeLists_app.txt:525-546,597-603,
make/CMakeLists_sources.txt:809-815, make/CMakeLists_plugins.txt:21-25,39-56).
in plugins/ next to the binary (make/CMakeLists_app.txt:533-554,605-615,
make/CMakeLists_sources.txt:811-817, make/CMakeLists_plugins.txt:21-25,39-56).
ES-DE finds VPinballX_BGFX or VPinballX_GL and
runs `<binary> -play <rom>`, Batocera builds the standalone tree at
3ec37c7f9a into /usr/bin/vpinball and runs `VPinballX_GL -PrefPath <dir> -Ini
@@ -48,11 +49,11 @@ notes: |
user/ and scripts/ beside it, then the installation folder with user/,
scripts/ and tables/ under it, then the preferences folder with user/ and
scripts/ under it (src/core/FileLocator.cpp:379-412,
src/core/ScriptGlobalTable.cpp:346-375). The
src/core/ScriptGlobalTable.cpp:352-381). The
scripts/ directory the build ships carries the 71 .vbs files tables include
by name, core.vbs and controller.vbs beside the per-manufacturer machine
scripts (WPC.vbs, sam.vbs, s11.vbs, zac.vbs and the rest), copied from
src/scripts (make/CMakeLists_app.txt:619-623). The Windows editor's
src/scripts (make/CMakeLists_app.txt:631-635). The Windows editor's
completion parser asks for core.vbs through the same search without a table,
so in the seven installation and preferences locations, and shows a warning
box when none holds it (src/ui/win/codeview.cpp:2068-2076,
@@ -62,7 +63,7 @@ notes: |
folder or the installation folder when SDL reports none, MusicDirectory the
table's music folder with an optional subfolder, ScriptsDirectory scripts/
under the installation folder; each fails when its directory does not exist
(src/core/ScriptGlobalTable.cpp:377-418, src/core/FileLocator.cpp:251-259).
(src/core/ScriptGlobalTable.cpp:383-424, src/core/FileLocator.cpp:251-259).
In AppPrefData mode user/, music/, cache/ and autosave/ sit beside the table;
a folder missing there on a read is looked up the AppOnly way, where user/
hangs off the installation folder and music/ is tried under the installation
@@ -83,10 +84,10 @@ notes: |
blankTable.vpx, exampleTable.vpx, lightSeqTable.vpx and strippedTable.vpx:
the live editor loads blankTable.vpx when started without a table or when
the requested one fails to load, and its New Table menu loads any of the
four (src/core/AppCommands.cpp:277-283, src/ui/live/EditorUI.cpp:334-353).
four (src/core/AppCommands.cpp:277-283, src/ui/live/EditorUI.cpp:338-357).
OpenGL builds also get shaders-10.8.1/, the .glfx and .fxh sources parsed at
runtime (src/core/FileLocator.cpp:223, src/renderer/Shader.cpp:1827-1828,2234-2237,
make/CMakeLists_app.txt:625-635); the directory is not copied for the BGFX
runtime (src/core/FileLocator.cpp:223, src/renderer/Shader.cpp:1827-1828,2231-2234,
make/CMakeLists_app.txt:637-647); the directory is not copied for the BGFX
renderer. Three plugins carry resources of their own: plugins/flexdmd/assets/
holds the bitmap fonts UltraDMD asks for by resource name, the
FlexDMD.Resources. prefix being stripped before the lookup
@@ -100,11 +101,11 @@ notes: |
Two parts of that tree ship without a reader on the desktop targets.
docs/Changelog.txt is read by the About dialog
(src/ui/win/dialogs/AboutDialog.cpp:34), which sits in the Windows source
list only (make/CMakeLists_sources.txt:491,540-541,
list only (make/CMakeLists_sources.txt:493,542-543,
make/CMakeLists_app.txt:78-80), and assets/web/, vpx.html with its scripts
and style sheet, is served by the web server that only the iOS and Android
library build compiles (lib/src/WebServer.cpp:92-99,
make/CMakeLists_sources.txt:817-823, make/CMakeLists_app.txt:669).
make/CMakeLists_sources.txt:819-825, make/CMakeLists_app.txt:679).
PinMAME is where the emulation happens. Tables that recreate a real solid
state machine hand a ROM name to the controller, which resolves its root as
@@ -340,7 +341,7 @@ files:
category: game_data
required: false
description: "MAME high score memory map database, read once per machine start"
source_ref: "pinmame src/hiscore.c:18,223-230, pinmame src/cpuexec.c:332-334,393,1755, src/core/player.cpp:457-459"
source_ref: "pinmame src/hiscore.c:18, 223-230, pinmame src/cpuexec.c:332-334, 393, 1755, src/core/player.cpp:463-465"
note: >-
hs_open is called unconditionally at the top of cpu_run for every PinMAME
machine, and the whole body sits behind `if (f)`, so an absent file is a
@@ -364,23 +365,40 @@ files:
absent file leaves the menu showing "cheat database not found"
(pinmame src/cheat.c:2463). Same bare-filename resolution as hiscore.dat.
- name: fabian-hrir.sofa
path: "assets/fabian-hrir.sofa"
system: vpinball
required: false
size: 1766147
sha1: "5bf46cfb6472b81f4aa7fb2c87bc79afc23109d7"
md5: "b98b60a050e3c2edfa7a59863c625c61"
description: "FABIAN full-sphere HRIR set, the HRTF of the binaural spatial audio renderer"
source_ref: "src/audio/SpatialAudio.cpp:49-56,344-360, make/CMakeLists_app.txt:631-635"
note: >-
Read from assets/ under the installation folder when libspatialaudio is
built with libmysofa, which every build script does; the headphone and VR
binauralizer then runs second order ambisonics on it. A missing or
unreadable file logs "Failed to load the binaural HRTF" and audio falls
back to loudspeaker decoding. The file sits in the repository's top-level
assets/ while the build copies src/assets, so no build output carries it.
analysis:
shipped_with_the_binary:
note: >-
Copied into the build output next to the executable and installed as one
unit by every distribution, so they arrive with the emulator rather than
being sourced separately.
source_ref: "make/CMakeLists_app.txt:619-623"
source_ref: "make/CMakeLists_app.txt:631-635"
entries:
- what: "scripts/, the .vbs library tables include"
source_ref: "src/core/FileLocator.cpp:379-412, src/core/ScriptGlobalTable.cpp:346-375, src/ui/win/codeview.cpp:2068-2076"
source_ref: "src/core/FileLocator.cpp:379-412, src/core/ScriptGlobalTable.cpp:352-381, src/ui/win/codeview.cpp:2068-2076"
- what: "assets/, textures, segment display maps, TableSizes.csv, the window icon, the fallback font, the editor icons"
source_ref: "src/renderer/Renderer.cpp:176-188,1493-1510, src/parts/bumper.cpp:148,160,172,186, src/parts/kicker.cpp:198-239, src/core/TableDB.cpp:16, src/renderer/Window.cpp:280, src/parts/textbox.cpp:719-720, src/ui/live/editor/EditorChrome.cpp:200"
- what: "assets/blankTable.vpx, exampleTable.vpx, lightSeqTable.vpx, strippedTable.vpx"
source_ref: "src/core/AppCommands.cpp:277-283, src/ui/live/EditorUI.cpp:334-353"
source_ref: "src/core/AppCommands.cpp:277-283, src/ui/live/EditorUI.cpp:338-357"
note: "The live editor's default table and its four New Table templates."
- what: "shaders-10.8.1/, .glfx and .fxh parsed at runtime by the OpenGL renderer"
source_ref: "src/core/FileLocator.cpp:223, src/renderer/Shader.cpp:1827-1828,2234-2237, make/CMakeLists_app.txt:625-635"
source_ref: "src/core/FileLocator.cpp:223, src/renderer/Shader.cpp:1827-1828, src/renderer/Shader.cpp:2231-2234, make/CMakeLists_app.txt:637-647"
- what: "plugins/flexdmd/assets/, the bitmap fonts UltraDMD names as FlexDMD.Resources.*"
source_ref: "plugins/flexdmd/UltraDMD.cpp:20-24,32-34, plugins/flexdmd/resources/AssetManager.cpp:103-109,252-272, make/CMakeLists_plugin_FlexDMD.txt:179-182"
- what: "plugins/scoreview/layouts/, the default score view layouts"
@@ -392,10 +410,10 @@ analysis:
note: "Copied into the build output but unreachable in the desktop standalone binary"
entries:
- what: "docs/Changelog.txt"
source_ref: "src/ui/win/dialogs/AboutDialog.cpp:34, make/CMakeLists_sources.txt:540-541, make/CMakeLists_app.txt:622"
source_ref: "src/ui/win/dialogs/AboutDialog.cpp:34, make/CMakeLists_sources.txt:542-543, make/CMakeLists_app.txt:634"
note: "The About dialog is in the Windows editor source list only."
- what: "assets/web/, vpx.html and the files it pulls"
source_ref: "lib/src/WebServer.cpp:92-99, make/CMakeLists_sources.txt:817-823"
source_ref: "lib/src/WebServer.cpp:92-99, make/CMakeLists_sources.txt:819-825"
note: "The web server is compiled into the iOS and Android library build only."
user_supplied_paths:
@@ -432,12 +450,12 @@ analysis:
- what: "score view layout, <table basename>.scv, else <table folder name>.scv, else the LayoutFolder setting"
source_ref: "plugins/scoreview/ScoreViewPlugin.cpp:23,46-62"
- what: "table music, music/ beside the table"
source_ref: "src/core/ScriptGlobalTable.cpp:116-130, src/core/FileLocator.cpp:267-377"
source_ref: "src/core/ScriptGlobalTable.cpp:122-136, src/core/FileLocator.cpp:267-377"
note: "In the legacy layout, music/ under the installation folder, then the table folder, then the preferences folder."
- what: "textbox fonts, <table folder>/<FontName>-<Style>.ttf"
source_ref: "src/parts/textbox.cpp:701-722"
- what: "table sidecars, the .vbs script override, the per-table .ini and the legacy .pov"
source_ref: "src/parts/pintable.cpp:1322-1328,1380-1396, src/parts/pintable.h:562-589"
source_ref: "src/parts/pintable.cpp:1325-1331, src/parts/pintable.cpp:1383-1399, src/parts/pintable.h:560-587"
generated_files:
- what: "VPinballX.ini, the settings file"
@@ -448,7 +466,7 @@ analysis:
Batocera seeds it from the assets/Default_VPinballX.ini its pinned tree
carries and rewrites it before every launch.
- what: "VPReg.ini, values saved by table scripts in the table's user folder; vpinball.log in the preferences folder"
source_ref: "src/core/ScriptGlobalTable.cpp:521-551, src/utils/Logger.cpp:164-165"
source_ref: "src/core/ScriptGlobalTable.cpp:527-557, src/utils/Logger.cpp:164-165"
- what: "PinMAME state: nvram/, cfg/, hi/, inp/, memcard/ and sta/"
source_ref: "pinmame src/libpinmame/libpinmame.cpp:1081,1083-1087"
note: "Written by PinMAME as the machine runs."
+9 -7
View File
@@ -4,8 +4,8 @@ core_classification: official_port
source: "https://git.libretro.com/libretro/wqxemu"
upstream: "https://github.com/AloysHF/WQXEmu"
profiled_date: "2026-09-17"
source_commit: "868b644df757379b15a04c7354f8f756d11076c3"
upstream_commit: "7191df429042116e707ba68f5b2d2a8ae567b2ad"
source_commit: "c41a6f26f6e1a5ff2b15f0b63e6103bd8889df22"
upstream_commit: "c41a6f26f6e1a5ff2b15f0b63e6103bd8889df22"
core_version: "0.1.0"
display_name: "Wenquxing - Electronic Dictionary (WQXEmu)"
cores:
@@ -63,10 +63,12 @@ notes: |
The standalone binary takes the same files as explicit --rom, --nor,
--nand and --nand0 paths and has no default directory; --model selects
the machine, otherwise detect_model guesses it from the file sizes
(crates/wqxemu/src/main.rs:27-42, crates/wqxemu/src/main.rs:239-274,
crates/wqxemu/src/main.rs:276-288,
(crates/wqxemu/src/main.rs:27-42, crates/wqxemu/src/main.rs:243-278,
crates/wqxemu/src/main.rs:280-292,
crates/wqxemu-core/src/machines/mod.rs:38-87). Its device skins are
include_bytes! PNGs (crates/wqxemu/src/keypad.rs:59-66).
include_bytes! PNGs, or drawn by code_skin when --skin code is given
(crates/wqxemu/src/main.rs:65-67, crates/wqxemu/src/keypad.rs:86-91,
crates/wqxemu/src/keypad.rs:220-227).
The libretro-core-info file names the PC1000 NOR image pc1000.fls. The
code opens pc1000.nor since commit 0a8f7a5 of 2026-08-23; the v0.1.0
@@ -201,7 +203,7 @@ files:
64 pages of the NAND array. No size check. The libretro core always
puts it in the NC2000 set and the standalone binary requires
--nand0 for this model, so a missing file stops the start.
source_ref: "crates/wqxemu-libretro/src/lib.rs:232-237, crates/wqxemu-core/src/machines/nc2000.rs:24, crates/wqxemu-core/src/machines/nc2000.rs:283-288, crates/wqxemu-core/src/machines/nc2000.rs:298-301, crates/wqxemu/src/main.rs:260-265"
source_ref: "crates/wqxemu-libretro/src/lib.rs:232-237, crates/wqxemu-core/src/machines/nc2000.rs:24, crates/wqxemu-core/src/machines/nc2000.rs:283-288, crates/wqxemu-core/src/machines/nc2000.rs:298-301, crates/wqxemu/src/main.rs:264-269"
- name: nc3000.nor
path: WQXEmu/nc3000/nc3000.nor
@@ -244,4 +246,4 @@ files:
optional for this model. Without it the core writes the marker
"ggv nc3000" at offset 528 of an erased first plane and the machine
boots the same way.
source_ref: "crates/wqxemu-libretro/src/lib.rs:238-246, crates/wqxemu-core/src/machines/nc3000.rs:27-31, crates/wqxemu-core/src/machines/nc3000.rs:290-297, crates/wqxemu-core/src/machines/nc3000.rs:342-350, crates/wqxemu/src/main.rs:266-270"
source_ref: "crates/wqxemu-libretro/src/lib.rs:238-246, crates/wqxemu-core/src/machines/nc3000.rs:27-31, crates/wqxemu-core/src/machines/nc3000.rs:290-297, crates/wqxemu-core/src/machines/nc3000.rs:342-350, crates/wqxemu/src/main.rs:270-274"
-2
View File
@@ -43,7 +43,6 @@ files:
- name: "sega_101.bin"
system: sega-saturn
region: [japan]
priority: 2
required: false
hle_fallback: true
@@ -52,7 +51,6 @@ files:
- name: "mpr-17933.bin"
system: sega-saturn
region: [north-america, europe]
priority: 3
required: false
hle_fallback: true
-2
View File
@@ -44,7 +44,6 @@ files:
- name: "sega_101.bin"
system: sega-saturn
region: [japan]
priority: 2
required: false
hle_fallback: true
@@ -55,7 +54,6 @@ files:
- name: "mpr-17933.bin"
system: sega-saturn
region: [north-america, europe]
priority: 3
required: false
hle_fallback: true
+8 -6
View File
@@ -458,10 +458,11 @@ files:
sha1: 0a16f66d7816d73af3b28dea2bb292415695f6f7
description: "Vulkan texture function shader"
note: >-
Read into the device at creation and specialised per draw. A source
that cannot be read fails GSDeviceVK::Create, and Vulkan is the backend
the libretro core presents through.
source_ref: "pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp:2308-2317, pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp:5349-5364, pcsx2/GS/Renderers/Common/GSDevice.cpp:372-375"
Read from the resources directory into the device at creation
(pcsx2/GS/Renderers/Common/GSDevice.cpp:372-375) and specialised per
draw. A source that cannot be read fails GSDeviceVK::Create, and Vulkan
is the backend the libretro core presents through.
source_ref: "pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp:2308-2317, pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp:5349-5364"
- name: convert.glsl
path: pcsx2/resources/shaders/vulkan/convert.glsl
@@ -828,7 +829,7 @@ files:
Named as the placeholder texture when the fullscreen interface
initialises; that initialisation fails without it and the interface
stays closed.
source_ref: "pcsx2/ImGui/FullscreenUI.cpp:335-341, pcsx2/ImGui/ImGuiFullscreen.cpp:292-300"
source_ref: "pcsx2/ImGui/FullscreenUI.cpp:335-341, pcsx2/ImGui/ImGuiFullscreen.cpp:216-226, 292-300"
- name: no-save.png
path: pcsx2/resources/fullscreenui/no-save.png
@@ -1381,7 +1382,8 @@ files:
size: 656
sha1: d168d39632798f8be799d21bd0a0c9ca13533272
description: "Flag icon, game list region unclassified"
source_ref: "pcsx2/ImGui/FullscreenUI.cpp:2846, pcsx2/GameList.cpp:155-190, pcsx2-qt/GameList/GameListModel.cpp:486"
note: "Opened as icons/flags/Other.svg by the fullscreen game list (pcsx2/ImGui/FullscreenUI.cpp:2846)."
source_ref: "pcsx2/GameList.cpp:155-190, pcsx2-qt/GameList/GameListModel.cpp:486"
- name: pl.svg
path: pcsx2/resources/icons/flags/pl.svg
+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "3ea3a3e21012cc2dbf24c08a0ad314233ade04e62c0d2164b76146f1b361d714"
$defaultInstallSha256 = "42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60"
$maximumInstallerBytes = 2MB
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
$temporary = $null
+16 -6
View File
@@ -954,8 +954,13 @@ def fetch_manifest(plat: str) -> dict:
sys.exit(1)
def fetch_targets(plat: str) -> dict:
"""Download target core list. Returns empty dict on 404."""
def fetch_targets(plat: str) -> "dict | None":
"""Download the target core list.
Empty on 404, where the platform publishes no targets. None when the list
could not be read: a timeout says nothing about the platform, and
answering "no targets" for it sent users to the full pack.
"""
url = TARGETS_URL.format(platform=plat)
try:
with urllib.request.urlopen(url, timeout=30) as resp:
@@ -965,10 +970,11 @@ def fetch_targets(plat: str) -> dict:
except urllib.error.HTTPError as exc:
if exc.code == 404:
return {}
print(f" Warning: failed to fetch targets for {plat}: {exc}", file=sys.stderr)
return {}
except (urllib.error.URLError, OSError, ValueError):
return {}
print(f" Error: failed to fetch targets for {plat}: {exc}", file=sys.stderr)
return None
except (urllib.error.URLError, OSError, ValueError) as exc:
print(f" Error: failed to fetch targets for {plat}: {exc}", file=sys.stderr)
return None
def _filter_by_target(
@@ -1580,6 +1586,8 @@ def main() -> None:
if args.list_targets:
targets = fetch_targets(plat_name)
if targets is None:
sys.exit(1)
if not targets:
print(f" No targets available for {plat_name}")
else:
@@ -1592,6 +1600,8 @@ def main() -> None:
# Target filtering
if args.target:
targets = fetch_targets(plat_name)
if targets is None:
sys.exit(1)
target_info = targets.get(args.target)
if target_info is None:
# Carrying on would install every file, which is the opposite
+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="3ea3a3e21012cc2dbf24c08a0ad314233ade04e62c0d2164b76146f1b361d714"
DEFAULT_INSTALL_SHA256="42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+416 -346
View File
File diff suppressed because it is too large. Load diff
+33 -44
View File
@@ -5,7 +5,7 @@
"platform": "bizhawk",
"display_name": "BizHawk",
"version": "1.0",
"generated": "2026-10-05T20:14:26Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "Firmware",
"detect": [
{
@@ -20,33 +20,12 @@
}
],
"standalone_copies": [],
"total_files": 816,
"total_size": 2570871683,
"pack_files": 818,
"pack_size": 2570871683,
"total_omitted": 2,
"omitted_files": [
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
}
],
"total_files": 817,
"total_size": 2571002755,
"pack_files": 819,
"pack_size": 2571002755,
"total_omitted": 0,
"omitted_files": [],
"files": [
{
"dest": "panafz1.bin",
@@ -3422,30 +3401,20 @@
},
{
"dest": "dsi_bios9.bin",
"sha1": "db61fa39ddbc5f5ed71fb19cda47609ef0201723",
"sha256": "664c608bd6ccfe583e87b3b924afa00f8697dc7ca92eeb6d206d68abb03d2e10",
"sha1": "719b9eef33692406d7170ff526069615759c4dfc",
"sha256": "6e9f70b51dc4f26289b597f14e2513620c2af964374a95f4c5a62b5cad199afc",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios9.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios9i.bin",
"cores": [
"melonds"
]
},
{
"dest": "dsi_bios7.bin",
"sha1": "d2b17224e156ee5e55b5d8d9f323b1c9b86dd951",
"sha256": "28a13952901b4065ef3f08c3252b49a00b6525e301b7ddaa3a390b4c7cbef901",
"sha1": "c7c7570bfe51c3c7c5da3b01331b94e7e7cb4f53",
"sha256": "12a03016ba07cfd874fc002df49a2c6eb2414eb7cfa395c2043035ec5e9193e8",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios7.bin",
"cores": [
"melonds"
]
},
{
"dest": "dsi_firmware.bin",
"sha1": "d2a5af338f09c5cbdd5d7628db5b9c075c69b616",
"sha256": "1d7c771de535e6e07182fc9820ac32e76c6f0b5bcefd256a861fdfab144761ec",
"size": 131072,
"repo_path": "bios/Nintendo/Nintendo DS/dsi_firmware.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios7i.bin",
"cores": [
"melonds"
]
@@ -6840,6 +6809,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": "cchip.zip",
"sha1": "364f2302a145a0fd6de767d7f8484badde1d1a6e",
+96 -206
View File
@@ -5,7 +5,7 @@
"platform": "emudeck",
"display_name": "EmuDeck",
"version": "1.0",
"generated": "2026-10-05T20:47:58Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "bios",
"detect": [
{
@@ -250,10 +250,10 @@
}
}
],
"total_files": 580,
"total_size": 3467876435,
"pack_files": 582,
"pack_size": 3467876435,
"total_files": 569,
"total_size": 3452229095,
"pack_files": 571,
"pack_size": 3452229095,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -591,10 +591,10 @@
},
{
"dest": "Cemu/otp.bin",
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
"size": 256,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
"sha1": "aa4ebc7f94c22b04945b6c9232547ada10b71f6f",
"sha256": "a12993f7fe069098ab6fe1cdb43099a1dfb568223bef08ed5cb43ec93d9ff2de",
"size": 1024,
"repo_path": "bios/Nintendo/Wii U/otp.bin",
"cores": [
"cemu"
]
@@ -3513,20 +3513,20 @@
},
{
"dest": "dsi_bios9.bin",
"sha1": "db61fa39ddbc5f5ed71fb19cda47609ef0201723",
"sha256": "664c608bd6ccfe583e87b3b924afa00f8697dc7ca92eeb6d206d68abb03d2e10",
"sha1": "719b9eef33692406d7170ff526069615759c4dfc",
"sha256": "6e9f70b51dc4f26289b597f14e2513620c2af964374a95f4c5a62b5cad199afc",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios9.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios9i.bin",
"cores": [
"melonds"
]
},
{
"dest": "dsi_bios7.bin",
"sha1": "d2b17224e156ee5e55b5d8d9f323b1c9b86dd951",
"sha256": "28a13952901b4065ef3f08c3252b49a00b6525e301b7ddaa3a390b4c7cbef901",
"sha1": "c7c7570bfe51c3c7c5da3b01331b94e7e7cb4f53",
"sha256": "12a03016ba07cfd874fc002df49a2c6eb2414eb7cfa395c2043035ec5e9193e8",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios7.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios7i.bin",
"cores": [
"melonds"
]
@@ -3645,20 +3645,20 @@
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "a439fbb390da38cada95a7cbb1d6ca199cd66ef8",
"sha256": "08d68695e5bff6bbcbb751d6e4453db9f5ead0b5f41e86480798a04376dfe090",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
"sha256": "a73a06c23ed38370e58a11fb1b86f7ea4c547061a60d7aa62bae446235fd2dff",
"size": 65536,
"repo_path": "bios/Commodore/Amiga/mt32-roms/cm32l_control.rom",
"repo_path": "bios/Commodore/Amiga/mt32-roms/mt32_control.rom",
"cores": [
"scummvm"
]
},
{
"dest": "scummvm/extra/MT32_PCM.ROM",
"sha1": "289cc298ad532b702461bfc738009d9ebe8025ea",
"sha256": "0dc50190bb7ea3fb212c970346b771a511772b528801e7eda6d27efda8c40698",
"size": 1048576,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_cm32l.rom",
"sha1": "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252",
"sha256": "d9164063f293410cf33f2f64cdcea6893b44723fe41938e07ec9aef58b406238",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_mt32.rom",
"cores": [
"scummvm"
]
@@ -4781,6 +4781,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-30004R.bin",
"sha1": "8fa040852d4b8688f0c84bcfffc65eb208f2b432",
@@ -4811,6 +4821,36 @@
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.BIN",
"sha1": "434bc0b4eb4827da0773ec0795aadc5162569a07",
@@ -4821,6 +4861,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.BIN",
"sha1": "cce6fac0f7e682ad167e1e828b2d53192c3d5051",
@@ -4831,6 +4881,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0100jd-20000117.bin",
"sha1": "5b33170323ed6344e2363fed8115dc3918bb96a4",
@@ -5571,6 +5631,16 @@
"pcsx2"
]
},
{
"dest": "ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
"sha256": "e017efa804f1f7d4d3da82b67d7370c5be062b4860f1cff4f898f93437d691a0",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/ps2-0230a-20080220.nvm",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0230e-20080220.bin",
"sha1": "9915b5ba56798f4027ac1bd8d10abe0c1c9c326a",
@@ -5621,46 +5691,6 @@
"pcsx2"
]
},
{
"dest": "GameIndex.yaml",
"sha1": "57369df9ca82a460e596cea7835c5a0797dee668",
"sha256": "efa94cb7b9208dc6812bb9407dc986560e523cce57f511eff30942f76b7288bf",
"size": 1737799,
"repo_path": "bios/Sony/PlayStation 2/.variants/GameIndex.yaml.57369df9",
"cores": [
"pcsx2"
]
},
{
"dest": "ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Sony/PlayStation 2/.variants/ROM2.BIN.5d5ab431",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH30004R.nvm",
"sha1": "1d3caaed7411425c16aee8c35db47b928e31f60b",
"sha256": "c598dea13e5bba2fcae69de5b051c15fcd5b25842978be1bb20a86cfff243eaf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/.variants/SCPH30004R.nvm.5e0200d0",
"cores": [
"pcsx2"
]
},
{
"dest": "patches.zip",
"sha1": "2562e542d66db945ccf158d7a221c2a28e3b6be4",
"sha256": "df2b384ec29221155e8151fa2ff2dc8f0eef2409a8dce5ee7f7514352ae4058a",
"size": 2165576,
"repo_path": "bios/Sony/PlayStation 2/.variants/patches.zip.5a4d0a00",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-dvd-110u.bin",
"sha1": "0bc9143cb6839471d62b8fdd0b2fc05e941527d3",
@@ -5792,81 +5822,11 @@
]
},
{
"dest": "EROM.BIN",
"sha1": "adb4dbb7feb8247811506a38896ddcb789c4b477",
"sha256": "f7c4944e3001e3849cdead743e200e009d4d07bd5afb20fb937a7b201895f713",
"size": 247968,
"repo_path": "bios/Sony/PlayStation 2/EROM.BIN",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.EROM",
"sha1": "2ed11fd9333e62e36659565a744330a5902ed5e3",
"sha256": "67a6b2779e9320abe24f75ce811acf29e54d6dc06ce65b11341b6d84103de7f8",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"dest": "SCPH30004R.nvm",
"sha1": "1d3caaed7411425c16aee8c35db47b928e31f60b",
"sha256": "c598dea13e5bba2fcae69de5b051c15fcd5b25842978be1bb20a86cfff243eaf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.EROM",
"sha1": "ec0121bb8e2a209044309780d2efc2559c9a0ef1",
"sha256": "e5bcd5925c1f22de48d49c3895dd421d167faddc033aac628f5facfba90ceef3",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"repo_path": "bios/Sony/PlayStation 2/.variants/SCPH30004R.nvm.5e0200d0",
"cores": [
"pcsx2"
]
@@ -5881,16 +5841,6 @@
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-75008_BIOS_V14_RUS_220.NVM",
"sha1": "fff13c6e30c55df9d6241ee08e5d0026c6a5d618",
@@ -5911,16 +5861,6 @@
"pcsx2"
]
},
{
"dest": "SCPH-90006_BIOS_VX_HK _230.EROM",
"sha1": "6b357e8624095770952fc408cdc6647fa62e0d28",
"sha256": "8801922f614b365d9d37557d7a375d06d385e936804943cb2c52abff55a3d254",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-90006_BIOS_VX_HK _230.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-90006_BIOS_VX_HK _230.NVM",
"sha1": "465c2738c280eb1864f679c80b55e468bf670057",
@@ -5941,56 +5881,6 @@
"pcsx2"
]
},
{
"dest": "cheats_ni.zip",
"sha1": "dec5e4f137890c338babe13cfb9d41ff869fa721",
"sha256": "91d5ae704ff8b6d90165a65f69e9e2be8a9ab9c588c525dc1c9c4ecbbdef1ffd",
"size": 42525,
"repo_path": "bios/Sony/PlayStation 2/cheats_ni.zip",
"cores": [
"pcsx2"
]
},
{
"dest": "cheats_ws.zip",
"sha1": "773b4279d8c8e182dc57e444dbf448a1d115e7e0",
"sha256": "d07e3adde4da6a3220363b12d7b066168918225546122643ef7d19fece53b8b1",
"size": 1273296,
"repo_path": "bios/Sony/PlayStation 2/cheats_ws.zip",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
"sha256": "e017efa804f1f7d4d3da82b67d7370c5be062b4860f1cff4f898f93437d691a0",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/ps2-0230a-20080220.nvm",
"cores": [
"pcsx2"
]
},
{
"dest": "message.wav",
"sha1": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"sha256": "dfd8035387a463722ad07af3c91934e0a685f2afa57e31d305f0602656d1edb1",
"size": 15696,
"repo_path": "bios/Sony/PlayStation 2/resources/sounds/achievements/message.wav",
"cores": [
"pcsx2"
]
},
{
"dest": "rom1.bin",
"sha1": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"sha256": "41de2047af8382988bfd568035ff26eec5f1cabc3efe773680546aa00a82857d",
"size": 199680,
"repo_path": "bios/Sony/PlayStation 2/rom1.bin",
"cores": [
"pcsx2"
]
},
{
"dest": "scph30004fr.nvm",
"sha1": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
+248 -209
View File
@@ -5,7 +5,7 @@
"platform": "lakka",
"display_name": "Lakka",
"version": "1.0",
"generated": "2026-10-05T22:37:20Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "system",
"detect": [
{
@@ -16,153 +16,12 @@
}
],
"standalone_copies": [],
"total_files": 5849,
"total_size": 6395485306,
"pack_files": 8492,
"pack_size": 6436018470,
"total_omitted": 14,
"omitted_files": [
{
"dest": "SC-55/WAVEROM3.BIN",
"name": "WAVEROM3.BIN",
"system": "dos",
"required": false,
"reason": "not_found",
"cores": [
"dosbox"
]
},
{
"dest": "SufamiTurbo.sfc",
"name": "SufamiTurbo.sfc",
"system": "nintendo-sufami-turbo",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Games Express CD Card (Japan).pce",
"name": "[BIOS] Games Express CD Card (Japan).pce",
"system": "nec-pc-engine-cd",
"required": false,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"name": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"system": "nec-pc-engine-cd",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp1.rom",
"name": "dsp1.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp1b.rom",
"name": "dsp1b.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp2.rom",
"name": "dsp2.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp3.rom",
"name": "dsp3.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp4.rom",
"name": "dsp4.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "st010.rom",
"name": "st010.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "st011.rom",
"name": "st011.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "st018.rom",
"name": "st018.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
}
],
"total_files": 5867,
"total_size": 6399851574,
"pack_files": 8510,
"pack_size": 6440384738,
"total_omitted": 0,
"omitted_files": [],
"files": [
{
"dest": "3do_arcade_saot.bin",
@@ -4256,10 +4115,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "40ecf6138c99a0aba775ef93240b295025a45500",
"sha256": "483face8f50f5e96d89528059522be3766b44f5e07e854bae1fa92ff0111d0a7",
"size": 2665743,
"repo_path": "bios/Sony/PlayStation 2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"cores": null
},
{
@@ -5892,10 +5751,10 @@
},
{
"dest": "Cemu/otp.bin",
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
"size": 256,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
"sha1": "aa4ebc7f94c22b04945b6c9232547ada10b71f6f",
"sha256": "a12993f7fe069098ab6fe1cdb43099a1dfb568223bef08ed5cb43ec93d9ff2de",
"size": 1024,
"repo_path": "bios/Nintendo/Wii U/otp.bin",
"cores": [
"cemu"
]
@@ -6162,6 +6021,16 @@
"dosbox"
]
},
{
"dest": "SC-55/WAVEROM3.BIN",
"sha1": "7454b817778179806f3f9d1985b3a2ef67ace76f",
"sha256": "334b2d16be3c2362210fdbec1c866ad58badeb0f84fd9bf5d0ac599baf077cc2",
"size": 1048576,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55-v1.00/sc55_waverom3.bin",
"cores": [
"dosbox"
]
},
{
"dest": "CM32LN_CONTROL.ROM",
"sha1": "dc1c5b1b90a4646d00f7daf3679733c7badc7077",
@@ -25590,6 +25459,96 @@
"mesen"
]
},
{
"dest": "dsp1.rom",
"sha1": "188d471fefea71eb53f0ee7064697ff0971b1014",
"sha256": "91e87d11e1c30d172556bed2211cce2efa94ba595f58c5d264809ef4d363a97b",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp1.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp1b.rom",
"sha1": "78b724811f5f18d8c67669d9390397eb1a47a5e2",
"sha256": "d789cb3c36b05c0b23b6c6f23be7aa37c6e78b6ee9ceac8d2d2aa9d8c4d35fa9",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp1b.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp2.rom",
"sha1": "198c4b1c3bfc6d69e734c5957af3dbfa26238dfb",
"sha256": "03ef4ef26c9f701346708cb5d07847b5203cf1b0818bf2930acd34510ffdd717",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp2.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp3.rom",
"sha1": "558da7cb3bd3876a6ca693661ffc6c110e948cf9",
"sha256": "0971b08f396c32e61989d1067dddf8e4b14649d548b2188f7c541b03d7c69e4e",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp3.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp4.rom",
"sha1": "af6478aecb6f1b67177e79c82ca04c56250a8c72",
"sha256": "752d03b2d74441e430b7f713001fa241f8bbcfc1a0d890ed4143f174dbe031da",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp4.rom",
"cores": [
"mesence"
]
},
{
"dest": "st010.rom",
"sha1": "6472828403de3589433a906e2c3f3d274c0ff008",
"sha256": "fa9bced838fedea11c6f6ace33d1878024bdd0d02cc9485899d0bdd4015ec24c",
"size": 53248,
"repo_path": "bios/Nintendo/SNES/SNES_st010.rom",
"cores": [
"mesence"
]
},
{
"dest": "st011.rom",
"sha1": "fecbae2cec76c710422486baa186ffa7ca1cf925",
"sha256": "8b2b3f3f3e6e29f4d21d8bc736b400bc988b7d2214ebee15643f01c1fee2f364",
"size": 53248,
"repo_path": "bios/Nintendo/SNES/SNES_st011.rom",
"cores": [
"mesence"
]
},
{
"dest": "st018.rom",
"sha1": "91383b92745cc7cc4f15409ac5bc2c2f699a43f1",
"sha256": "6df209ab5d2524d1839c038be400ae5eb20dafc14a3771a3239cd9e8acd53806",
"size": 163840,
"repo_path": "bios/Nintendo/SNES/SNES_st018.rom",
"cores": [
"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",
@@ -25620,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",
@@ -47512,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"
]
@@ -55406,6 +55385,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": "WQXEmu/nc1020/obj_lu.bin",
"sha1": "64b8a315ecbc420ae594ccced43498392988669e",
@@ -56490,20 +56489,20 @@
},
{
"dest": "SC-55/ROM1.BIN",
"sha1": "aea7a4c0c7ffe1f212f7b9faecfd728862ac6904",
"sha256": "d7f03bf65c1f98c9c1226f4e1d1ecd4ac778a2dc4a332568ab0d5fee7ede9042",
"size": 4096,
"repo_path": "bios/Galaksija/ROM1.BIN",
"sha1": "b91bb1d9dccffe831b7cfde7800a3fe32b2fbda6",
"sha256": "8a1eb33c7599b746c0c50283e4349a1bb1773b5c0ec0e9661219bf6c067d2042",
"size": 32768,
"repo_path": "bios/Roland/soundcanvas-roms/SC-55mk2-v1.01/rom1.bin",
"cores": [
"dosbox"
]
},
{
"dest": "SC-55/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"sha1": "078cb5feea05e80bb9a1bb857a2163ee434fd053",
"sha256": "a4c9fd821059054c7e7681d61f49ce6f42ed2fe407a7ec1ba0dfdc9722582ce0",
"size": 524288,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55mk2-v1.01/rom2.bin",
"cores": [
"dosbox"
]
@@ -56570,10 +56569,10 @@
},
{
"dest": "np2/bios.rom",
"sha1": "1f5f7013f18c08ff50d7942e76c4fbd782412414",
"sha256": "863df0975a305110a7da4591b704e8f5dc7302f03c63603aa2dcd03e84c36ff9",
"size": 65536,
"repo_path": "bios/IBM/PC/ibmpcjr/bios.rom",
"sha1": "76a7360502fa0296ea93b4c537174610a834d367",
"sha256": "d0f5b6d96a01295ab5436d7852d2c9eea1faa87fcf016b7e6599cdd689266cd2",
"size": 98304,
"repo_path": "bios/NEC/PC-98/bios.rom",
"cores": [
"nekop2"
]
@@ -56600,20 +56599,20 @@
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "a439fbb390da38cada95a7cbb1d6ca199cd66ef8",
"sha256": "08d68695e5bff6bbcbb751d6e4453db9f5ead0b5f41e86480798a04376dfe090",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
"sha256": "a73a06c23ed38370e58a11fb1b86f7ea4c547061a60d7aa62bae446235fd2dff",
"size": 65536,
"repo_path": "bios/Commodore/Amiga/mt32-roms/cm32l_control.rom",
"repo_path": "bios/Commodore/Amiga/mt32-roms/mt32_control.rom",
"cores": [
"scummvm"
]
},
{
"dest": "scummvm/extra/MT32_PCM.ROM",
"sha1": "289cc298ad532b702461bfc738009d9ebe8025ea",
"sha256": "0dc50190bb7ea3fb212c970346b771a511772b528801e7eda6d27efda8c40698",
"size": 1048576,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_cm32l.rom",
"sha1": "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252",
"sha256": "d9164063f293410cf33f2f64cdcea6893b44723fe41938e07ec9aef58b406238",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_mt32.rom",
"cores": [
"scummvm"
]
@@ -57250,6 +57249,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-30004R.bin",
"sha1": "8fa040852d4b8688f0c84bcfffc65eb208f2b432",
@@ -57280,6 +57289,36 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70004.BIN",
"sha1": "434bc0b4eb4827da0773ec0795aadc5162569a07",
@@ -57290,6 +57329,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.BIN",
"sha1": "cce6fac0f7e682ad167e1e828b2d53192c3d5051",
@@ -57300,6 +57349,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/ps2-0160h-20030602.bin",
"sha1": "f03bc2b7e7722d1788c9e7a341e43ac3ffe33a47",
@@ -57550,16 +57609,6 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70006_BIOS_V12_HK_200.NVM",
"sha1": "06ccc54cdbe8b017a629155ccb66310cba9c4720",
@@ -57570,16 +57619,6 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-75008_BIOS_V14_RUS_220.NVM",
"sha1": "fff13c6e30c55df9d6241ee08e5d0026c6a5d618",
@@ -57610,6 +57649,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-90006_BIOS_VX_HK _230.ROM1",
"sha1": "b96cf94772f01b8434038285896467884a84b0c2",
"sha256": "1b7c68be427e6f571596ef6b444e8b86d1b4e2a226d62f6bfc86717a798f2317",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-90006_BIOS_VX_HK _230.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
@@ -57639,16 +57688,6 @@
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"yaps2"
]
}
]
}
+159 -259
View File
@@ -5,7 +5,7 @@
"platform": "recalbox",
"display_name": "Recalbox",
"version": "1.0",
"generated": "2026-10-05T22:37:20Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "bios",
"detect": [
{
@@ -237,22 +237,12 @@
}
}
],
"total_files": 3236,
"total_size": 4205115053,
"pack_files": 3238,
"pack_size": 4205115053,
"total_omitted": 4,
"total_files": 3229,
"total_size": 4193670403,
"pack_files": 3231,
"pack_size": 4193670403,
"total_omitted": 1,
"omitted_files": [
{
"dest": "SC-55/WAVEROM3.BIN",
"name": "WAVEROM3.BIN",
"system": "dos",
"required": false,
"reason": "not_found",
"cores": [
"dosbox"
]
},
{
"dest": "geolith/neocd.zip",
"name": "neocd.zip",
@@ -260,26 +250,6 @@
"required": true,
"reason": "hash_mismatch",
"cores": null
},
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
}
],
"files": [
@@ -8867,20 +8837,20 @@
},
{
"dest": "SC-55/ROM1.BIN",
"sha1": "aea7a4c0c7ffe1f212f7b9faecfd728862ac6904",
"sha256": "d7f03bf65c1f98c9c1226f4e1d1ecd4ac778a2dc4a332568ab0d5fee7ede9042",
"size": 4096,
"repo_path": "bios/Galaksija/ROM1.BIN",
"sha1": "b91bb1d9dccffe831b7cfde7800a3fe32b2fbda6",
"sha256": "8a1eb33c7599b746c0c50283e4349a1bb1773b5c0ec0e9661219bf6c067d2042",
"size": 32768,
"repo_path": "bios/Roland/soundcanvas-roms/SC-55mk2-v1.01/rom1.bin",
"cores": [
"dosbox"
]
},
{
"dest": "SC-55/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"sha1": "078cb5feea05e80bb9a1bb857a2163ee434fd053",
"sha256": "a4c9fd821059054c7e7681d61f49ce6f42ed2fe407a7ec1ba0dfdc9722582ce0",
"size": 524288,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55mk2-v1.01/rom2.bin",
"cores": [
"dosbox"
]
@@ -8915,6 +8885,16 @@
"dosbox"
]
},
{
"dest": "SC-55/WAVEROM3.BIN",
"sha1": "7454b817778179806f3f9d1985b3a2ef67ace76f",
"sha256": "334b2d16be3c2362210fdbec1c866ad58badeb0f84fd9bf5d0ac599baf077cc2",
"size": 1048576,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55-v1.00/sc55_waverom3.bin",
"cores": [
"dosbox"
]
},
{
"dest": "libbass.so",
"sha1": "415365e930b4c7d33e9af3225bea230e15bdebe7",
@@ -24205,20 +24185,20 @@
},
{
"dest": "dsi_bios9.bin",
"sha1": "db61fa39ddbc5f5ed71fb19cda47609ef0201723",
"sha256": "664c608bd6ccfe583e87b3b924afa00f8697dc7ca92eeb6d206d68abb03d2e10",
"sha1": "719b9eef33692406d7170ff526069615759c4dfc",
"sha256": "6e9f70b51dc4f26289b597f14e2513620c2af964374a95f4c5a62b5cad199afc",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios9.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios9i.bin",
"cores": [
"melonds"
]
},
{
"dest": "dsi_bios7.bin",
"sha1": "d2b17224e156ee5e55b5d8d9f323b1c9b86dd951",
"sha256": "28a13952901b4065ef3f08c3252b49a00b6525e301b7ddaa3a390b4c7cbef901",
"sha1": "c7c7570bfe51c3c7c5da3b01331b94e7e7cb4f53",
"sha256": "12a03016ba07cfd874fc002df49a2c6eb2414eb7cfa395c2043035ec5e9193e8",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios7.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios7i.bin",
"cores": [
"melonds"
]
@@ -25045,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"
]
@@ -28675,10 +28655,10 @@
},
{
"dest": "gamecontrollerdb.txt",
"sha1": "446e3d7138becc17d3bc31401a241247318f42a9",
"sha256": "d65433feb3602d056900016cda40cf581f00289f179da336613b55ae3136afe2",
"size": 26890,
"repo_path": "bios/Game Engines/C-Dogs SDL/cdogs/data/gamecontrollerdb.txt",
"sha1": "ab035b7804089690018318e4a05f9a8fc59421ff",
"sha256": "eb002773dc8a16aa96f9ee2609798e231a9deb60c45e21fbdd4e221c9e8b7d77",
"size": 600521,
"repo_path": "bios/Game Engines/Solarus/gamecontrollerdb.txt",
"cores": [
"solarus"
]
@@ -28953,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",
@@ -29043,6 +29043,16 @@
"visual-pinball"
]
},
{
"dest": "assets/fabian-hrir.sofa",
"sha1": "5bf46cfb6472b81f4aa7fb2c87bc79afc23109d7",
"sha256": "f22e921a6bc49a78c6d2eb3db963e9e12b4c4c5a77adeba2b786c38be37e6636",
"size": 1766147,
"repo_path": "bios/Other/visual-pinball/assets/fabian-hrir.sofa",
"cores": [
"visual-pinball"
]
},
{
"dest": "xmil/FNT0808.X1",
"sha1": "1c1a0d8c9f4c446ccd7470516b215ddca5052fb2",
@@ -29945,20 +29955,20 @@
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "a439fbb390da38cada95a7cbb1d6ca199cd66ef8",
"sha256": "08d68695e5bff6bbcbb751d6e4453db9f5ead0b5f41e86480798a04376dfe090",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
"sha256": "a73a06c23ed38370e58a11fb1b86f7ea4c547061a60d7aa62bae446235fd2dff",
"size": 65536,
"repo_path": "bios/Commodore/Amiga/mt32-roms/cm32l_control.rom",
"repo_path": "bios/Commodore/Amiga/mt32-roms/mt32_control.rom",
"cores": [
"scummvm"
]
},
{
"dest": "scummvm/extra/MT32_PCM.ROM",
"sha1": "289cc298ad532b702461bfc738009d9ebe8025ea",
"sha256": "0dc50190bb7ea3fb212c970346b771a511772b528801e7eda6d27efda8c40698",
"size": 1048576,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_cm32l.rom",
"sha1": "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252",
"sha256": "d9164063f293410cf33f2f64cdcea6893b44723fe41938e07ec9aef58b406238",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_mt32.rom",
"cores": [
"scummvm"
]
@@ -30727,6 +30737,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-30004R.bin",
"sha1": "8fa040852d4b8688f0c84bcfffc65eb208f2b432",
@@ -30757,6 +30777,36 @@
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.BIN",
"sha1": "434bc0b4eb4827da0773ec0795aadc5162569a07",
@@ -30767,6 +30817,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.BIN",
"sha1": "cce6fac0f7e682ad167e1e828b2d53192c3d5051",
@@ -30777,6 +30837,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0100jd-20000117.bin",
"sha1": "5b33170323ed6344e2363fed8115dc3918bb96a4",
@@ -31517,6 +31587,16 @@
"pcsx2"
]
},
{
"dest": "ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
"sha256": "e017efa804f1f7d4d3da82b67d7370c5be062b4860f1cff4f898f93437d691a0",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/ps2-0230a-20080220.nvm",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0230e-20080220.bin",
"sha1": "9915b5ba56798f4027ac1bd8d10abe0c1c9c326a",
@@ -31567,46 +31647,6 @@
"pcsx2"
]
},
{
"dest": "GameIndex.yaml",
"sha1": "57369df9ca82a460e596cea7835c5a0797dee668",
"sha256": "efa94cb7b9208dc6812bb9407dc986560e523cce57f511eff30942f76b7288bf",
"size": 1737799,
"repo_path": "bios/Sony/PlayStation 2/.variants/GameIndex.yaml.57369df9",
"cores": [
"pcsx2"
]
},
{
"dest": "ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Sony/PlayStation 2/.variants/ROM2.BIN.5d5ab431",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH30004R.nvm",
"sha1": "1d3caaed7411425c16aee8c35db47b928e31f60b",
"sha256": "c598dea13e5bba2fcae69de5b051c15fcd5b25842978be1bb20a86cfff243eaf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/.variants/SCPH30004R.nvm.5e0200d0",
"cores": [
"pcsx2"
]
},
{
"dest": "patches.zip",
"sha1": "2562e542d66db945ccf158d7a221c2a28e3b6be4",
"sha256": "df2b384ec29221155e8151fa2ff2dc8f0eef2409a8dce5ee7f7514352ae4058a",
"size": 2165576,
"repo_path": "bios/Sony/PlayStation 2/.variants/patches.zip.5a4d0a00",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-dvd-110u.bin",
"sha1": "0bc9143cb6839471d62b8fdd0b2fc05e941527d3",
@@ -31738,81 +31778,11 @@
]
},
{
"dest": "EROM.BIN",
"sha1": "adb4dbb7feb8247811506a38896ddcb789c4b477",
"sha256": "f7c4944e3001e3849cdead743e200e009d4d07bd5afb20fb937a7b201895f713",
"size": 247968,
"repo_path": "bios/Sony/PlayStation 2/EROM.BIN",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.EROM",
"sha1": "2ed11fd9333e62e36659565a744330a5902ed5e3",
"sha256": "67a6b2779e9320abe24f75ce811acf29e54d6dc06ce65b11341b6d84103de7f8",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"dest": "SCPH30004R.nvm",
"sha1": "1d3caaed7411425c16aee8c35db47b928e31f60b",
"sha256": "c598dea13e5bba2fcae69de5b051c15fcd5b25842978be1bb20a86cfff243eaf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.EROM",
"sha1": "ec0121bb8e2a209044309780d2efc2559c9a0ef1",
"sha256": "e5bcd5925c1f22de48d49c3895dd421d167faddc033aac628f5facfba90ceef3",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"repo_path": "bios/Sony/PlayStation 2/.variants/SCPH30004R.nvm.5e0200d0",
"cores": [
"pcsx2"
]
@@ -31827,16 +31797,6 @@
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-75008_BIOS_V14_RUS_220.NVM",
"sha1": "fff13c6e30c55df9d6241ee08e5d0026c6a5d618",
@@ -31857,16 +31817,6 @@
"pcsx2"
]
},
{
"dest": "SCPH-90006_BIOS_VX_HK _230.EROM",
"sha1": "6b357e8624095770952fc408cdc6647fa62e0d28",
"sha256": "8801922f614b365d9d37557d7a375d06d385e936804943cb2c52abff55a3d254",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-90006_BIOS_VX_HK _230.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-90006_BIOS_VX_HK _230.NVM",
"sha1": "465c2738c280eb1864f679c80b55e468bf670057",
@@ -31887,66 +31837,6 @@
"pcsx2"
]
},
{
"dest": "cheats_ni.zip",
"sha1": "dec5e4f137890c338babe13cfb9d41ff869fa721",
"sha256": "91d5ae704ff8b6d90165a65f69e9e2be8a9ab9c588c525dc1c9c4ecbbdef1ffd",
"size": 42525,
"repo_path": "bios/Sony/PlayStation 2/cheats_ni.zip",
"cores": [
"pcsx2"
]
},
{
"dest": "cheats_ws.zip",
"sha1": "773b4279d8c8e182dc57e444dbf448a1d115e7e0",
"sha256": "d07e3adde4da6a3220363b12d7b066168918225546122643ef7d19fece53b8b1",
"size": 1273296,
"repo_path": "bios/Sony/PlayStation 2/cheats_ws.zip",
"cores": [
"pcsx2"
]
},
{
"dest": "eeprom.dat",
"sha1": "ffc6261e487efa8c7442069f71acfc4aa826993d",
"sha256": "8667e718294e9e0df1d30600ba3eeb201f764aad2dad72748643e4a285e1d1f7",
"size": 64,
"repo_path": "bios/Sony/PlayStation 2/eeprom.dat",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
"sha256": "e017efa804f1f7d4d3da82b67d7370c5be062b4860f1cff4f898f93437d691a0",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/ps2-0230a-20080220.nvm",
"cores": [
"pcsx2"
]
},
{
"dest": "message.wav",
"sha1": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"sha256": "dfd8035387a463722ad07af3c91934e0a685f2afa57e31d305f0602656d1edb1",
"size": 15696,
"repo_path": "bios/Sony/PlayStation 2/resources/sounds/achievements/message.wav",
"cores": [
"pcsx2"
]
},
{
"dest": "rom1.bin",
"sha1": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"sha256": "41de2047af8382988bfd568035ff26eec5f1cabc3efe773680546aa00a82857d",
"size": 199680,
"repo_path": "bios/Sony/PlayStation 2/rom1.bin",
"cores": [
"pcsx2"
]
},
{
"dest": "scph30004fr.nvm",
"sha1": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -31966,6 +31856,16 @@
"cores": [
"pcsx2"
]
},
{
"dest": "eeprom.dat",
"sha1": "ffc6261e487efa8c7442069f71acfc4aa826993d",
"sha256": "8667e718294e9e0df1d30600ba3eeb201f764aad2dad72748643e4a285e1d1f7",
"size": 64,
"repo_path": "bios/Sony/PlayStation 2/eeprom.dat",
"cores": [
"pcsx2"
]
}
]
}
+248 -209
View File
@@ -5,7 +5,7 @@
"platform": "retroarch",
"display_name": "RetroArch",
"version": "1.0",
"generated": "2026-10-05T22:37:20Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "system",
"detect": [
{
@@ -255,153 +255,12 @@
}
}
],
"total_files": 5849,
"total_size": 6395485306,
"pack_files": 8492,
"pack_size": 6436018470,
"total_omitted": 14,
"omitted_files": [
{
"dest": "SC-55/WAVEROM3.BIN",
"name": "WAVEROM3.BIN",
"system": "dos",
"required": false,
"reason": "not_found",
"cores": [
"dosbox"
]
},
{
"dest": "SufamiTurbo.sfc",
"name": "SufamiTurbo.sfc",
"system": "nintendo-sufami-turbo",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Games Express CD Card (Japan).pce",
"name": "[BIOS] Games Express CD Card (Japan).pce",
"system": "nec-pc-engine-cd",
"required": false,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"name": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"system": "nec-pc-engine-cd",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp1.rom",
"name": "dsp1.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp1b.rom",
"name": "dsp1b.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp2.rom",
"name": "dsp2.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp3.rom",
"name": "dsp3.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp4.rom",
"name": "dsp4.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "st010.rom",
"name": "st010.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "st011.rom",
"name": "st011.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "st018.rom",
"name": "st018.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
}
],
"total_files": 5867,
"total_size": 6399851574,
"pack_files": 8510,
"pack_size": 6440384738,
"total_omitted": 0,
"omitted_files": [],
"files": [
{
"dest": "3do_arcade_saot.bin",
@@ -4495,10 +4354,10 @@
},
{
"dest": "pcsx2/resources/GameIndex.yaml",
"sha1": "40ecf6138c99a0aba775ef93240b295025a45500",
"sha256": "483face8f50f5e96d89528059522be3766b44f5e07e854bae1fa92ff0111d0a7",
"size": 2665743,
"repo_path": "bios/Sony/PlayStation 2/resources/GameIndex.yaml",
"sha1": "7efb2ed721cf0c0f0677324e09be311a8337e793",
"sha256": "3bf3c54d6f16d88d3669be698179bb8c2fb981ee5ce71bcb952719429a838cc1",
"size": 2693579,
"repo_path": "bios/Other/yaps2/GameIndex.yaml",
"cores": null
},
{
@@ -6131,10 +5990,10 @@
},
{
"dest": "Cemu/otp.bin",
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
"size": 256,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
"sha1": "aa4ebc7f94c22b04945b6c9232547ada10b71f6f",
"sha256": "a12993f7fe069098ab6fe1cdb43099a1dfb568223bef08ed5cb43ec93d9ff2de",
"size": 1024,
"repo_path": "bios/Nintendo/Wii U/otp.bin",
"cores": [
"cemu"
]
@@ -6401,6 +6260,16 @@
"dosbox"
]
},
{
"dest": "SC-55/WAVEROM3.BIN",
"sha1": "7454b817778179806f3f9d1985b3a2ef67ace76f",
"sha256": "334b2d16be3c2362210fdbec1c866ad58badeb0f84fd9bf5d0ac599baf077cc2",
"size": 1048576,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55-v1.00/sc55_waverom3.bin",
"cores": [
"dosbox"
]
},
{
"dest": "CM32LN_CONTROL.ROM",
"sha1": "dc1c5b1b90a4646d00f7daf3679733c7badc7077",
@@ -25829,6 +25698,96 @@
"mesen"
]
},
{
"dest": "dsp1.rom",
"sha1": "188d471fefea71eb53f0ee7064697ff0971b1014",
"sha256": "91e87d11e1c30d172556bed2211cce2efa94ba595f58c5d264809ef4d363a97b",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp1.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp1b.rom",
"sha1": "78b724811f5f18d8c67669d9390397eb1a47a5e2",
"sha256": "d789cb3c36b05c0b23b6c6f23be7aa37c6e78b6ee9ceac8d2d2aa9d8c4d35fa9",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp1b.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp2.rom",
"sha1": "198c4b1c3bfc6d69e734c5957af3dbfa26238dfb",
"sha256": "03ef4ef26c9f701346708cb5d07847b5203cf1b0818bf2930acd34510ffdd717",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp2.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp3.rom",
"sha1": "558da7cb3bd3876a6ca693661ffc6c110e948cf9",
"sha256": "0971b08f396c32e61989d1067dddf8e4b14649d548b2188f7c541b03d7c69e4e",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp3.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp4.rom",
"sha1": "af6478aecb6f1b67177e79c82ca04c56250a8c72",
"sha256": "752d03b2d74441e430b7f713001fa241f8bbcfc1a0d890ed4143f174dbe031da",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp4.rom",
"cores": [
"mesence"
]
},
{
"dest": "st010.rom",
"sha1": "6472828403de3589433a906e2c3f3d274c0ff008",
"sha256": "fa9bced838fedea11c6f6ace33d1878024bdd0d02cc9485899d0bdd4015ec24c",
"size": 53248,
"repo_path": "bios/Nintendo/SNES/SNES_st010.rom",
"cores": [
"mesence"
]
},
{
"dest": "st011.rom",
"sha1": "fecbae2cec76c710422486baa186ffa7ca1cf925",
"sha256": "8b2b3f3f3e6e29f4d21d8bc736b400bc988b7d2214ebee15643f01c1fee2f364",
"size": 53248,
"repo_path": "bios/Nintendo/SNES/SNES_st011.rom",
"cores": [
"mesence"
]
},
{
"dest": "st018.rom",
"sha1": "91383b92745cc7cc4f15409ac5bc2c2f699a43f1",
"sha256": "6df209ab5d2524d1839c038be400ae5eb20dafc14a3771a3239cd9e8acd53806",
"size": 163840,
"repo_path": "bios/Nintendo/SNES/SNES_st018.rom",
"cores": [
"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",
@@ -25859,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",
@@ -47751,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"
]
@@ -55645,6 +55624,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": "WQXEmu/nc1020/obj_lu.bin",
"sha1": "64b8a315ecbc420ae594ccced43498392988669e",
@@ -56729,20 +56728,20 @@
},
{
"dest": "SC-55/ROM1.BIN",
"sha1": "aea7a4c0c7ffe1f212f7b9faecfd728862ac6904",
"sha256": "d7f03bf65c1f98c9c1226f4e1d1ecd4ac778a2dc4a332568ab0d5fee7ede9042",
"size": 4096,
"repo_path": "bios/Galaksija/ROM1.BIN",
"sha1": "b91bb1d9dccffe831b7cfde7800a3fe32b2fbda6",
"sha256": "8a1eb33c7599b746c0c50283e4349a1bb1773b5c0ec0e9661219bf6c067d2042",
"size": 32768,
"repo_path": "bios/Roland/soundcanvas-roms/SC-55mk2-v1.01/rom1.bin",
"cores": [
"dosbox"
]
},
{
"dest": "SC-55/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"sha1": "078cb5feea05e80bb9a1bb857a2163ee434fd053",
"sha256": "a4c9fd821059054c7e7681d61f49ce6f42ed2fe407a7ec1ba0dfdc9722582ce0",
"size": 524288,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55mk2-v1.01/rom2.bin",
"cores": [
"dosbox"
]
@@ -56809,10 +56808,10 @@
},
{
"dest": "np2/bios.rom",
"sha1": "1f5f7013f18c08ff50d7942e76c4fbd782412414",
"sha256": "863df0975a305110a7da4591b704e8f5dc7302f03c63603aa2dcd03e84c36ff9",
"size": 65536,
"repo_path": "bios/IBM/PC/ibmpcjr/bios.rom",
"sha1": "76a7360502fa0296ea93b4c537174610a834d367",
"sha256": "d0f5b6d96a01295ab5436d7852d2c9eea1faa87fcf016b7e6599cdd689266cd2",
"size": 98304,
"repo_path": "bios/NEC/PC-98/bios.rom",
"cores": [
"nekop2"
]
@@ -56839,20 +56838,20 @@
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "a439fbb390da38cada95a7cbb1d6ca199cd66ef8",
"sha256": "08d68695e5bff6bbcbb751d6e4453db9f5ead0b5f41e86480798a04376dfe090",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
"sha256": "a73a06c23ed38370e58a11fb1b86f7ea4c547061a60d7aa62bae446235fd2dff",
"size": 65536,
"repo_path": "bios/Commodore/Amiga/mt32-roms/cm32l_control.rom",
"repo_path": "bios/Commodore/Amiga/mt32-roms/mt32_control.rom",
"cores": [
"scummvm"
]
},
{
"dest": "scummvm/extra/MT32_PCM.ROM",
"sha1": "289cc298ad532b702461bfc738009d9ebe8025ea",
"sha256": "0dc50190bb7ea3fb212c970346b771a511772b528801e7eda6d27efda8c40698",
"size": 1048576,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_cm32l.rom",
"sha1": "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252",
"sha256": "d9164063f293410cf33f2f64cdcea6893b44723fe41938e07ec9aef58b406238",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_mt32.rom",
"cores": [
"scummvm"
]
@@ -57489,6 +57488,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-30004R.bin",
"sha1": "8fa040852d4b8688f0c84bcfffc65eb208f2b432",
@@ -57519,6 +57528,36 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70004.BIN",
"sha1": "434bc0b4eb4827da0773ec0795aadc5162569a07",
@@ -57529,6 +57568,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.BIN",
"sha1": "cce6fac0f7e682ad167e1e828b2d53192c3d5051",
@@ -57539,6 +57588,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/ps2-0160h-20030602.bin",
"sha1": "f03bc2b7e7722d1788c9e7a341e43ac3ffe33a47",
@@ -57789,16 +57848,6 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70006_BIOS_V12_HK_200.NVM",
"sha1": "06ccc54cdbe8b017a629155ccb66310cba9c4720",
@@ -57809,16 +57858,6 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-75008_BIOS_V14_RUS_220.NVM",
"sha1": "fff13c6e30c55df9d6241ee08e5d0026c6a5d618",
@@ -57849,6 +57888,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-90006_BIOS_VX_HK _230.ROM1",
"sha1": "b96cf94772f01b8434038285896467884a84b0c2",
"sha256": "1b7c68be427e6f571596ef6b444e8b86d1b4e2a226d62f6bfc86717a798f2317",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-90006_BIOS_VX_HK _230.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
@@ -57878,16 +57927,6 @@
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"yaps2"
]
}
]
}
+152 -322
View File
@@ -5,7 +5,7 @@
"platform": "retrobat",
"display_name": "RetroBat",
"version": "1.0",
"generated": "2026-10-05T22:37:20Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "bios",
"detect": [
{
@@ -236,11 +236,11 @@
}
}
],
"total_files": 12284,
"total_size": 8492475899,
"pack_files": 12286,
"pack_size": 8492475899,
"total_omitted": 34,
"total_files": 12271,
"total_size": 8483676765,
"pack_files": 12273,
"pack_size": 8483676765,
"total_omitted": 30,
"omitted_files": [
{
"dest": "base/pak001.pk4",
@@ -432,26 +432,6 @@
"xash3d_fwgs"
]
},
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "main/pak0.pk3",
"name": "pak0.pk3",
@@ -532,26 +512,6 @@
"yquake2"
]
},
{
"dest": "soundfonts/gzdoom.sf2",
"name": "gzdoom.sf2",
"system": "doom",
"required": false,
"reason": "not_found",
"cores": [
"gzdoom"
]
},
{
"dest": "soundfonts/raze.sf2",
"name": "raze.sf2",
"system": "duke3d",
"required": false,
"reason": "not_found",
"cores": [
"raze"
]
},
{
"dest": "valve/pak0.pak",
"name": "pak0.pak",
@@ -5040,10 +5000,10 @@
},
{
"dest": "EGAGRAPH.ABS",
"sha1": "69556566c7db7ba4df9f505ddf6fdd024079ee89",
"sha256": "582c08db6f2646e0b8880a5e062bb4a5967d2235994fa0d374f1599146151712",
"size": 336143,
"repo_path": "bios/Softdisk/Catacomb Abyss/EGAGRAPH.ABS",
"sha1": "04511fded0103b6abea3dde1413e217838ef5bb2",
"sha256": "b2250a125cd02ad304328afea66de98459adef16adc11360ad76ebb4d690eec9",
"size": 320626,
"repo_path": "bios/Softdisk/Catacomb Abyss/.variants/EGAGRAPH.ABS.04511fde",
"cores": [
"catacombgl"
]
@@ -40100,10 +40060,10 @@
},
{
"dest": "Cemu/otp.bin",
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
"size": 256,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
"sha1": "aa4ebc7f94c22b04945b6c9232547ada10b71f6f",
"sha256": "a12993f7fe069098ab6fe1cdb43099a1dfb568223bef08ed5cb43ec93d9ff2de",
"size": 1024,
"repo_path": "bios/Nintendo/Wii U/otp.bin",
"cores": [
"cemu"
]
@@ -57306,6 +57266,16 @@
"gzdoom"
]
},
{
"dest": "soundfonts/gzdoom.sf2",
"sha1": "4394193fb95c1e59916efe9ac3a47952ab5002f5",
"sha256": "a9058609b07c1f0e0c1f420e76c2e80ba78136e6173e836caab51ffb13e552f1",
"size": 3299098,
"repo_path": "bios/Other/idtech4a++/uzdoom/soundfonts/uzdoom.sf2",
"cores": [
"gzdoom"
]
},
{
"dest": "fm_banks/GENMIDI.GS.wopl",
"sha1": "538d220d87b86b997f539265eb12a6c319e17a1c",
@@ -81152,26 +81122,6 @@
"mupen64plus_next"
]
},
{
"dest": "np2kai/bios.rom",
"sha1": "1f5f7013f18c08ff50d7942e76c4fbd782412414",
"sha256": "863df0975a305110a7da4591b704e8f5dc7302f03c63603aa2dcd03e84c36ff9",
"size": 65536,
"repo_path": "bios/IBM/PC/ibmpcjr/bios.rom",
"cores": [
"np2kai"
]
},
{
"dest": "np2kai/sound.rom",
"sha1": "d5dbc4fea3b8367024d363f5351baecd6adcd8ef",
"sha256": "667a0e17189cc9f28185386ac6fe86784164740992a662b0a6fb0e00713b20e8",
"size": 16384,
"repo_path": "bios/NEC/PC-98/sound.rom",
"cores": [
"np2kai"
]
},
{
"dest": "np2kai/2608_bd.wav",
"sha1": "0a56c142ef40cec50f3ee56a6e42d0029c9e2818",
@@ -81244,10 +81194,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"
]
@@ -85192,16 +85142,6 @@
"px68k"
]
},
{
"dest": "quasi88/n88.rom",
"sha1": "3518193b8207bdebf22c1380c2db8c554baff329",
"sha256": "d76f9118f9994eb391a0dc9c1fbfbfa2e9bc584a65da75f3ba7065d3c03cab76",
"size": 32768,
"repo_path": "bios/NEC/PC-98/n88.rom",
"cores": [
"quasi88"
]
},
{
"dest": "quasi88/n88_0.rom",
"sha1": "d1ae642aed4f0584eeb81ff50180db694e5101d4",
@@ -85242,26 +85182,6 @@
"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",
@@ -85332,6 +85252,16 @@
"raze"
]
},
{
"dest": "soundfonts/raze.sf2",
"sha1": "9dba9212f2e34446ab034595665809f09f303d63",
"sha256": "fca3e514b635a21789d4224e84865d2954a2a914d46b64aa8219ddb565c44869",
"size": 3283278,
"repo_path": "bios/Other/ScummVM/Roland_SC-55.sf2",
"cores": [
"raze"
]
},
{
"dest": "BLOOD.RFF",
"sha1": "5cd9d0f008a380bb5cddd0a32bcbedf09500ab57",
@@ -119322,6 +119252,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",
@@ -119422,6 +119372,16 @@
"visual-pinball"
]
},
{
"dest": "assets/fabian-hrir.sofa",
"sha1": "5bf46cfb6472b81f4aa7fb2c87bc79afc23109d7",
"sha256": "f22e921a6bc49a78c6d2eb3db963e9e12b4c4c5a77adeba2b786c38be37e6636",
"size": 1766147,
"repo_path": "bios/Other/visual-pinball/assets/fabian-hrir.sofa",
"cores": [
"visual-pinball"
]
},
{
"dest": "psvita/PSVUPDAT.PUP",
"sha1": "cc72dfcc964577cc29112ef368c28f55277c237c",
@@ -119446,16 +119406,6 @@
"storage": "release",
"release_asset": "PSP2UPDAT.PUP"
},
{
"dest": "xmil/IPLROM.X1T",
"sha1": "44620f57a25f0bcac2b57ca2b0f1ebad3bf305d3",
"sha256": "212895703175665be8544daa55b65da1aebcf1e9a2db65bcc1622e564b802b71",
"size": 32768,
"repo_path": "bios/Sharp/X1/.variants/IPLROM.X1T.44620f57",
"cores": [
"x1"
]
},
{
"dest": "xmil/FNT0808.X1",
"sha1": "1c1a0d8c9f4c446ccd7470516b215ddca5052fb2",
@@ -120726,16 +120676,6 @@
"bluemsx"
]
},
{
"dest": "Triforce/segaboot.gcm",
"sha1": "7297a11be143011ceb75a0720f96855003415a5d",
"sha256": "9a2eee93f5ed961aea4b12a318e2b4a54fa69a31239c1378b8bef6ea02e90391",
"size": 2097152,
"repo_path": "bios/Sega/Triforce/segaboot.gcm",
"cores": [
"dolphin"
]
},
{
"dest": "GBA/gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
@@ -121534,6 +121474,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-30004R.bin",
"sha1": "8fa040852d4b8688f0c84bcfffc65eb208f2b432",
@@ -121564,6 +121514,36 @@
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.BIN",
"sha1": "434bc0b4eb4827da0773ec0795aadc5162569a07",
@@ -121574,6 +121554,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.BIN",
"sha1": "cce6fac0f7e682ad167e1e828b2d53192c3d5051",
@@ -121584,6 +121574,16 @@
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0100jd-20000117.bin",
"sha1": "5b33170323ed6344e2363fed8115dc3918bb96a4",
@@ -122324,6 +122324,16 @@
"pcsx2"
]
},
{
"dest": "ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
"sha256": "e017efa804f1f7d4d3da82b67d7370c5be062b4860f1cff4f898f93437d691a0",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/ps2-0230a-20080220.nvm",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0230e-20080220.bin",
"sha1": "9915b5ba56798f4027ac1bd8d10abe0c1c9c326a",
@@ -122394,46 +122404,6 @@
"pcsx2"
]
},
{
"dest": "GameIndex.yaml",
"sha1": "57369df9ca82a460e596cea7835c5a0797dee668",
"sha256": "efa94cb7b9208dc6812bb9407dc986560e523cce57f511eff30942f76b7288bf",
"size": 1737799,
"repo_path": "bios/Sony/PlayStation 2/.variants/GameIndex.yaml.57369df9",
"cores": [
"pcsx2"
]
},
{
"dest": "ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Sony/PlayStation 2/.variants/ROM2.BIN.5d5ab431",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH30004R.nvm",
"sha1": "1d3caaed7411425c16aee8c35db47b928e31f60b",
"sha256": "c598dea13e5bba2fcae69de5b051c15fcd5b25842978be1bb20a86cfff243eaf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/.variants/SCPH30004R.nvm.5e0200d0",
"cores": [
"pcsx2"
]
},
{
"dest": "patches.zip",
"sha1": "2562e542d66db945ccf158d7a221c2a28e3b6be4",
"sha256": "df2b384ec29221155e8151fa2ff2dc8f0eef2409a8dce5ee7f7514352ae4058a",
"size": 2165576,
"repo_path": "bios/Sony/PlayStation 2/.variants/patches.zip.5a4d0a00",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-dvd-110u.bin",
"sha1": "0bc9143cb6839471d62b8fdd0b2fc05e941527d3",
@@ -122565,81 +122535,11 @@
]
},
{
"dest": "EROM.BIN",
"sha1": "adb4dbb7feb8247811506a38896ddcb789c4b477",
"sha256": "f7c4944e3001e3849cdead743e200e009d4d07bd5afb20fb937a7b201895f713",
"size": 247968,
"repo_path": "bios/Sony/PlayStation 2/EROM.BIN",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.EROM",
"sha1": "2ed11fd9333e62e36659565a744330a5902ed5e3",
"sha256": "67a6b2779e9320abe24f75ce811acf29e54d6dc06ce65b11341b6d84103de7f8",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"dest": "SCPH30004R.nvm",
"sha1": "1d3caaed7411425c16aee8c35db47b928e31f60b",
"sha256": "c598dea13e5bba2fcae69de5b051c15fcd5b25842978be1bb20a86cfff243eaf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.EROM",
"sha1": "ec0121bb8e2a209044309780d2efc2559c9a0ef1",
"sha256": "e5bcd5925c1f22de48d49c3895dd421d167faddc033aac628f5facfba90ceef3",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"repo_path": "bios/Sony/PlayStation 2/.variants/SCPH30004R.nvm.5e0200d0",
"cores": [
"pcsx2"
]
@@ -122654,16 +122554,6 @@
"pcsx2"
]
},
{
"dest": "SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-75008_BIOS_V14_RUS_220.NVM",
"sha1": "fff13c6e30c55df9d6241ee08e5d0026c6a5d618",
@@ -122684,16 +122574,6 @@
"pcsx2"
]
},
{
"dest": "SCPH-90006_BIOS_VX_HK _230.EROM",
"sha1": "6b357e8624095770952fc408cdc6647fa62e0d28",
"sha256": "8801922f614b365d9d37557d7a375d06d385e936804943cb2c52abff55a3d254",
"size": 3145728,
"repo_path": "bios/Sony/PlayStation 2/SCPH-90006_BIOS_VX_HK _230.EROM",
"cores": [
"pcsx2"
]
},
{
"dest": "SCPH-90006_BIOS_VX_HK _230.NVM",
"sha1": "465c2738c280eb1864f679c80b55e468bf670057",
@@ -122714,66 +122594,6 @@
"pcsx2"
]
},
{
"dest": "cheats_ni.zip",
"sha1": "dec5e4f137890c338babe13cfb9d41ff869fa721",
"sha256": "91d5ae704ff8b6d90165a65f69e9e2be8a9ab9c588c525dc1c9c4ecbbdef1ffd",
"size": 42525,
"repo_path": "bios/Sony/PlayStation 2/cheats_ni.zip",
"cores": [
"pcsx2"
]
},
{
"dest": "cheats_ws.zip",
"sha1": "773b4279d8c8e182dc57e444dbf448a1d115e7e0",
"sha256": "d07e3adde4da6a3220363b12d7b066168918225546122643ef7d19fece53b8b1",
"size": 1273296,
"repo_path": "bios/Sony/PlayStation 2/cheats_ws.zip",
"cores": [
"pcsx2"
]
},
{
"dest": "eeprom.dat",
"sha1": "ffc6261e487efa8c7442069f71acfc4aa826993d",
"sha256": "8667e718294e9e0df1d30600ba3eeb201f764aad2dad72748643e4a285e1d1f7",
"size": 64,
"repo_path": "bios/Sony/PlayStation 2/eeprom.dat",
"cores": [
"pcsx2"
]
},
{
"dest": "ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
"sha256": "e017efa804f1f7d4d3da82b67d7370c5be062b4860f1cff4f898f93437d691a0",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/ps2-0230a-20080220.nvm",
"cores": [
"pcsx2"
]
},
{
"dest": "message.wav",
"sha1": "c54f04b63ac6f04e0a0944ef260b5497e4494dbd",
"sha256": "dfd8035387a463722ad07af3c91934e0a685f2afa57e31d305f0602656d1edb1",
"size": 15696,
"repo_path": "bios/Sony/PlayStation 2/resources/sounds/achievements/message.wav",
"cores": [
"pcsx2"
]
},
{
"dest": "rom1.bin",
"sha1": "47d2ec4b342649e4c391043ab915d4435f9d180d",
"sha256": "41de2047af8382988bfd568035ff26eec5f1cabc3efe773680546aa00a82857d",
"size": 199680,
"repo_path": "bios/Sony/PlayStation 2/rom1.bin",
"cores": [
"pcsx2"
]
},
{
"dest": "scph30004fr.nvm",
"sha1": "92ce9e739dcbfa0d6afb442620b71919d26bf283",
@@ -122793,6 +122613,16 @@
"cores": [
"pcsx2"
]
},
{
"dest": "eeprom.dat",
"sha1": "ffc6261e487efa8c7442069f71acfc4aa826993d",
"sha256": "8667e718294e9e0df1d30600ba3eeb201f764aad2dad72748643e4a285e1d1f7",
"size": 64,
"repo_path": "bios/Sony/PlayStation 2/eeprom.dat",
"cores": [
"pcsx2"
]
}
]
}
+498 -418
View File
File diff suppressed because it is too large. Load diff
+361 -431
View File
File diff suppressed because it is too large. Load diff
+252 -213
View File
@@ -5,7 +5,7 @@
"platform": "rocknix",
"display_name": "ROCKNIX",
"version": "1.0",
"generated": "2026-10-05T22:37:20Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "bios",
"detect": [
{
@@ -17,153 +17,12 @@
}
],
"standalone_copies": [],
"total_files": 5786,
"total_size": 6566365388,
"pack_files": 5788,
"pack_size": 6566365388,
"total_omitted": 14,
"omitted_files": [
{
"dest": "SC-55/WAVEROM3.BIN",
"name": "WAVEROM3.BIN",
"system": "dos",
"required": false,
"reason": "not_found",
"cores": [
"dosbox"
]
},
{
"dest": "SufamiTurbo.sfc",
"name": "SufamiTurbo.sfc",
"system": "nintendo-sufami-turbo",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Games Express CD Card (Japan).pce",
"name": "[BIOS] Games Express CD Card (Japan).pce",
"system": "nec-pc-engine-cd",
"required": false,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"name": "[BIOS] Super CD-ROM System (Japan) (v3.0).pce",
"system": "nec-pc-engine-cd",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp1.rom",
"name": "dsp1.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp1b.rom",
"name": "dsp1b.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp2.rom",
"name": "dsp2.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp3.rom",
"name": "dsp3.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "dsp4.rom",
"name": "dsp4.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "jagboot.rom",
"name": "jagboot.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "jagboot_m.rom",
"name": "jagboot_m.rom",
"system": "atari-jaguar",
"required": false,
"reason": "not_found",
"cores": [
"virtualjaguar"
]
},
{
"dest": "st010.rom",
"name": "st010.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "st011.rom",
"name": "st011.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
},
{
"dest": "st018.rom",
"name": "st018.rom",
"system": "nintendo-snes",
"required": true,
"reason": "not_found",
"cores": [
"mesence"
]
}
],
"total_files": 5804,
"total_size": 6570732492,
"pack_files": 5806,
"pack_size": 6570732492,
"total_omitted": 0,
"omitted_files": [],
"files": [
{
"dest": "xemu/bios/mcpx_1.0.bin",
@@ -2555,10 +2414,10 @@
},
{
"dest": "Cemu/otp.bin",
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
"size": 256,
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
"sha1": "aa4ebc7f94c22b04945b6c9232547ada10b71f6f",
"sha256": "a12993f7fe069098ab6fe1cdb43099a1dfb568223bef08ed5cb43ec93d9ff2de",
"size": 1024,
"repo_path": "bios/Nintendo/Wii U/otp.bin",
"cores": [
"cemu"
]
@@ -2877,20 +2736,20 @@
},
{
"dest": "SC-55/ROM1.BIN",
"sha1": "aea7a4c0c7ffe1f212f7b9faecfd728862ac6904",
"sha256": "d7f03bf65c1f98c9c1226f4e1d1ecd4ac778a2dc4a332568ab0d5fee7ede9042",
"size": 4096,
"repo_path": "bios/Galaksija/ROM1.BIN",
"sha1": "b91bb1d9dccffe831b7cfde7800a3fe32b2fbda6",
"sha256": "8a1eb33c7599b746c0c50283e4349a1bb1773b5c0ec0e9661219bf6c067d2042",
"size": 32768,
"repo_path": "bios/Roland/soundcanvas-roms/SC-55mk2-v1.01/rom1.bin",
"cores": [
"dosbox"
]
},
{
"dest": "SC-55/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"sha1": "078cb5feea05e80bb9a1bb857a2163ee434fd053",
"sha256": "a4c9fd821059054c7e7681d61f49ce6f42ed2fe407a7ec1ba0dfdc9722582ce0",
"size": 524288,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55mk2-v1.01/rom2.bin",
"cores": [
"dosbox"
]
@@ -2925,6 +2784,16 @@
"dosbox"
]
},
{
"dest": "SC-55/WAVEROM3.BIN",
"sha1": "7454b817778179806f3f9d1985b3a2ef67ace76f",
"sha256": "334b2d16be3c2362210fdbec1c866ad58badeb0f84fd9bf5d0ac599baf077cc2",
"size": 1048576,
"repo_path": "bios/Roland/sounddata/Nuked-SC55-Resources/ROMs/SC-55-v1.00/sc55_waverom3.bin",
"cores": [
"dosbox"
]
},
{
"dest": "libbass.so",
"sha1": "415365e930b4c7d33e9af3225bea230e15bdebe7",
@@ -16743,10 +16612,10 @@
},
{
"dest": "galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723",
"size": 8192,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN",
"sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 4096,
"repo_path": "bios/Galaksija/ROM2.BIN",
"cores": [
"galaksija"
]
@@ -23473,6 +23342,96 @@
"mesen-s"
]
},
{
"dest": "dsp1.rom",
"sha1": "188d471fefea71eb53f0ee7064697ff0971b1014",
"sha256": "91e87d11e1c30d172556bed2211cce2efa94ba595f58c5d264809ef4d363a97b",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp1.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp1b.rom",
"sha1": "78b724811f5f18d8c67669d9390397eb1a47a5e2",
"sha256": "d789cb3c36b05c0b23b6c6f23be7aa37c6e78b6ee9ceac8d2d2aa9d8c4d35fa9",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp1b.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp2.rom",
"sha1": "198c4b1c3bfc6d69e734c5957af3dbfa26238dfb",
"sha256": "03ef4ef26c9f701346708cb5d07847b5203cf1b0818bf2930acd34510ffdd717",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp2.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp3.rom",
"sha1": "558da7cb3bd3876a6ca693661ffc6c110e948cf9",
"sha256": "0971b08f396c32e61989d1067dddf8e4b14649d548b2188f7c541b03d7c69e4e",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp3.rom",
"cores": [
"mesence"
]
},
{
"dest": "dsp4.rom",
"sha1": "af6478aecb6f1b67177e79c82ca04c56250a8c72",
"sha256": "752d03b2d74441e430b7f713001fa241f8bbcfc1a0d890ed4143f174dbe031da",
"size": 8192,
"repo_path": "bios/Nintendo/SNES/SNES_dsp4.rom",
"cores": [
"mesence"
]
},
{
"dest": "st010.rom",
"sha1": "6472828403de3589433a906e2c3f3d274c0ff008",
"sha256": "fa9bced838fedea11c6f6ace33d1878024bdd0d02cc9485899d0bdd4015ec24c",
"size": 53248,
"repo_path": "bios/Nintendo/SNES/SNES_st010.rom",
"cores": [
"mesence"
]
},
{
"dest": "st011.rom",
"sha1": "fecbae2cec76c710422486baa186ffa7ca1cf925",
"sha256": "8b2b3f3f3e6e29f4d21d8bc736b400bc988b7d2214ebee15643f01c1fee2f364",
"size": 53248,
"repo_path": "bios/Nintendo/SNES/SNES_st011.rom",
"cores": [
"mesence"
]
},
{
"dest": "st018.rom",
"sha1": "91383b92745cc7cc4f15409ac5bc2c2f699a43f1",
"sha256": "6df209ab5d2524d1839c038be400ae5eb20dafc14a3771a3239cd9e8acd53806",
"size": 163840,
"repo_path": "bios/Nintendo/SNES/SNES_st018.rom",
"cores": [
"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",
@@ -23503,6 +23462,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": "bios.col",
"sha1": "45bedc4cbdeac66c7df59e9e599195c778d86a92",
@@ -44675,10 +44654,10 @@
},
{
"dest": "np2/bios.rom",
"sha1": "1f5f7013f18c08ff50d7942e76c4fbd782412414",
"sha256": "863df0975a305110a7da4591b704e8f5dc7302f03c63603aa2dcd03e84c36ff9",
"size": 65536,
"repo_path": "bios/IBM/PC/ibmpcjr/bios.rom",
"sha1": "76a7360502fa0296ea93b4c537174610a834d367",
"sha256": "d0f5b6d96a01295ab5436d7852d2c9eea1faa87fcf016b7e6599cdd689266cd2",
"size": 98304,
"repo_path": "bios/NEC/PC-98/bios.rom",
"cores": [
"nekop2"
]
@@ -45625,10 +45604,10 @@
},
{
"dest": "np2kai/bios.rom",
"sha1": "1f5f7013f18c08ff50d7942e76c4fbd782412414",
"sha256": "863df0975a305110a7da4591b704e8f5dc7302f03c63603aa2dcd03e84c36ff9",
"size": 65536,
"repo_path": "bios/IBM/PC/ibmpcjr/bios.rom",
"sha1": "76a7360502fa0296ea93b4c537174610a834d367",
"sha256": "d0f5b6d96a01295ab5436d7852d2c9eea1faa87fcf016b7e6599cdd689266cd2",
"size": 98304,
"repo_path": "bios/NEC/PC-98/bios.rom",
"cores": [
"np2kai"
]
@@ -45725,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"
]
@@ -52261,20 +52240,20 @@
},
{
"dest": "scummvm/extra/MT32_CONTROL.ROM",
"sha1": "a439fbb390da38cada95a7cbb1d6ca199cd66ef8",
"sha256": "08d68695e5bff6bbcbb751d6e4453db9f5ead0b5f41e86480798a04376dfe090",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
"sha256": "a73a06c23ed38370e58a11fb1b86f7ea4c547061a60d7aa62bae446235fd2dff",
"size": 65536,
"repo_path": "bios/Commodore/Amiga/mt32-roms/cm32l_control.rom",
"repo_path": "bios/Commodore/Amiga/mt32-roms/mt32_control.rom",
"cores": [
"scummvm"
]
},
{
"dest": "scummvm/extra/MT32_PCM.ROM",
"sha1": "289cc298ad532b702461bfc738009d9ebe8025ea",
"sha256": "0dc50190bb7ea3fb212c970346b771a511772b528801e7eda6d27efda8c40698",
"size": 1048576,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_cm32l.rom",
"sha1": "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252",
"sha256": "d9164063f293410cf33f2f64cdcea6893b44723fe41938e07ec9aef58b406238",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_mt32.rom",
"cores": [
"scummvm"
]
@@ -54939,6 +54918,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": "WQXEmu/nc1020/obj_lu.bin",
"sha1": "64b8a315ecbc420ae594ccced43498392988669e",
@@ -56903,6 +56902,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-30004R.bin",
"sha1": "8fa040852d4b8688f0c84bcfffc65eb208f2b432",
@@ -56933,6 +56942,36 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"sha1": "7aef1dd6de4b4f64f31daad615bbccdb3e62e79c",
"sha256": "8dc012640186c46a39fcdd64a8ab9430080a220bd0630da58c977f35725de5cf",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM2",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"sha1": "b284280341f16b1581c28ff81934bfdf403da1f0",
"sha256": "ad812eb36b8e7daa6106411e43840f70175f012a7a7dd289965872fe43933cc4",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70004.BIN",
"sha1": "434bc0b4eb4827da0773ec0795aadc5162569a07",
@@ -56943,6 +56982,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70004.ROM1",
"sha1": "cc44355ded232a2ba5309bb24d4cd55af08696b2",
"sha256": "b86fbccaa111ca7497630337dafea7da7320fc4f924ada22b92145ef806e7b42",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70004.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.BIN",
"sha1": "cce6fac0f7e682ad167e1e828b2d53192c3d5051",
@@ -56953,6 +57002,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/ps2-0100jd-20000117.bin",
"sha1": "5b33170323ed6344e2363fed8115dc3918bb96a4",
@@ -57893,16 +57952,6 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-50009_BIOS_V10_CHN_190.NVM",
"sha1": "3ffa2e74c8c8ce0e10e2069956f12d2407b91c65",
"sha256": "5b255bce650e66efeeb3b0bebc0dff705600d05aa2e076a315d02c6a7ed6ac9a",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-50009_BIOS_V10_CHN_190.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70006_BIOS_V12_HK_200.NVM",
"sha1": "06ccc54cdbe8b017a629155ccb66310cba9c4720",
@@ -57913,16 +57962,6 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-70008_BIOS_V12_RUS_200.NVM",
"sha1": "6611151e8f599230cdf19a17318e7e5ddc781961",
"sha256": "b1704c7da37f03db017ad7576508c61b250d9ed4f3ceda8732ea91a3034f6cbf",
"size": 1024,
"repo_path": "bios/Sony/PlayStation 2/SCPH-70008_BIOS_V12_RUS_200.NVM",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-75008_BIOS_V14_RUS_220.NVM",
"sha1": "fff13c6e30c55df9d6241ee08e5d0026c6a5d618",
@@ -57953,6 +57992,16 @@
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-90006_BIOS_VX_HK _230.ROM1",
"sha1": "b96cf94772f01b8434038285896467884a84b0c2",
"sha256": "1b7c68be427e6f571596ef6b444e8b86d1b4e2a226d62f6bfc86717a798f2317",
"size": 524288,
"repo_path": "bios/Sony/PlayStation 2/SCPH-90006_BIOS_VX_HK _230.ROM1",
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/ps2-0230a-20080220.nvm",
"sha1": "258ad0f26b0b269462bbd9acbb5506d5dfbcc3d5",
@@ -57982,16 +58031,6 @@
"cores": [
"yaps2"
]
},
{
"dest": "pcsx2/bios/SCPH-10000.MEC",
"sha1": "3f8affbe07024ccd2f5b8374072d0928c583a862",
"sha256": "fbbfc6c156266ac7e9afb37f0cc53fb78fadf2f09f580e56b2da57e6539e463b",
"size": 4,
"repo_path": "bios/Sony/PlayStation 2/SCPH-10000.MEC",
"cores": [
"yaps2"
]
}
]
}
+20 -20
View File
@@ -5,7 +5,7 @@
"platform": "romm",
"display_name": "RomM",
"version": "1.0",
"generated": "2026-10-05T20:14:26Z",
"generated": "2026-10-06T03:26:07Z",
"base_destination": "bios",
"detect": [
{
@@ -16,9 +16,9 @@
],
"standalone_copies": [],
"total_files": 958,
"total_size": 1580732707,
"total_size": 1580216611,
"pack_files": 960,
"pack_size": 1580732707,
"pack_size": 1580216611,
"total_omitted": 0,
"omitted_files": [],
"files": [
@@ -3812,20 +3812,20 @@
},
{
"dest": "nds/dsi_bios9.bin",
"sha1": "db61fa39ddbc5f5ed71fb19cda47609ef0201723",
"sha256": "664c608bd6ccfe583e87b3b924afa00f8697dc7ca92eeb6d206d68abb03d2e10",
"sha1": "719b9eef33692406d7170ff526069615759c4dfc",
"sha256": "6e9f70b51dc4f26289b597f14e2513620c2af964374a95f4c5a62b5cad199afc",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios9.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios9i.bin",
"cores": [
"melonds"
]
},
{
"dest": "nds/dsi_bios7.bin",
"sha1": "d2b17224e156ee5e55b5d8d9f323b1c9b86dd951",
"sha256": "28a13952901b4065ef3f08c3252b49a00b6525e301b7ddaa3a390b4c7cbef901",
"sha1": "c7c7570bfe51c3c7c5da3b01331b94e7e7cb4f53",
"sha256": "12a03016ba07cfd874fc002df49a2c6eb2414eb7cfa395c2043035ec5e9193e8",
"size": 65536,
"repo_path": "bios/Nintendo/DS/dsi_bios7.bin",
"repo_path": "bios/Nintendo/DS/NDS_Bios7i.bin",
"cores": [
"melonds"
]
@@ -4632,10 +4632,10 @@
},
{
"dest": "pc-9800-series/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"
]
@@ -7642,20 +7642,20 @@
},
{
"dest": "scummvm/scummvm/extra/MT32_CONTROL.ROM",
"sha1": "a439fbb390da38cada95a7cbb1d6ca199cd66ef8",
"sha256": "08d68695e5bff6bbcbb751d6e4453db9f5ead0b5f41e86480798a04376dfe090",
"sha1": "b083518fffb7f66b03c23b7eb4f868e62dc5a987",
"sha256": "a73a06c23ed38370e58a11fb1b86f7ea4c547061a60d7aa62bae446235fd2dff",
"size": 65536,
"repo_path": "bios/Commodore/Amiga/mt32-roms/cm32l_control.rom",
"repo_path": "bios/Commodore/Amiga/mt32-roms/mt32_control.rom",
"cores": [
"scummvm"
]
},
{
"dest": "scummvm/scummvm/extra/MT32_PCM.ROM",
"sha1": "289cc298ad532b702461bfc738009d9ebe8025ea",
"sha256": "0dc50190bb7ea3fb212c970346b771a511772b528801e7eda6d27efda8c40698",
"size": 1048576,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_cm32l.rom",
"sha1": "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252",
"sha256": "d9164063f293410cf33f2f64cdcea6893b44723fe41938e07ec9aef58b406238",
"size": 524288,
"repo_path": "bios/Commodore/Amiga/mt32-roms/pcm_mt32.rom",
"cores": [
"scummvm"
]
File diff suppressed because it is too large. Load diff
+10
View File
@@ -65,6 +65,16 @@ data_directories:
local_cache: data/pcsx2-resources
description: "PCSX2 resources (GameIndex.yaml, RedumpDatabase.yaml, fonts, shaders, icons, sounds)"
# ref: pcsx2-libretro/Main.cpp:157-158 (yaps2) - system/pcsx2/resources/
# yaps2's own bin/resources at the revision its profile is pinned to.
yaps2-resources:
source_url: "https://github.com/yaps2/yaps2/archive/{version}.tar.gz"
source_type: tarball
source_path: "yaps2-{version}/bin/resources"
version: 57ea93c0ce8306063b31fe6658dab6b492bbedfe
local_cache: data/yaps2-resources
description: "yaps2 resources (GameIndex.yaml, RedumpDatabase.yaml, fonts, shaders, icons, sounds)"
# ref: src/Cafe/GameProfile/GameProfile.cpp:181-195 — Cemu/gameProfiles/default/
cemu-gameprofiles:
source_url: "https://github.com/cemu-project/Cemu/archive/{version}.tar.gz"
+6
View File
@@ -625,6 +625,8 @@ platforms:
source_url: https://raw.githubusercontent.com/RetroBat-Official/emulatorlauncher/master/batocera-systems/Resources/batocera-systems.json
source_format: json
hash_type: md5
# Windows: names differing only by case land on one file.
case_insensitive_fs: true
schedule: weekly
cores:
- '81'
@@ -812,6 +814,8 @@ platforms:
source_wiki: https://raw.githubusercontent.com/EmuDeck/emudeck.github.io/main/docs/tables/
source_format: bash_script+csv
hash_type: md5
# Windows: names differing only by case land on one file.
case_insensitive_fs: true
schedule: weekly
target_scraper: emudeck_targets
target_source: https://github.com/dragoonDorise/EmuDeck
@@ -976,6 +980,8 @@ platforms:
source_url: https://raw.githubusercontent.com/TASEmulators/BizHawk/master/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs
source_format: csharp_firmware_database
hash_type: sha1
# Windows: names differing only by case land on one file.
case_insensitive_fs: true
schedule: monthly
cores:
- applewin
+19
View File
@@ -25,6 +25,25 @@ cores:
- vita3k
- xemu
- xroar
standalone_cores:
- azahar
- cemu
- dolphin
- duckstation
- gzdoom
- mame
- melonds
- openbor
- pcsx2
- pico-8
- ppsspp
- primehack
- rpcs3
- ruffle
- solarus
- vita3k
- xemu
- xroar
systems:
triforce:
files:
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -21,7 +21,7 @@
"sha1": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"md5": { "type": "string", "pattern": "^[a-f0-9]{32}$" },
"size": { "type": "integer", "minimum": 0 },
"status": { "enum": ["verified", "verified_md5", "verified_rebuild", "verified_data", "untracked"] },
"status": { "enum": ["verified", "verified_md5", "verified_rebuild", "verified_data", "verified_members", "untracked"] },
"name": { "type": "string" }
}
}
+14 -11
View File
@@ -54,6 +54,7 @@ import upstream # noqa: E402
from common import ( # noqa: E402
list_registered_platforms,
load_emulator_profiles,
upstream_profile_index,
yaml_load,
)
from exporter import discover_exporters # noqa: E402
@@ -364,14 +365,9 @@ def check_native(platforms_dir: Path, cache_dir: Path, truth_dir: Path) -> list[
# --- targets ----------------------------------------------------------------
def profile_name_index(profiles: dict[str, dict]) -> dict[str, str]:
"""Upstream core name -> profile key, the index target filtering uses."""
index: dict[str, str] = {}
for key, profile in profiles.items():
index.setdefault(key, key)
for alias in profile.get("cores") or []:
index.setdefault(str(alias), key)
return index
def profile_name_index(profiles: dict[str, dict]) -> dict[str, set[str]]:
"""Upstream core name -> the profiles that claim it, as target filtering reads it."""
return upstream_profile_index(profiles, include_aliases=True)
def _removed_cores(overrides: dict, platform: str) -> dict[str, set[str]]:
@@ -539,13 +535,20 @@ def coreinfo_gaps(
Matching folds case: the buildbot serves lower-case names while a few
.info files keep the project's own casing.
"""
index = {k.casefold(): v for k, v in profile_name_index(profiles).items()}
index: dict[str, set[str]] = {}
for key, claimants in profile_name_index(profiles).items():
index.setdefault(key.casefold(), set()).update(claimants)
unprofiled = [n for n in names if n.casefold() not in index]
# A name only standalone profiles claim: libretro now builds a core
# nothing in the collection describes as one.
standalone = [
(n, index[n.casefold()])
(n, sorted(index[n.casefold()])[0])
for n in names
if n.casefold() in index
and str(profiles[index[n.casefold()]].get("type", "")).strip() == "standalone"
and all(
str(profiles[c].get("type", "")).strip() == "standalone"
for c in index[n.casefold()]
)
]
return unprofiled, standalone
+2 -2
View File
@@ -24,7 +24,7 @@ import sys
from collections.abc import Iterable
sys.path.insert(0, os.path.dirname(__file__))
from common import load_database
from common import composition_tier, load_database
from largefiles import (
LARGE_FILES_RELEASE,
LARGE_FILES_REPO,
@@ -129,7 +129,7 @@ def _section_for(path: str) -> str:
return "Arcade"
if top == "QEMU":
return "Virtual machine firmware"
if top in ("RPG Maker", "ScummVM"):
if composition_tier(path) == "game_data":
return "Game engine data"
if top in ("Apple", "Commodore", "Atari", "Sinclair", "Amstrad"):
return "Computer"
+82 -37
View File
@@ -35,19 +35,23 @@ def load_database(db_path: str) -> dict:
_casefold_index_cache: dict[int, dict[str, list[str]]] = {}
# One slot: the dict it was built from, its size then, and the folded view.
# Keyed by id() alone, a database freed and replaced by another at the same
# address got the old index back, and the case-insensitive step answered from
# a collection that no longer existed.
_casefold_index_cache: list[tuple[dict, int, dict[str, list[str]]]] = []
def _casefold_name_index(by_name: dict) -> dict[str, list[str]]:
"""Build (and cache) a casefolded view of the by_name index."""
key = id(by_name)
cached = _casefold_index_cache.get(key)
if cached is not None:
return cached
if _casefold_index_cache:
source, size, folded = _casefold_index_cache[0]
if source is by_name and size == len(by_name):
return folded
folded: dict[str, list[str]] = {}
for name, sha1s in by_name.items():
folded.setdefault(name.casefold(), []).extend(sha1s)
_casefold_index_cache[key] = folded
_casefold_index_cache[:] = [(by_name, len(by_name), folded)]
return folded
@@ -477,23 +481,30 @@ def _affinity_tokens(text: str) -> set[str]:
def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]:
"""Same-named candidates, the ones stored with the file's owner first.
"""Same-named candidates, the likeliest first.
A name step without a tie-break took whichever path sorted first: the
TheXTech coin.ogg for Syobon Action, a Hurrican sound for ARMSX2. The
destination's directories and the profile that asks name where its own
copy lives; the order is otherwise kept.
TheXTech coin.ogg for Syobon Action, a Hurrican sound for ARMSX2, the
1 MB CM-32L PCM ROM for ScummVM's 512 KB MT32_PCM.ROM. A size the entry
declares orders the candidates first (ordering rejects nothing, so an
unvalidated size keeps its informative status); then the destination's
directories and the profile that asks name where its own copy lives. The
order is otherwise kept.
"""
if len(paths) < 2:
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 "")
for owner in (file_entry.get("source_profile"), file_entry.get("source_emulator")):
if owner:
wanted |= _affinity_tokens(str(owner).replace(" ", ""))
if not wanted or len(paths) < 2:
return paths
sized = any(file_entry.get(k) for k in ("size", "min_size", "max_size"))
def score(path: str) -> int:
return len(wanted & _affinity_tokens(path.rsplit("/", 1)[0]))
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)))
return fits, len(wanted & _affinity_tokens(path.rsplit("/", 1)[0]))
return sorted(paths, key=score, reverse=True)
@@ -1065,6 +1076,7 @@ GAME_DATA_TOPS = (
"Interplay",
"Monolith",
"Softdisk",
"Toys for Bob",
"Valve",
)
@@ -1298,6 +1310,46 @@ def runs_standalone(
)
# What identifies a profile entry's content, carried unchanged wherever the
# entry travels (cross-reference report, core extras, second-pass copies).
# Each copy once kept its own subset: np2kai's bios.rom lost min_size and
# validation on the way and resolved to an IBM PCjr ROM of the same name.
PROFILE_IDENTITY_FIELDS = (
"sha1", "md5", "sha256", "crc32", "size", "min_size", "max_size",
"validation", "aliases", "unsourceable",
)
def upstream_profile_index(
profiles: dict[str, dict], include_aliases: bool = False
) -> dict[str, set[str]]:
"""Every name a core is known by upstream -> the profiles that claim it.
A name can be claimed twice: the buildbot's `pcsx2` is the LRPS2 core,
which lrps2.yml lists in `cores:`, and also the key of the standalone
pcsx2 profile. A last-wins dict kept one of the two depending on file
order, and a RetroArch target filter dropped LRPS2's files. Callers
choose: a platform list prefers the exact key, a target filter keeps
every claimant.
"""
index: dict[str, set[str]] = {}
for name, profile in profiles.items():
if not include_aliases and profile.get("type") == "alias":
continue
index.setdefault(name, set()).add(name)
for core in profile.get("cores") or []:
index.setdefault(str(core), set()).add(name)
return index
def preferred_profile(index: dict[str, set[str]], name: str) -> str | None:
"""The profile a name designates: its own key first, else the first claimant."""
claimants = index.get(name)
if not claimants:
return None
return name if name in claimants else sorted(claimants)[0]
def resolve_platform_cores(
config: dict,
profiles: dict[str, dict],
@@ -1323,14 +1375,12 @@ def resolve_platform_cores(
}
elif isinstance(cores_config, list):
core_set = {str(c) for c in cores_config}
core_to_profile: dict[str, str] = {}
for name, p in profiles.items():
if p.get("type") == "alias":
continue
core_to_profile[name] = name
for core_name in p.get("cores", []):
core_to_profile[str(core_name)] = name
result = {core_to_profile[c] for c in core_set if c in core_to_profile}
index = upstream_profile_index(profiles)
result = {
profile
for c in core_set
if (profile := preferred_profile(index, c)) is not None
}
# Support "all_libretro" as a list element: combines all libretro
# profiles with explicitly listed standalone cores (e.g. RetroDECK
# ships RetroArch + standalone emulators)
@@ -1355,13 +1405,10 @@ def resolve_platform_cores(
# Upstream sources (buildbot, es_systems) may use different names
# than our profile keys (e.g., mednafen_psx vs beetle_psx).
# The profiles' cores: field lists these alternate names.
upstream_to_profile: dict[str, str] = {}
for name, p in profiles.items():
upstream_to_profile[name] = name
for alias in p.get("cores", []):
upstream_to_profile[str(alias)] = name
# Expand target_cores to profile keys
expanded = {upstream_to_profile.get(c, c) for c in target_cores}
index = upstream_profile_index(profiles, include_aliases=True)
expanded = {
profile for c in target_cores for profile in index.get(c, {c})
}
result = result & expanded
return result
@@ -1467,12 +1514,10 @@ def filter_systems_by_target(
return systems
# Build reverse index for target core name resolution
upstream_to_profile: dict[str, str] = {}
for name, p in profiles.items():
upstream_to_profile[name] = name
for alias in p.get("cores", []):
upstream_to_profile[str(alias)] = name
expanded_target = {upstream_to_profile.get(c, c) for c in target_cores}
index = upstream_profile_index(profiles, include_aliases=True)
expanded_target = {
profile for c in target_cores for profile in index.get(c, {c})
}
_norm_sid = _norm_system_id
@@ -1532,8 +1577,8 @@ def expand_platform_declared_names(config: dict, db: dict) -> set[str]:
md5 = fe.get("md5", "")
if not md5:
continue
# Skip multi-hash and zippedFile entries (inner ROM MD5, not file MD5)
if "," in md5 or fe.get("zippedFile"):
# Skip multi-hash and zipped_file entries (inner ROM MD5, not file MD5)
if "," in md5 or fe.get("zipped_file"):
continue
sha1 = by_md5.get(md5.lower())
if not sha1:
+85 -47
View File
@@ -6,8 +6,7 @@ Reproduces the exact verification logic from Azahar/Citra source code:
- movable.sed: magic check + RSA on embedded LFCS
- otp.bin: AES-128-CBC decrypt + magic + SHA256 hash
RSA verification is pure Python (no dependencies).
AES decryption requires 'cryptography' library or falls back to openssl CLI.
RSA verification and AES decryption are pure Python (no dependencies).
Source refs:
Azahar src/core/hw/unique_data.cpp
@@ -19,7 +18,6 @@ from __future__ import annotations
import hashlib
import struct
import subprocess
from collections.abc import Callable
from pathlib import Path
@@ -137,54 +135,94 @@ def _rsa_verify_pkcs1v15_sha256(
return em == expected_em
# AES-128-CBC decryption (with fallback)
# AES-128-CBC decryption, standard library only. The build promises stdlib +
# pyyaml; a contributor without cryptography, pycryptodome or openssl on PATH
# got a RuntimeError from verify.py on otp.bin. 256 bytes need no speed.
def _xtime(value: int) -> int:
value <<= 1
return (value ^ 0x11B) & 0xFF if value & 0x100 else value
def _gmul(a: int, b: int) -> int:
product = 0
while b:
if b & 1:
product ^= a
a = _xtime(a)
b >>= 1
return product
def _build_sbox() -> tuple[list[int], list[int]]:
sbox = [0] * 256
inverse = [0] * 256
for value in range(256):
# Multiplicative inverse in GF(2^8), then the affine transform.
inv = 0 if value == 0 else next(
c for c in range(1, 256) if _gmul(value, c) == 1
)
out = inv
for shift in range(1, 5):
out ^= ((inv << shift) | (inv >> (8 - shift))) & 0xFF
out ^= 0x63
sbox[value] = out
inverse[out] = value
return sbox, inverse
_SBOX, _INV_SBOX = _build_sbox()
def _expand_key(key: bytes) -> list[list[int]]:
"""The eleven round keys of AES-128, each sixteen bytes."""
words = [list(key[i : i + 4]) for i in range(0, 16, 4)]
rcon = 1
for i in range(4, 44):
word = list(words[i - 1])
if i % 4 == 0:
word = [_SBOX[b] for b in word[1:] + word[:1]]
word[0] ^= rcon
rcon = _xtime(rcon)
words.append([a ^ b for a, b in zip(words[i - 4], word)])
return [sum(words[r * 4 : r * 4 + 4], []) for r in range(11)]
def _decrypt_block(block: bytes, round_keys: list[list[int]]) -> bytes:
state = [b ^ k for b, k in zip(block, round_keys[10])]
for rnd in range(9, -1, -1):
# Inverse ShiftRows on a column-major state, then inverse SubBytes.
state = [state[(i + 4 * (i % 4) * 3) % 16] for i in range(16)]
state = [_INV_SBOX[b] for b in state]
state = [b ^ k for b, k in zip(state, round_keys[rnd])]
if rnd:
mixed = []
for c in range(4):
a0, a1, a2, a3 = state[c * 4 : c * 4 + 4]
mixed += [
_gmul(a0, 14) ^ _gmul(a1, 11) ^ _gmul(a2, 13) ^ _gmul(a3, 9),
_gmul(a0, 9) ^ _gmul(a1, 14) ^ _gmul(a2, 11) ^ _gmul(a3, 13),
_gmul(a0, 13) ^ _gmul(a1, 9) ^ _gmul(a2, 14) ^ _gmul(a3, 11),
_gmul(a0, 11) ^ _gmul(a1, 13) ^ _gmul(a2, 9) ^ _gmul(a3, 14),
]
state = mixed
return bytes(state)
def _aes_128_cbc_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
"""Decrypt AES-128-CBC without padding."""
# Try cryptography library first
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
return decryptor.update(data) + decryptor.finalize()
except ImportError:
pass
# Try pycryptodome
try:
from Crypto.Cipher import AES # type: ignore[import-untyped]
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.decrypt(data)
except ImportError:
pass
# Fallback to openssl CLI
try:
result = subprocess.run(
[
"openssl",
"enc",
"-aes-128-cbc",
"-d",
"-K",
key.hex(),
"-iv",
iv.hex(),
"-nopad",
],
input=data,
capture_output=True,
check=True,
)
return result.stdout
except (subprocess.CalledProcessError, FileNotFoundError):
raise RuntimeError(
"AES decryption requires 'cryptography' or 'pycryptodome' library, "
"or 'openssl' CLI tool"
)
if len(key) != 16 or len(iv) != 16 or len(data) % 16:
raise ValueError("AES-128-CBC needs a 16-byte key and IV and whole blocks")
round_keys = _expand_key(key)
out = bytearray()
previous = iv
for offset in range(0, len(data), 16):
block = data[offset : offset + 16]
plain = _decrypt_block(block, round_keys)
out += bytes(a ^ b for a, b in zip(plain, previous))
previous = block
return bytes(out)
# File verification functions
+9 -7
View File
@@ -179,26 +179,23 @@ def main() -> None:
reports: list[dict] = []
formatter = _format_markdown if args.format == "markdown" else _format_terminal
compared = 0
for platform in platforms:
truth = _load_truth(args.truth_dir, platform)
if truth is None:
if not args.json_output:
print(
f"skip {platform}: no truth YAML in {args.truth_dir}/",
file=sys.stderr,
)
print(f"skip {platform}: no truth YAML in {args.truth_dir}/", file=sys.stderr)
continue
try:
scraped = load_platform_config(platform, args.platforms_dir)
except FileNotFoundError:
if not args.json_output:
print(f"skip {platform}: no scraped config", file=sys.stderr)
print(f"skip {platform}: no scraped config", file=sys.stderr)
continue
report = diff_platform_truth(truth, scraped)
report["platform"] = platform
compared += 1
if args.json_output:
reports.append(report)
@@ -210,6 +207,11 @@ def main() -> None:
json.dump(reports, sys.stdout, indent=2)
print()
# A named platform must be compared; --all must compare at least one.
if compared < (len(platforms) if args.platform else 1):
print(f"compared {compared} of {len(platforms)} platform(s)", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+39 -12
View File
@@ -57,6 +57,24 @@ class NativeFile:
return "both"
return "platform" if self.platform is not None else "truth"
def _identity_source(self) -> str:
"""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. 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 = {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"
if any(truth[f] & plat[f] for f in HASH_FIELDS):
return "both"
return "truth"
def hashes(self, field_name: str) -> list[str]:
"""Accepted values for a hash, truth first when it has an opinion.
@@ -67,16 +85,17 @@ class NativeFile:
"""
truth_values = _hash_values(self.truth or {}, field_name)
platform_values = _hash_values(self.platform or {}, field_name)
if truth_values and platform_values and not set(truth_values) & set(
platform_values
):
source = self._identity_source()
if source == "platform":
return platform_values
if source == "truth":
return truth_values
if truth_values:
# Keep the platform's extra accepted revisions alongside ours.
merged = list(truth_values)
merged.extend(v for v in platform_values if v not in merged)
return merged
return platform_values
if not truth_values:
return platform_values
if not set(truth_values) & set(platform_values):
return truth_values
# Keep the platform's extra accepted revisions alongside ours.
return truth_values + [v for v in platform_values if v not in truth_values]
def hash(self, field_name: str) -> str:
values = self.hashes(field_name)
@@ -113,8 +132,8 @@ class NativeFile:
a name-matched 480-byte fbneo boot.bin turned RomM's Dreamcast
boot.bin into size 480 beside its 2 MB md5, which never verifies.
"""
truth_hashed = any(_hash_values(self.truth or {}, f) for f in HASH_FIELDS)
order = (self.truth, self.platform) if truth_hashed else (self.platform, self.truth)
source = self._identity_source()
order = (self.platform, self.truth) if source == "platform" else (self.truth, self.platform)
for entry in order:
if entry and entry.get("size"):
return int(entry["size"])
@@ -289,7 +308,15 @@ def build_native_model(
def by_name(candidate: NativeFile) -> bool:
theirs = _match_key(candidate.platform or {})[1]
return bool(t_name) and theirs == t_name
if not t_name or theirs != 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")
p_size = (candidate.platform or {}).get("size")
return not (
isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size
)
def by_hash(candidate: NativeFile) -> bool:
if not t_hashes:
+3 -1
View File
@@ -84,7 +84,9 @@ class Exporter(BaseExporter):
elif hash_match is True:
attrs.append('hashMatchMandatory="true"')
note = " ".join(str(fe.native("note", "")).split())
# The platform's own note only: ours is prose about our profiles, with
# file:line citations, and the format does not carry note corrections.
note = " ".join(str((fe.platform or {}).get("note") or "").split())
if note:
attrs.append(f"note={quoteattr(note)}")
+48 -16
View File
@@ -25,6 +25,34 @@ MANIFEST = "component_manifest.json"
PLAIN_LABELS = ("Required", "Optional")
def _systems_of(entry: dict) -> list[str]:
"""The systems one entry serves: neogeo.zip is declared once for three."""
declared = entry.get("system")
if isinstance(declared, list):
return [str(s) for s in declared]
return [str(declared)] if declared else []
def _md5_set(value: object) -> set[str]:
return {m.strip().lower() for m in str(value or "").split(",") if m.strip()}
def _matching_keys(
entry: dict, by_key: dict[tuple[str, str], OrderedDict]
) -> list[tuple[str, str]]:
"""Our keys this platform entry answers to.
An entry without a system matches ours only when one system alone
declares the name.
"""
name = str(entry.get("filename", ""))
systems = _systems_of(entry)
if systems:
return [(name, s) for s in systems if (name, s) in by_key]
named = [k for k in by_key if k[0] == name]
return named if len(named) == 1 else []
class Exporter(BaseExporter):
"""Write RetroDECK's component manifests, corrected."""
@@ -169,26 +197,30 @@ class Exporter(BaseExporter):
merged: list[OrderedDict] = []
corrected: set[tuple[str, str]] = set()
for entry in existing if isinstance(existing, list) else []:
if not isinstance(entry, dict):
continue
name = str(entry.get("filename", ""))
declared = entry.get("system")
# One entry can serve several systems: neogeo.zip is declared
# once for neogeo, fbneo and arcade.
systems = (
[str(s) for s in declared] if isinstance(declared, list)
else [str(declared)] if declared else []
entries = [e for e in existing if isinstance(e, dict)] if isinstance(existing, list) else []
# A name and system declared several times are several revisions (the
# 64DD IPL USA and Dev): one entry of ours corrects the revision it
# shares a hash with, never all of them.
seen_keys: dict[tuple[str, str], int] = {}
for entry in entries:
for system in _systems_of(entry):
key = (str(entry.get("filename", "")), system)
seen_keys[key] = seen_keys.get(key, 0) + 1
for entry in entries:
keys = _matching_keys(entry, by_key)
other_revision = (
keys
and seen_keys.get(keys[0], 0) > 1
and not _md5_set(entry.get("md5")) & _md5_set(by_key[keys[0]].get("md5"))
)
keys = [(name, s) for s in systems if (name, s) in by_key]
if not systems:
# An entry without a system matches ours only when one
# system alone declares the name.
named = [k for k in by_key if k[0] == name]
keys = named if len(named) == 1 else []
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)
combined.update(
(field, value) for field, value in by_key[keys[0]].items()
+28 -4
View File
@@ -142,12 +142,23 @@ class Exporter(BaseExporter):
@classmethod
def _insertion_point(cls, help_text: str) -> int | None:
"""Where a name joins the list, or None when there is no list."""
"""Where a name joins the list, or None when there is no list.
A list of alternatives ("a or b", "a/b") is not an enumeration: an
appended ", c" reads as one more required file and the plural turns
"one of these" into "all of these". Such a list is left alone.
"""
clause = _BIOS_CLAUSE.search(help_text)
if clause is None:
return None
names = cls._names_in(clause.group(0))
return clause.start() + names[-1].end() if names else None
text = clause.group(0)
names = cls._names_in(text)
if not names:
return None
between = text[names[0].start():names[-1].end()]
if re.search(r"\bor\b|/", between):
return None
return clause.start() + names[-1].end()
@staticmethod
def _in_search_order(candidates: list[NativeFile]) -> list[str]:
@@ -164,6 +175,19 @@ class Exporter(BaseExporter):
names.append(fe.name)
return names
@staticmethod
def _required_for(fe: NativeFile, core: str | None) -> bool:
"""Whether the package's own core needs the file.
The merged `required` is true when any core needs it: pcsx_rearmed
runs in HLE without a BIOS, yet four were proposed as required for
its package because other PSX cores require them.
"""
required_by = (fe.truth or {}).get("_required_by")
if required_by is None:
return fe.required
return core in required_by
@staticmethod
def _join(names: list[str]) -> str:
"""RetroPie's own idiom: a, b and c."""
@@ -235,7 +259,7 @@ class Exporter(BaseExporter):
candidates = [
fe
for fe in files
if fe.required
if self._required_for(fe, core)
and not (
{fe.name.lower()}
| {str(a).lower() for a in (fe.native("aliases", []) or [])}
+76 -56
View File
@@ -13,6 +13,7 @@ from __future__ import annotations
import argparse
import json
import os
import shutil
import sys
from datetime import datetime, timezone
from pathlib import Path
@@ -257,33 +258,33 @@ def save_cache(cache_path: str, cache: dict):
json.dump(cache, f)
def _load_gitignored_large_files() -> dict[str, str]:
"""Read .gitignore and return {filename: bios_path} for large files."""
def _load_gitignored_large_files() -> set[str]:
"""The bios/ paths .gitignore registers as release assets."""
gitignore = Path(".gitignore")
if not gitignore.exists():
return {}
entries = {}
for line in gitignore.read_text().splitlines():
line = line.strip()
if line.startswith("bios/") and not line.startswith("#"):
name = Path(line).name
entries[name] = line
return entries
return set()
return {
line.strip()
for line in gitignore.read_text().splitlines()
if line.strip().startswith("bios/")
}
def _preserve_large_file_entries(files: dict, db_path: str) -> int:
"""Preserve database entries for large files not on disk.
"""Keep the entries of release assets the checkout does not hold.
Large files (>50 MB) are stored as GitHub release assets and listed
in .gitignore. When generate_db runs locally without them, their
entries would be lost. This reads the existing database, downloads
missing files from the release, and re-adds entries with paths
pointing to the local cache.
Files kept out of git live as assets of the large-files release, and
.gitignore registers their paths. An entry survives a scan that missed
its file only under that registered path: a bare name is shared by
other files (pak0.pk3, history.db), and a path rewritten into the
download cache is no longer one .gitignore knows, so the manifest
would send the installer to the repository for it. A fetched asset is
written back to its registered path.
"""
from common import fetch_large_file
large_files = _load_gitignored_large_files()
if not large_files:
registered = _load_gitignored_large_files()
if not registered:
return 0
try:
@@ -299,22 +300,17 @@ def _preserve_large_file_entries(files: dict, db_path: str) -> int:
count = 0
for sha1, entry in existing_db.get("files", {}).items():
if sha1 in files:
continue
name = entry.get("name", "")
path = entry.get("path", "")
# Match by gitignored bios/ path OR by filename of a known large file
if path not in large_files.values() and name not in large_files:
if sha1 in files or path not in registered or path in scanned_paths:
continue
cached = fetch_large_file(
path if path in large_files.values() else name,
path,
expected_sha1=entry.get("sha1", ""),
expected_md5=entry.get("md5", ""),
)
if cached:
entry = {**entry, "path": cached}
elif path in scanned_paths:
continue
if cached and not os.path.exists(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
shutil.copy2(cached, path)
files[sha1] = entry
count += 1
return count
@@ -395,6 +391,27 @@ def main():
return 0
def _as_list(value: object) -> list[str]:
"""A hash field as lowercase values, whether written alone or as a list."""
if not value:
return []
values = value if isinstance(value, list) else str(value).split(",")
return [str(v).strip().lower() for v in values if str(v).strip()]
def _primary_names(entries: list) -> set[str]:
return {str(fe.get("name", "")).lower() for fe in entries if isinstance(fe, dict)}
def _own_aliases(file_entry: dict, primary_names: set[str]) -> list[str]:
"""Aliases that name no other entry of the same profile."""
name = str(file_entry.get("name", "")).lower()
return [
alias for alias in file_entry.get("aliases") or []
if str(alias).lower() == name or str(alias).lower() not in primary_names
]
def _collect_all_aliases(files: dict) -> dict:
"""Collect alternate filenames from platform YAMLs, core-info, and known aliases.
@@ -461,21 +478,10 @@ def _collect_all_aliases(files: dict) -> dict:
except ImportError:
pass
try:
sys.path.insert(0, "scripts")
from scraper.coreinfo_scraper import Scraper as CoreInfoScraper
ci_reqs = CoreInfoScraper().fetch_requirements()
for r in ci_reqs:
basename = r.name
# Try to match by MD5 or by known canonical names
matched = None
if r.md5 and r.md5 in md5_to_sha1:
matched = md5_to_sha1[r.md5]
if matched:
_add_alias(basename, matched)
except (ImportError, ConnectionError, OSError):
pass
# core-info is not read here: it was the only network call of a build
# that must give the same database offline, and the one name it added
# (gearcoleco's writer.rom) is a profile entry proven by its sha1, which
# the profile pass below now registers.
# Collect aliases from emulator YAMLs (aliases field on file entries)
emulators_dir = Path("emulators")
@@ -491,26 +497,40 @@ def _collect_all_aliases(files: dict) -> dict:
emu_config = yaml_load(f) or {}
except (yaml.YAMLError, OSError):
continue
# A profile whose entries name each other (dosbox-x opens
# MT32_CONTROL.ROM and CM32L_CONTROL.ROM alike, so each entry
# aliases the other) states that ITS slot takes either name.
# Indexed globally that became evidence for every emulator:
# ScummVM's MT-32 slot received the CM-32L ROMs.
primary_names = _primary_names(emu_config.get("files", []))
for file_entry in emu_config.get("files", []):
entry_aliases = file_entry.get("aliases", [])
entry_name = file_entry.get("name", "")
entry_aliases = _own_aliases(file_entry, primary_names)
# A profile may accept several revisions: each held one
# is designated by the entry, none of them by guess.
matched: set[str] = {
value for value in _as_list(file_entry.get("sha1"))
if value in files
}
matched |= {
md5_to_sha1[value]
for value in _as_list(file_entry.get("md5"))
if value in md5_to_sha1
}
if matched:
# Proven by content, the profile's own name designates
# the file whatever the collection calls it.
entry_aliases.insert(0, entry_name)
if not entry_aliases:
continue
entry_name = file_entry.get("name", "")
sha1 = file_entry.get("sha1", "")
md5 = file_entry.get("md5", "")
matched = None
if sha1 and sha1 in files:
matched = sha1
elif md5 and md5 in md5_to_sha1:
matched = md5_to_sha1[md5]
elif entry_name and name_count.get(entry_name) == 1:
if not matched and entry_name and name_count.get(entry_name) == 1:
# A name carried by several files names none of them:
# quasi88's disk.rom aliases went to whichever
# disk.rom the scan met last, a Tandy CoCo ROM.
matched = name_to_sha1[entry_name]
if matched:
matched = {name_to_sha1[entry_name]}
for sha in sorted(matched):
for alias_name in entry_aliases:
_add_alias(alias_name, matched)
_add_alias(alias_name, sha)
except ImportError:
pass
+327 -226
View File
@@ -28,13 +28,13 @@ import zipfile
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import resolve_platform_cores as _platform_cores
from common import (
apply_target_overrides,
artifact_lock,
ArtifactLockBusy,
build_target_cores_cache,
build_zip_contents_index,
check_inside_zip,
compute_hashes,
expand_directory_entries,
expand_platform_declared_names,
@@ -57,10 +57,10 @@ from common import (
resolve_local_file,
sanitize_pack_path,
select_emulator_profiles,
size_fits,
yaml_load,
)
import packresolve
from refresh_data_dirs import cache_lock
import region as region_mod
import slot as slot_mod
import slots
@@ -73,6 +73,10 @@ from nativemode import (
reads_file_contents,
)
from validation import (
inner_rom_check,
settle_mismatch,
agnostic_substitute,
frontend_digest_matches,
_build_validation_index,
check_file_validation,
filter_files_by_mode,
@@ -283,24 +287,6 @@ def download_external(file_entry: dict, dest_path: str) -> bool:
PACK_DOCUMENTS = ("README.txt", "manifest.json")
def _inner_rom_check(file_entry: dict, local_path: str) -> str:
"""How an archive answers an entry that pins a ROM inside it.
Batocera, RetroBat and ROCKNIX hash a member of the ZIP, never the ZIP:
the resolver hands back the archive as a mismatch and this decides it.
Returns check_inside_zip's answer for the first accepted MD5 that
matches, else for the last one tried. The pack and the install manifest
both read it, so a file one of them ships is a file the other lists.
"""
declared = [m.strip() for m in file_entry.get("md5", "").split(",") if m.strip()]
result = "not_in_zip"
for candidate in declared or [""]:
result = check_inside_zip(local_path, file_entry["zipped_file"], candidate)
if result == "ok":
break
return result
def _data_directory_members(
pack_systems: dict,
data_registry: dict | None,
@@ -342,22 +328,25 @@ def _data_directory_members(
dd_prefix = base_dest
else:
dd_prefix = dd_dest
for root, _dirs, filenames in os.walk(local_path):
for fname in filenames:
src = os.path.join(root, fname)
rel = os.path.relpath(src, local_path)
full = f"{dd_prefix}/{rel}"
if full in seen_destinations or (
full.lower() in seen_lower and case_insensitive
):
continue
if _has_path_conflict(full, seen_destinations, seen_parents):
continue
seen_destinations.add(full)
_register_path(full, seen_destinations, seen_parents)
if case_insensitive:
seen_lower.add(full.lower())
yield src, full
# Held while the caller writes what is yielded: a refresh swapping
# the tree waits until the walk is done.
with cache_lock(local_path, shared=True):
for root, _dirs, filenames in os.walk(local_path):
for fname in filenames:
src = os.path.join(root, fname)
rel = os.path.relpath(src, local_path)
full = f"{dd_prefix}/{rel}"
if full in seen_destinations or (
full.lower() in seen_lower and case_insensitive
):
continue
if _has_path_conflict(full, seen_destinations, seen_parents):
continue
seen_destinations.add(full)
_register_path(full, seen_destinations, seen_parents)
if case_insensitive:
seen_lower.add(full.lower())
yield src, full
def _pack_data_directories(
@@ -487,7 +476,7 @@ def _preferred_entries(
def accepted(path: str | None) -> int:
if not path or not path.endswith(".zip"):
return -1
return sum(_inner_rom_check(fe, path) == "ok" for fe in members)
return sum(inner_rom_check(fe, path) == "ok" for fe in members)
fe, path, _st = max(resolved, key=lambda item: accepted(item[1]))
if accepted(path) > 0:
@@ -517,6 +506,7 @@ def _select_variants(
source: str,
regions,
one_per_slot: bool,
required_only: bool = False,
) -> tuple[set, list, list]:
"""Which regional and slot alternatives this pack leaves out.
@@ -542,6 +532,7 @@ def _select_variants(
target_cores=target_cores,
include_extras=(source != "platform"),
include_all=(source == "truth"),
required_only=required_only,
)
if regions:
region_index = region_mod.build_region_index(emu_profiles or {})
@@ -600,6 +591,10 @@ def generate_pack(
config = load_platform_config(platform_name, platforms_dir)
if zip_contents is None:
zip_contents = {}
if emu_profiles is None:
# The pipeline always passes them; a caller that does not must get the
# same pack, not one built without validation, slots or agnostic cores.
emu_profiles = load_emulator_profiles(emulators_dir)
verification_mode = config.get("verification_mode", "existence")
platform_display = config.get("platform", platform_name)
@@ -641,6 +636,7 @@ def generate_pack(
from common import resolve_platform_cores
validation_index = {}
platform_profiles: dict[str, dict] = {}
if emu_profiles:
platform_profiles = {
name: emu_profiles[name]
@@ -696,7 +692,7 @@ def generate_pack(
# direction. Runs only when --region is given.
region_drops, region_fallbacks, slot_undecidable = _select_variants(
config, pack_systems, emulators_dir, db, base_dest, emu_profiles,
target_cores, source, regions, one_per_slot,
target_cores, source, regions, one_per_slot, required_only,
)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
@@ -806,38 +802,16 @@ def generate_pack(
continue
if status == "not_found":
# Agnostic fallback: if an agnostic core covers this system,
# find any matching file in the DB
by_name = db.get("indexes", {}).get("by_name", {})
# A filename-agnostic core takes any image of its system,
# renamed; only an existence frontend accepts that.
files_db = db.get("files", {})
agnostic_path = None
agnostic_resolved = False
if emu_profiles:
for _emu_key, _emu_prof in emu_profiles.items():
if _emu_prof.get("bios_mode") != "agnostic":
continue
if sys_id not in set(_emu_prof.get("systems", [])):
continue
for _ef in _emu_prof.get("files", []):
ef_name = _ef.get("name", "")
for _sha1 in by_name.get(ef_name, []):
_entry = files_db.get(_sha1, {})
_path = _entry.get("path", "")
if _path:
_prefix = _path.rsplit("/", 1)[0] + "/"
for _s, _e in files_db.items():
if _e.get("path", "").startswith(_prefix):
if size_fits(_ef, _e.get("size", 0)):
if os.path.exists(_e["path"]):
local_path = _e["path"]
agnostic_path = _prefix
agnostic_resolved = True
break
break
if agnostic_resolved:
break
if agnostic_resolved:
break
if not reads_file_contents(verification_mode):
found = agnostic_substitute(file_entry, sys_id, db, platform_profiles)
if found:
local_path, agnostic_path = found
agnostic_resolved = True
if agnostic_resolved and local_path:
# Write rename README
@@ -882,7 +856,7 @@ def generate_pack(
):
zf_name = file_entry.get("zipped_file")
if zf_name and local_path:
last_result = _inner_rom_check(file_entry, local_path)
last_result = inner_rom_check(file_entry, local_path)
zip_ok = last_result == "ok"
if zip_ok:
status = "zip_exact"
@@ -900,6 +874,11 @@ def generate_pack(
)
if not zip_ok:
continue
elif frontend_digest_matches(
file_entry, local_path, digest_algorithm(verification_mode)
):
status = "frontend_digest_exact"
file_status.setdefault(dedup_key, "ok")
else:
file_status[dedup_key] = "excluded"
file_reasons[dedup_key] = "hash mismatch"
@@ -977,8 +956,16 @@ def generate_pack(
core_files = []
elif precomputed_extras is not None:
core_files = precomputed_extras
elif system_filter and source != "truth":
core_files = []
elif system_filter:
# The systems asked for, with what their cores need: the split
# packs get exactly this, and a full pack of one system had none.
core_files = _extras_for_systems(
_collect_emulator_extras(
config, emulators_dir, db, set(), base_dest, emu_profiles,
target_cores=target_cores, include_all=(source == "truth"),
),
system_filter,
)
else:
core_files = _collect_emulator_extras(
config,
@@ -991,22 +978,6 @@ def generate_pack(
include_all=(source == "truth"),
)
# Truth mode + system_filter: filter core files by system ID
if system_filter and source == "truth" and core_files:
from common import _norm_system_id
norm_filter = {_norm_system_id(s) for s in system_filter} | set(
system_filter
)
core_files = [
fe
for fe in core_files
if (
set(_extra_system_ids(fe))
| {_norm_system_id(s) for s in _extra_system_ids(fe)}
)
& norm_filter
]
core_count = 0
for fe in core_files:
if required_only and fe.get("required") is False:
@@ -1110,6 +1081,12 @@ def generate_pack(
undecidable_slots=slot_undecidable,
narrowings=narrowings,
system_filter=system_filter,
bios_paths=[
str(detect.get("bios_path", ""))
for detect in (
_pack_registry.get(platform_name, {}).get("install", {}) or {}
).get("detect", [])
],
)
_write_generated_member(zf, "README.txt", readme_text)
@@ -1222,6 +1199,29 @@ def _normalize_zip_for_pack(
def _archive_destination(
archive_name: str, archive_prefix: str, pack_structure: dict | None, standalone: bool
) -> str:
"""Where an emulator pack places one of the profile's archives."""
archive_dest = sanitize_pack_path(archive_name)
if archive_prefix:
archive_dest = f"{archive_prefix}/{archive_dest}"
prefix = (pack_structure or {}).get("standalone" if standalone else "libretro", "")
return f"{prefix}/{archive_dest}" if prefix else archive_dest
def _archive_is_optional(archive_entry: dict, archive_name: str, files: list[dict]) -> bool:
"""Optional as a whole: its own entry and every member it carries.
geolith's _Required pack held neocd.zip and irrmaze.zip all the same.
"""
return archive_entry.get("required") is False and all(
member.get("required") is False
for member in files
if member.get("archive") == archive_name
)
def generate_emulator_pack(
profile_names: list[str],
emulators_dir: str,
@@ -1332,14 +1332,9 @@ def generate_emulator_pack(
# Pack archives as units
archive_prefix = profile.get("archive_prefix", "")
for archive_name in sorted(archives):
archive_dest = sanitize_pack_path(archive_name)
if archive_prefix:
archive_dest = f"{archive_prefix}/{archive_dest}"
if pack_structure:
mode_key = "standalone" if standalone else "libretro"
prefix = pack_structure.get(mode_key, "")
if prefix:
archive_dest = f"{prefix}/{archive_dest}"
archive_dest = _archive_destination(
archive_name, archive_prefix, pack_structure, standalone
)
if archive_dest.lower() in seen_lower:
continue
@@ -1356,6 +1351,8 @@ def generate_emulator_pack(
),
{"name": archive_name},
)
if required_only and _archive_is_optional(archive_entry, archive_name, files):
continue
local_path, status = resolve_file(
archive_entry,
db,
@@ -1605,6 +1602,24 @@ def _group_systems_by_manufacturer(
return groups
def _extras_for_systems(extras: list[dict], system_ids: list[str]) -> list[dict]:
"""Core extras owned by one of the given systems.
Extras carry their profile/system identity directly; display labels are
presentation only and must never drive routing.
"""
from common import _norm_system_id
wanted = set(system_ids) | {_norm_system_id(s) for s in system_ids}
return [
fe for fe in extras
if (
set(_extra_system_ids(fe))
| {_norm_system_id(s) for s in _extra_system_ids(fe)}
) & wanted
]
def generate_split_packs(
platform_name: str,
platforms_dir: str,
@@ -1665,26 +1680,9 @@ def generate_split_packs(
)
else:
all_extras = []
# Extras carry their profile/system identity directly; display labels are
# presentation only and must never drive routing.
from common import _norm_system_id
{_norm_system_id(s): s for s in systems}
results = []
for group_name, group_system_ids in sorted(groups.items()):
group_sys_set = set(group_system_ids)
group_norm = {_norm_system_id(s) for s in group_system_ids}
group_match = group_sys_set | group_norm
group_extras = [
fe
for fe in all_extras
if (
set(_extra_system_ids(fe))
| {_norm_system_id(s) for s in _extra_system_ids(fe)}
)
& group_match
]
group_extras = _extras_for_systems(all_extras, group_system_ids)
zip_path = generate_pack(
platform_name,
platforms_dir,
@@ -1723,19 +1721,13 @@ def generate_split_packs(
results.append(zip_path)
# Warn about extras that couldn't be distributed (emulators without systems: field)
all_groups_match = set()
for group_system_ids in groups.values():
group_norm = {_norm_system_id(s) for s in group_system_ids}
all_groups_match |= set(group_system_ids) | group_norm
undistributed = [
fe
for fe in all_extras
if not (
set(_extra_system_ids(fe))
| {_norm_system_id(s) for s in _extra_system_ids(fe)}
distributed = {
id(fe)
for fe in _extras_for_systems(
all_extras, [s for ids in groups.values() for s in ids]
)
& all_groups_match
]
}
undistributed = [fe for fe in all_extras if id(fe) not in distributed]
if undistributed:
emus = sorted({fe.get("source_emulator", "?") for fe in undistributed})
print(
@@ -2022,6 +2014,7 @@ def _validate_args(args, parser):
parser.error("--source requires --platform or --all")
if has_from_md5:
for flag, given in (
("--manifest", args.manifest),
("--target", args.target),
("--required-only", args.required_only),
("--source", source_given),
@@ -2082,6 +2075,7 @@ def _run_manifest_mode(
else:
variants = [(args.source, args.required_only)]
failed: list[str] = []
for source, required_only in variants:
for group_platforms, representative in groups:
print(f"\nGenerating manifest for {representative} [source={source}]...")
@@ -2149,6 +2143,13 @@ def _run_manifest_mode(
print(f" {alias_path}: alias of {representative}")
except (FileNotFoundError, OSError, yaml.YAMLError) as e:
print(f" ERROR: {e}")
failed.append(representative)
# A platform whose manifest could not be built has none, and install.py
# serves whatever an older run left there: that is a failed run.
if failed:
print(f"ERROR: no manifest for {', '.join(sorted(set(failed)))}")
sys.exit(1)
@contextlib.contextmanager
@@ -2239,6 +2240,11 @@ def _run_verify_packs(args):
sys.exit(1)
def _pack_label(source: str, required_only: bool) -> str:
"""The variant tag pipeline.parse_pack_counts reads after a pack name."""
return f"[source={source}, required]" if required_only else f"[source={source}]"
def _run_platform_packs(
args,
groups,
@@ -2259,6 +2265,7 @@ def _run_platform_packs(
else:
variants = [(args.source, args.required_only)]
failed: list[str] = []
for source, required_only in variants:
for group_platforms, representative in groups:
aliases = [p for p in group_platforms if p != representative]
@@ -2268,9 +2275,9 @@ def _run_platform_packs(
for p in group_platforms
]
label = " / ".join(all_names)
print(f"\nGenerating pack for {label} [source={source}]...")
print(f"\nGenerating pack for {label} {_pack_label(source, required_only)}...")
else:
print(f"\nGenerating pack for {representative} [source={source}]...")
print(f"\nGenerating pack for {representative} {_pack_label(source, required_only)}...")
try:
tc = target_cores_cache.get(representative) if args.target else None
@@ -2340,6 +2347,7 @@ def _run_platform_packs(
print(f" Renamed -> {os.path.basename(new_path)}")
except (FileNotFoundError, OSError, yaml.YAMLError) as e:
print(f" ERROR: {e}")
failed.append(representative)
print("\nVerifying packs and generating manifests...")
skip_conf = bool(system_filter or args.split)
@@ -2363,11 +2371,133 @@ def _run_platform_packs(
regions=getattr(args, "regions", None),
)
all_ok = all_ok and ok
if failed:
# Verification only sees the ZIPs that exist; a pack that was never
# written does not fail it.
print(f"ERROR: no pack for {', '.join(sorted(set(failed)))}")
sys.exit(1)
if not all_ok:
print("WARNING: some packs have verification errors")
sys.exit(1)
def _refuse_for_all_variants(args, parser) -> None:
"""Flags --all-variants would override, or modes that never read it."""
if not args.all_variants:
return
# It builds the six source x required combinations itself: a --source
# or --required-only beside it would be overridden, and the modes that
# build one pack (emulator, system, hashes) never read it.
for flag, given in (
("--source", args.source != "full"),
("--required-only", args.required_only),
("--emulator", args.emulator),
("--from-md5", args.from_md5 or args.from_md5_file),
):
if given:
parser.error(f"{flag} is incompatible with --all-variants")
if args.system and not (args.platform or args.all):
parser.error("--all-variants requires --platform or --all")
if args.verify_packs and args.manifest:
parser.error("--verify-packs is incompatible with --manifest")
if args.verify_packs:
# The check that follows the build reads the full pack's name.
for flag, given in (
("--system", args.system),
("--one-per-slot", args.one_per_slot),
("--split", args.split),
):
if given:
parser.error(f"{flag} is incompatible with --verify-packs")
def _refuse_unapplied_flags(args, parser) -> None:
"""Refuse every flag the requested mode would not apply.
A mode applies a narrowing flag or refuses it: a swallowed flag answers
about an artifact the caller did not name. Run before any quick-exit
mode, since --verify-packs and --manifest-targets return early.
"""
# Parsed before the quick-exit modes: --verify-packs returns early and
# still needs the region priority list to narrow its expectation.
args.regions = []
if args.region:
try:
args.regions = region_mod.parse_requested(args.region)
except ValueError as exc:
parser.error(str(exc))
if args.manifest_targets:
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_for_all_variants(args, parser)
mode = next(
(
name for name, on in (
("--manifest-targets", args.manifest_targets),
("--verify-packs", args.verify_packs and not args.all_variants),
("--list", args.list),
("--list-emulators", args.list_emulators),
("--list-systems", args.list_systems),
("--list-targets", args.list_targets),
) if on
),
None,
)
if mode is None:
return
given = _given_flags(args)
for flag in sorted(given - QUICK_MODE_FLAGS[mode] - {mode}):
parser.error(f"{flag} is incompatible with {mode}")
# What each mode that returns early reads. Anything else given beside it is
# refused: the mode would answer about an artifact the caller did not name.
QUICK_MODE_FLAGS: dict[str, set[str]] = {
# Checks packs already on disk against the platform's own list, narrowed
# by region and target and by nothing else.
"--verify-packs": {"--platform", "--all", "--target", "--region", "--include-archived"},
# One manifest per hardware target, from the target files themselves.
"--manifest-targets": set(),
"--list": set(),
"--list-emulators": set(),
"--list-systems": {"--platform"},
"--list-targets": {"--platform"},
}
def _given_flags(args) -> set[str]:
"""Flags that select or narrow what a run produces, as typed."""
flags = {
"--platform": args.platform,
"--all": args.all,
"--emulator": args.emulator,
"--system": args.system,
"--standalone": args.standalone,
"--split": args.split,
"--group-by": args.group_by != "system",
"--manifest": args.manifest,
"--manifest-targets": args.manifest_targets,
"--verify-packs": args.verify_packs,
"--all-variants": args.all_variants,
"--from-md5": args.from_md5,
"--from-md5-file": args.from_md5_file,
"--required-only": args.required_only,
"--source": args.source != "full",
"--target": args.target,
"--region": args.region,
"--one-per-slot": args.one_per_slot,
"--list": args.list,
"--list-emulators": args.list_emulators,
"--list-systems": args.list_systems,
"--list-targets": args.list_targets,
"--include-archived": args.include_archived,
"--refresh-data": args.refresh_data,
}
return {flag for flag, on in flags.items() if on}
def main():
parser = argparse.ArgumentParser(description="Generate platform BIOS ZIP packs")
@@ -2472,74 +2602,15 @@ def main():
args = parser.parse_args()
packresolve.set_offline(bool(args.offline))
# Parsed before the quick-exit modes: --verify-packs returns early and
# still needs the region priority list to narrow its expectation.
args.regions = []
if args.region:
try:
args.regions = region_mod.parse_requested(args.region)
except ValueError as exc:
parser.error(str(exc))
if args.manifest_targets:
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")
# --all-variants builds the six source x required combinations itself:
# a --source or --required-only beside it would be overridden, and the
# modes that build one pack (emulator, system, hashes) never read it.
if args.all_variants:
for flag, given in (
("--source", args.source != "full"),
("--required-only", args.required_only),
("--emulator", args.emulator),
("--from-md5", args.from_md5 or args.from_md5_file),
):
if given:
parser.error(f"{flag} is incompatible with --all-variants")
if args.system and not (args.platform or args.all):
parser.error("--all-variants requires --platform or --all")
if args.verify_packs and args.manifest:
parser.error("--verify-packs is incompatible with --manifest")
if args.verify_packs:
# The check that follows the build reads the full pack's name.
for flag, given in (
("--system", args.system),
("--one-per-slot", args.one_per_slot),
("--split", args.split),
):
if given:
parser.error(f"{flag} is incompatible with --verify-packs")
_refuse_unapplied_flags(args, parser)
# Quick-exit modes: --verify-packs alone = verify existing packs only
# Combined with --all-variants, generation runs first then verify
if args.verify_packs and not args.all_variants:
# This mode checks packs already on disk against the platform's own
# list, narrowed by the region priority above and by --target. A
# narrowing flag it cannot honour is refused rather than dropped: a
# dropped flag answers about an artifact the caller did not name.
for flag, given in (
("--one-per-slot", args.one_per_slot),
("--required-only", args.required_only),
("--source", args.source != "full"),
("--system", args.system),
):
if given:
parser.error(f"{flag} is incompatible with --verify-packs")
with _pack_output_lock(args.output_dir, exclusive=False):
_run_verify_packs(args)
return
if args.manifest_targets:
# This mode writes one manifest per hardware target from the target
# files themselves; it reads none of the narrowing flags. --region and
# --one-per-slot are refused above, and these three the same way.
for flag, given in (
("--target", args.target),
("--required-only", args.required_only),
("--source", args.source != "full"),
):
if given:
parser.error(f"{flag} is incompatible with --manifest-targets")
with _pack_output_lock(args.output_dir):
generate_target_manifests(
os.path.join(args.platforms_dir, "targets"), args.output_dir
@@ -2900,6 +2971,38 @@ def _manifest_core_entries(
return total_size
def _manifest_region_drops(
config: dict,
pack_systems: dict,
emulators_dir: str,
db: dict,
base_dest: str,
emu_profiles: dict,
target_cores: set[str] | None,
source: str,
required_only: bool,
regions: list[str] | None,
) -> set[str]:
"""Destinations a region list removes, grouped as the pack groups them."""
if not regions:
return set()
region_groups, _extra_dests = platform_region_groups(
config,
pack_systems,
emulators_dir,
db,
base_dest,
emu_profiles,
target_cores=target_cores,
include_extras=(source != "platform"),
include_all=(source == "truth"),
required_only=required_only,
)
return region_mod.resolve_region_drops(
region_groups, region_mod.build_region_index(emu_profiles), regions
)
def generate_manifest(
platform_name: str,
platforms_dir: str,
@@ -2932,6 +3035,10 @@ def generate_manifest(
base_dest = config.get("base_destination", "")
case_insensitive = config.get("case_insensitive_fs", False)
verification_mode = config.get("verification_mode", "existence")
manifest_profiles = {
name: emu_profiles[name]
for name in _platform_cores(config, emu_profiles)
}
# Load registry for install metadata
registry: dict = {}
@@ -2946,9 +3053,7 @@ def generate_manifest(
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Filter systems by target
from common import resolve_platform_cores
plat_cores = resolve_platform_cores(config, emu_profiles) if target_cores else None
plat_cores = _platform_cores(config, emu_profiles) if target_cores else None
pack_systems = filter_systems_by_target(
config.get("systems", {}),
emu_profiles,
@@ -2965,6 +3070,7 @@ def generate_manifest(
# Sizes of the files the pack carries and the installer cannot fetch: the
# collection does not index them, a data directory cache answered.
pack_only_sizes: list[int] = []
rename_notes: set[str] = set()
if data_registry is None:
data_registry = load_data_dir_registry(platforms_dir)
slot_overrides = slots.pack_overrides(
@@ -2992,23 +3098,10 @@ def generate_manifest(
"cores": cores,
}
region_drops: set[str] = set()
if regions:
region_index = region_mod.build_region_index(emu_profiles)
region_groups, _extra_dests = platform_region_groups(
config,
pack_systems,
emulators_dir,
db,
base_dest,
emu_profiles,
target_cores=target_cores,
include_extras=(source != "platform"),
include_all=(source == "truth"),
)
region_drops = region_mod.resolve_region_drops(
region_groups, region_index, regions
)
region_drops = _manifest_region_drops(
config, pack_systems, emulators_dir, db, base_dest, emu_profiles,
target_cores, source, required_only, regions,
)
# Phase 1: baseline files
if source != "truth":
@@ -3057,24 +3150,26 @@ def generate_manifest(
)
continue
local_path, status = resolve_file(
file_entry,
db,
bios_dir,
zip_contents,
data_dir_registry=data_registry,
offline=offline,
)
override = slot_overrides.get(full_dest)
if override:
local_path, status = override, "slot_arbitrated"
if (
status == "hash_mismatch"
and local_path
and file_entry.get("zipped_file")
and _inner_rom_check(file_entry, local_path) == "ok"
):
status = "zip_exact"
else:
local_path, status = resolve_file(
file_entry,
db,
bios_dir,
zip_contents,
data_dir_registry=data_registry,
offline=offline,
)
if status == "not_found" and not reads_file_contents(verification_mode):
found = agnostic_substitute(file_entry, sys_id, db, manifest_profiles)
if found:
local_path, status = found[0], "agnostic_fallback"
if os.path.basename(local_path) != file_entry.get("name", ""):
# The pack explains the rename in a note of its own.
rename_notes.add(file_entry.get("name", ""))
status = settle_mismatch(file_entry, local_path, status, verification_mode)
# An existence platform never reads the bytes, so a declared
# hash the local dump contradicts is not a reason to withhold
# the file. Hash platforms would reject it, so they omit it.
@@ -3192,7 +3287,10 @@ def generate_manifest(
"standalone_copies": standalone_copies,
"total_files": len(manifest_files),
"total_size": total_size,
"pack_files": len(manifest_files) + len(data_sizes) + len(PACK_DOCUMENTS),
"pack_files": (
len(manifest_files) + len(data_sizes) + len(PACK_DOCUMENTS)
+ len(rename_notes)
),
"pack_size": total_size + sum(data_sizes),
"total_omitted": len(omitted_by_destination),
"omitted_files": sorted(
@@ -3388,12 +3486,15 @@ def verify_and_finalize_packs(
all_ok = True
# Map ZIP names to platform names
# By the exact name this run's reduction gives a platform's pack, never by
# substring: a _Custom or regional pack left by another run was judged
# against this run's expectation and failed it.
registered = list_registered_platforms(platforms_dir, include_archived=True)
expected = _expected_pack_names(registered, platforms_dir, regions, target_name)
pack_to_platform: dict[str, list[str]] = {}
for name in _pack_archives(output_dir):
for pname in list_registered_platforms(platforms_dir, include_archived=True):
cfg = load_platform_config(pname, platforms_dir)
display = cfg.get("platform", pname).replace(" ", "_")
if display in name or display.replace("_", "") in name.replace("_", ""):
for pname in registered:
if name in expected[pname]:
pack_to_platform.setdefault(name, []).append(pname)
for name in _pack_archives(output_dir):
+8 -6
View File
@@ -38,8 +38,10 @@ from common import (
load_emulator_profiles,
load_provenance_snapshots,
parse_md5_list,
preferred_profile,
require_yaml,
unique_emulator_profiles,
upstream_profile_index,
write_if_changed as _write_artifact,
yaml_load,
)
@@ -3116,11 +3118,11 @@ def generate_cross_reference(
k: v for k, v in profiles.items() if v.get("type") not in ("alias", "test")
}
# Build core -> profile lookup by core name
core_to_profile: dict[str, str] = {}
for pname, p in unique.items():
for core in p.get("cores", [pname]):
core_to_profile[str(core)] = pname
# 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({
@@ -3463,7 +3465,7 @@ open them depends on their name:
| Setup | What it is | Pack | Extract to |
|-------|-----------|------|-----------|
| [RetroPie](https://retropie.org.uk/) | The classic Pi emulation setup, largest community, most online guides | [RetroArch]({rel}) | `~/RetroPie/BIOS/` |
| [RetroPie](https://retropie.org.uk/) | The classic Pi emulation setup, largest community, most online guides | [RetroPie]({rel}) | `~/RetroPie/BIOS/` |
| [Lakka](https://www.lakka.tv/) | Lightweight RetroArch OS, minimal config, boots straight into the UI | [RetroArch]({rel}) | `/storage/system/` |
| [Batocera](https://batocera.org/) | Easy setup, works on Pi 3/4/5 and many other boards (Odroid, etc.) | [Batocera]({rel}) | `/userdata/bios/` |
| [Recalbox](https://www.recalbox.com/) | Plug-and-play experience, good for a first build | [Recalbox]({rel}) | `/recalbox/share/bios/` |
+59 -52
View File
@@ -6,7 +6,9 @@ against the hash the caller declares."""
from __future__ import annotations
import contextlib
import http.client
import os
import sys
import re
import tempfile
import urllib.error
@@ -98,6 +100,13 @@ def asset_candidates(name: str, registered: Iterable[str]) -> list[str]:
matches = [
names[p] for p in sorted(names) if os.path.basename(p) == name
]
# A pinned revision is stored as .variants/<name>.<md5 prefix>: ROCKNIX
# pins PS3UPDAT.PUP to the variant, and asking for the bare name never
# reached its asset.
matches += [
names[p] for p in sorted(names)
if "/.variants/" in p and os.path.basename(p).startswith(name + ".")
]
return matches or [name]
@@ -150,21 +159,15 @@ def _fetch_asset(
hashes = None
if hashes is None:
pass
elif expected_sha1 or expected_md5:
if expected_sha1 and hashes["sha1"].lower() != expected_sha1.lower():
_drop(cached)
elif expected_md5:
md5_list = [
m.strip().lower() for m in expected_md5.split(",") if m.strip()
]
if hashes["md5"].lower() not in md5_list:
_drop(cached)
else:
return cached
else:
return cached
else:
elif not (expected_sha1 or expected_md5) or _matches(
hashes, expected_sha1, expected_md5
):
return cached
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
if offline:
return None
@@ -182,42 +185,46 @@ def _fetch_asset(
if " " in name:
candidates.append(name.replace(" ", "."))
downloaded = False
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:
with open(tmp_path, "wb") as f:
while True:
chunk = resp.read(65536)
if not chunk:
break
f.write(chunk)
downloaded = True
break
except (urllib.error.URLError, urllib.error.HTTPError):
if os.path.exists(tmp_path):
os.unlink(tmp_path)
if not downloaded:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
return None
if expected_sha1 or expected_md5:
hashes = compute_hashes(tmp_path)
if expected_sha1 and hashes["sha1"].lower() != expected_sha1.lower():
os.unlink(tmp_path)
try:
downloaded = False
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:
with open(tmp_path, "wb") as f:
while True:
chunk = resp.read(65536)
if not chunk:
break
f.write(chunk)
downloaded = True
break
except (urllib.error.URLError, OSError, http.client.HTTPException) as exc:
# A stalled or cut stream is a timeout or IncompleteRead, not
# a URLError: it escaped this handler and left its scratch.
print(f" large file {candidate}: {exc}", file=sys.stderr)
if not downloaded:
return None
if expected_md5:
md5_list = [m.strip().lower() for m in expected_md5.split(",") if m.strip()]
if hashes["md5"].lower() not in md5_list:
os.unlink(tmp_path)
return None
os.replace(tmp_path, cached)
return cached
if (expected_sha1 or expected_md5) and not _matches(
compute_hashes(tmp_path), expected_sha1, expected_md5
):
return None
os.replace(tmp_path, cached)
return cached
finally:
_drop(tmp_path)
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():
return False
if expected_md5:
md5_list = [m.strip().lower() for m in expected_md5.split(",") if m.strip()]
return hashes["md5"].lower() in md5_list
return True
+90 -5
View File
@@ -6,6 +6,7 @@ this module builds so the report and the pack withdraw the same files."""
from __future__ import annotations
from common import PROFILE_IDENTITY_FIELDS
from common import expand_platform_declared_names
from common import load_emulator_profiles
from common import resolution_is_hash_exact
@@ -265,9 +266,82 @@ def _agnostic_scan_extras(
"agnostic_scan": True,
}
)
extras.extend(_companion_extras(
_companions(path, scan_name, _companion_extensions(profile, fname), files_db),
folder, extras_prefix, seen_dests, profile, emu_name, f,
))
return extras
def _companion_extras(
companions: list[tuple[str, dict]],
folder: str,
extras_prefix: str,
seen_dests: set[str],
profile: dict,
emu_name: str,
seed: dict,
) -> list[dict]:
"""Extras for the companions of one scanned image, placed beside it."""
extras: list[dict] = []
for companion_sha1, companion in companions:
companion_dest = f"{folder}/{companion['name']}" if folder else companion["name"]
companion_full = f"{extras_prefix}/{companion_dest}" if extras_prefix else companion_dest
if companion_full in seen_dests:
continue
seen_dests.add(companion_full)
extras.append({
"name": companion["name"],
"destination": companion_dest,
"sha1": companion_sha1,
"required": False,
"hle_fallback": False,
"source_emulator": profile.get("emulator", emu_name),
"source_profile": emu_name,
"source_system": seed.get("system"),
"source_systems": list(profile.get("systems", [])),
"region": seed.get("region"),
"variant_group": seed.get("variant_group"),
"agnostic_scan": True,
})
return extras
def _companion_extensions(profile: dict, seed_name: str) -> set[str]:
"""Extensions the profile declares beside a free-named BIOS entry.
PCSX2 reads `<bios>.rom1` or the BIOS path with its extension replaced
(`<stem>.rom1`), and likewise nvm and mec (BiosTools.cpp:214-229,
CDVD.cpp:160-195). A profile states that by naming the companions after
its BIOS entry: ps2-0230a-20080220.rom1 beside ps2-0230a-20080220.bin.
"""
stem = seed_name.rsplit(".", 1)[0].lower()
exts: set[str] = set()
for entry in profile.get("files", []):
other = str(entry.get("name", "")).lower()
if other != seed_name.lower() and other.startswith(stem + "."):
exts.add(other[len(stem) + 1:])
return exts
def _companions(
image_path: str, image_name: str, extensions: set[str], files_db: dict
) -> list[tuple[str, dict]]:
"""Files beside a scanned image that the core opens as its companions."""
if not extensions:
return []
directory = image_path.rsplit("/", 1)[0] + "/"
stem = image_name.rsplit(".", 1)[0].lower()
wanted = {f"{image_name.lower()}.{ext}" for ext in extensions}
wanted |= {f"{stem}.{ext}" for ext in extensions}
return sorted(
(sha1, entry)
for sha1, entry in files_db.items()
if entry.get("path", "").rsplit("/", 1)[0] + "/" == directory
and str(entry.get("name", "")).lower() in wanted
)
def _archive_prefix_extras(
profiles: dict,
relevant: set,
@@ -442,7 +516,7 @@ def _collect_emulator_extras(
# validation block; dropping those fields here would let a same-named
# but different payload enter a generated pack.
expected = u.get("expected") or {}
for identity in ("sha1", "md5", "sha256", "crc32", "size"):
for identity in PROFILE_IDENTITY_FIELDS:
declared = u.get(identity)
if declared in (None, "", []):
declared = expected.get(identity)
@@ -527,7 +601,7 @@ def _collect_emulator_extras(
# The same identity the first pass carries: without it the copy
# resolves on its name alone and a same-named file of another
# game fills the slot the profile marks as not held.
for identity in ("sha1", "md5", "sha256", "crc32", "size", "unsourceable"):
for identity in PROFILE_IDENTITY_FIELDS:
if f.get(identity) not in (None, "", []):
extra[identity] = f[identity]
extras.append(extra)
@@ -555,6 +629,13 @@ def _extra_system_ids(extra: dict) -> list[str]:
return [str(explicit)]
return [str(value) for value in extra.get("source_systems", []) if value]
def _kept(entries: list[dict], required_only: bool) -> list[dict]:
"""The entries a pack keeps: optional ones go under --required-only."""
if not required_only:
return entries
return [e for e in entries if e.get("required") is not False]
def platform_region_groups(
config: dict,
systems: dict,
@@ -566,6 +647,7 @@ def platform_region_groups(
target_cores: set[str] | None = None,
include_extras: bool = True,
include_all: bool = False,
required_only: bool = False,
) -> tuple[dict[str, list[tuple[str, str]]], dict[tuple[str, str, str], str]]:
"""Group a platform's pack candidates the way region filtering reads them.
@@ -578,7 +660,9 @@ def platform_region_groups(
groups: dict[str, list[tuple[str, str]]] = {}
for sys_id, system in systems.items():
members = groups.setdefault(sys_id, [])
for file_entry in system.get("files", []):
# Decided over what the pack keeps: an optional winner removed
# afterwards left its slot empty.
for file_entry in _kept(system.get("files", []), required_only):
dest = sanitize_pack_path(
file_entry.get("destination", file_entry.get("name", ""))
)
@@ -589,7 +673,7 @@ def platform_region_groups(
if not include_extras or db is None:
return groups, extra_dests
for extra in _collect_emulator_extras(
extras = _collect_emulator_extras(
config,
emulators_dir,
db,
@@ -598,7 +682,8 @@ def platform_region_groups(
emu_profiles,
target_cores=target_cores,
include_all=include_all,
):
)
for extra in _kept(extras, required_only):
dest = sanitize_pack_path(extra.get("destination", extra.get("name", "")))
if not dest:
continue
+15 -5
View File
@@ -19,6 +19,7 @@ def _build_readme(
undecidable_slots: list[str] | None = None,
narrowings: list[tuple[str, str]] | None = None,
system_filter: list[str] | None = None,
bios_paths: list[str] | None = None,
) -> str:
"""Build a personalized step-by-step README for each platform pack."""
narrowings = narrowings or []
@@ -128,8 +129,9 @@ def _build_readme(
" ----------------\n"
" 1. Open Dolphin file manager\n"
" 2. Show hidden files (Ctrl+H)\n"
" 3. Navigate to ~/retrodeck/bios/\n"
" 4. Extract all files from this archive directly into ~/retrodeck/bios/\n\n"
" 3. Navigate to ~/retrodeck/\n"
" 4. Extract all files from this archive directly into ~/retrodeck/\n"
" (the archive already holds the bios/ and roms/ folders)\n\n"
" NOTE: RetroDECK uses its own BIOS checker. After\n"
" copying, open RetroDECK > Tools > BIOS Checker to\n"
" verify everything is detected.\n\n"
@@ -138,8 +140,8 @@ def _build_readme(
"INSTALLATION GUIDE (Windows)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Download and run install.bat from:\n"
" https://github.com/Abdess/retrobios/releases\n\n"
" Open PowerShell and run:\n\n"
" irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open your RetroBat installation folder\n"
@@ -186,11 +188,19 @@ def _build_readme(
# Lakka uses same guide as RetroArch
guides["lakka"] = guides["retroarch"]
# A platform without a written guide is told where its files go by the
# registry, which the installer reads too: "your BIOS directory" sent
# MiSTer users looking for a folder MiSTer does not have.
targets = [path for path in bios_paths or [] if path]
where = (
" or ".join(f"{path}/" for path in targets)
if targets else "your BIOS directory"
)
guide = guides.get(
platform_name,
(
f"INSTALLATION\n\n"
f" 1. Extract all files from this archive directly into your BIOS directory\n"
f" 1. Extract all files from this archive directly into {where}\n"
f" 2. Overwrite if asked\n\n"
),
)
+68 -16
View File
@@ -17,6 +17,7 @@ from packpaths import _register_path
from ziptools import build_zip_contents_index
from ziptools import check_inside_zip
from nativemode import digest_algorithm
from validation import settle_mismatch
from nativemode import hash_mismatch_excludes_file
import hashlib
from common import filter_systems_by_target
@@ -30,6 +31,62 @@ from packresolve import resolve_file
from common import resolve_platform_cores
from common import sanitize_pack_path
import zipfile
import io
from collections.abc import Callable
def _members_are_held(data: bytes, by_md5: dict, held_inside: dict) -> bool:
"""Whether every member of an archive is a dump the collection holds."""
try:
with zipfile.ZipFile(io.BytesIO(data)) as archive:
members = [i for i in archive.infolist() if not i.is_dir()]
if not members:
return False
for info in members:
digest = hashlib.md5(archive.read(info)).hexdigest()
if digest not in by_md5 and digest not in held_inside:
return False
except (zipfile.BadZipFile, OSError):
return False
return True
def _once(build: Callable[[], dict]) -> Callable[[], dict]:
"""A value built on first use and kept."""
built: list[dict] = []
def get() -> dict:
if not built:
built.append(build())
return built[0]
return get
def _settle_untracked(
status: str,
name: str,
zf: zipfile.ZipFile,
by_md5: dict,
held_inside: Callable[[], dict],
errors: list[str],
file_name: str,
) -> tuple[str, str]:
"""Status of a member no hash recognised, and the name to record.
An archive the builder assembled (a MAME clone set) passes when every
member is a dump the collection holds, loose or inside a romset. Bytes
nothing recognises were written wrong or come from a source that no
longer matches: counted and passed, they went unseen.
"""
if status != "untracked":
return status, file_name
if name.endswith(".zip") and _members_are_held(zf.read(name), by_md5, held_inside()):
return "verified_members", os.path.basename(name)
errors.append(f"{name}: content matches no collected file")
return "untracked", file_name
def verify_pack(
zip_path: str, db: dict, data_registry: dict | None = None
) -> tuple[bool, dict]:
@@ -41,6 +98,9 @@ def verify_pack(
"""
files_db = db.get("files", {}) # SHA1 -> file_info
by_md5 = db.get("indexes", {}).get("by_md5", {}) # MD5 -> SHA1
# Built on the first archive no hash recognises, once per pack: the
# index opens every archive of the collection.
held_inside = _once(lambda: build_zip_contents_index(db))
by_name = db.get("indexes", {}).get("by_name", {}) # name -> [SHA1]
# Data directory file index
@@ -72,7 +132,7 @@ def verify_pack(
if info.is_dir():
continue
name = info.filename
if name.startswith("INSTRUCTIONS_") or name in (
if name.startswith(("INSTRUCTIONS_", "RENAMED_")) or name in (
"manifest.json",
"README.txt",
):
@@ -182,6 +242,10 @@ def verify_pack(
except (zipfile.BadZipFile, OSError):
continue
status, file_name = _settle_untracked(
status, name, zf, by_md5, held_inside, errors, file_name
)
manifest["files"].append(
{
"path": name,
@@ -404,22 +468,10 @@ def _intentional_hash_exclusion(
data_dir_registry=data_dir_registry,
offline=True,
)
if status != "hash_mismatch":
# The builder ships what the frontend would accept: the exact inner
# ROM of a zipped_file entry, or a file its own digest matches.
if settle_mismatch(entry, local_path, status, verification_mode) != "hash_mismatch":
return False
# A container can mismatch the outer declaration while still carrying
# the exact inner ROM requested by Batocera-style zipped_file entries.
zipped_file = entry.get("zipped_file")
if zipped_file and local_path:
declared = str(entry.get("md5") or "")
candidates = [value.strip() for value in declared.split(",") if value.strip()]
if not candidates:
candidates = [""]
if any(
check_inside_zip(local_path, zipped_file, candidate) == "ok"
for candidate in candidates
):
return False
return True
def _structural_errors(zf, zip_set: set) -> list[str]:
+120 -69
View File
@@ -71,6 +71,14 @@ def parse_verify_counts(output: str) -> dict[str, tuple[int, int]]:
return counts
def _full_pack_label(label: str) -> str:
"""The pack name, or "" for a reduced variant such as [source=platform]."""
meta = re.search(r"\s+\[([^\]]+)\]$", label)
if meta is None:
return label
return label[: meta.start()] if meta.group(1) == "source=full" else ""
def parse_pack_counts(output: str) -> dict[str, tuple[int, int]]:
"""Extract per-pack OK/total from generate_pack output.
@@ -81,11 +89,13 @@ def parse_pack_counts(output: str) -> dict[str, tuple[int, int]]:
for line in output.splitlines():
m = re.match(r"Generating (?:shared )?pack for (.+)\.\.\.", line)
if m:
# Labels carry execution metadata such as ``[source=full]``.
# It is not part of the platform identity used for consistency.
current_label = re.sub(
r"\s+\[source=[^\]]+\]$", "", m.group(1).strip()
)
# Labels carry execution metadata such as ``[source=full]``. Only
# the full, unreduced pack is what verify describes: with
# --all-variants the six variants shared one label and the last
# (platform, required) was compared to the full report.
current_label = _full_pack_label(m.group(1).strip())
continue
if not current_label:
continue
if "files packed" not in line:
continue
@@ -93,7 +103,6 @@ def parse_pack_counts(output: str) -> dict[str, tuple[int, int]]:
base_m = re.search(r"\((\d+) baseline", line)
ok_m = re.search(r"(\d+)/(\d+) files OK", line)
if base_m and ok_m:
int(base_m.group(1))
ok, total = int(ok_m.group(1)), int(ok_m.group(2))
counts[current_label] = (ok, total)
elif ok_m:
@@ -144,6 +153,15 @@ def check_consistency(verify_output: str, pack_output: str) -> bool:
print("\n--- 5/8 consistency check ---")
all_ok = True
if not v or not p:
# Nothing parsed is nothing compared: a changed output format would
# otherwise pass the check without a single platform in it.
print(f" parsed {len(v)} verify and {len(p)} pack results: nothing to compare")
all_ok = False
for p_label in sorted(p):
if not any(_match_key(p_label) & _match_key(v_label) for v_label in v):
print(f" {p_label}: pack with no verify result")
all_ok = False
for v_label, (v_ok, v_total) in sorted(v.items()):
# Match by normalized name overlap. Platform display labels and
@@ -205,6 +223,81 @@ class _Skipped:
SKIPPED = _Skipped()
def _refuse_unapplied_flags(parser: argparse.ArgumentParser, args) -> None:
"""--all-variants builds every source itself: a --source beside it is dropped."""
if args.all_variants and args.source != "full":
parser.error("--source is incompatible with --all-variants")
def _truth_steps(args: argparse.Namespace, results: dict) -> bool:
"""Steps 2c to 2e: truth model, its diff, the native export."""
all_ok = True
# Step 2c: Generate truth YAMLs
# A targeted run writes its model in a subdirectory of its own, and the
# diff has to read the same one or it compares a narrowed model against
# the whole scrape.
truth_dir = Path(args.output_dir) / "truth"
if args.target:
truth_dir = truth_dir / re.sub(
r"[^a-z0-9]+", "-", args.target.strip().lower()
).strip("-")
if args.with_truth or args.with_export:
truth_cmd = [
sys.executable,
"scripts/generate_truth.py",
"--all",
"--output-dir",
str(Path(args.output_dir) / "truth"),
]
if args.target:
truth_cmd.extend(["--target", args.target])
ok, _ = run(truth_cmd, "2c generate truth")
# A native file is one per platform, never per target: the export
# corrects it against the full model, which a targeted run does not
# write.
if ok and args.with_export and args.target:
ok, _ = run(truth_cmd[:-2], "2c generate truth (export model)")
results["generate_truth"] = ok
all_ok = all_ok and ok
else:
results["generate_truth"] = SKIPPED
# Step 2d: Diff truth vs scraped
if args.with_truth or args.with_export:
diff_cmd = [sys.executable, "scripts/diff_truth.py", "--all"]
diff_cmd.extend(["--truth-dir", str(truth_dir)])
ok, _ = run(diff_cmd, "2d diff truth")
results["diff_truth"] = ok
all_ok = all_ok and ok
else:
results["diff_truth"] = SKIPPED
# Step 2e: Export native formats
if args.with_export:
export_cmd = [
sys.executable,
"scripts/export_native.py",
"--all",
"--output-dir",
str(Path(args.output_dir) / "upstream"),
"--truth-dir",
str(Path(args.output_dir) / "truth"),
]
# Seven of the formats carry code, so the export patches the
# platform's own file rather than regenerating it. Offline that
# file has to be in the cache already.
if not args.offline:
export_cmd.append("--fetch")
ok, _ = run(export_cmd, "2e export native")
results["export_native"] = ok
all_ok = all_ok and ok
else:
results["export_native"] = SKIPPED
return all_ok
def main():
parser = argparse.ArgumentParser(description="Run the full retrobios pipeline")
parser.add_argument(
@@ -249,8 +342,7 @@ def main():
help="Export native formats (implies --with-truth)",
)
args = parser.parse_args()
if args.all_variants and args.source != "full":
parser.error("--source is incompatible with --all-variants")
_refuse_unapplied_flags(parser, args)
# A second run on the same output directory is refused before any work:
# the database rebuild alone takes minutes, and the reader holding the
@@ -363,63 +455,7 @@ def main():
print("\n--- 2b2 check release assets: SKIPPED (--offline) ---")
results["check_release_assets"] = SKIPPED
# Step 2c: Generate truth YAMLs
# A targeted run writes its model in a subdirectory of its own, and the
# diff has to read the same one or it compares a narrowed model against
# the whole scrape.
truth_dir = Path(args.output_dir) / "truth"
if args.target:
truth_dir = truth_dir / re.sub(
r"[^a-z0-9]+", "-", args.target.strip().lower()
).strip("-")
if args.with_truth or args.with_export:
truth_cmd = [
sys.executable,
"scripts/generate_truth.py",
"--all",
"--output-dir",
str(Path(args.output_dir) / "truth"),
]
if args.target:
truth_cmd.extend(["--target", args.target])
ok, _ = run(truth_cmd, "2c generate truth")
results["generate_truth"] = ok
all_ok = all_ok and ok
else:
results["generate_truth"] = SKIPPED
# Step 2d: Diff truth vs scraped
if args.with_truth or args.with_export:
diff_cmd = [sys.executable, "scripts/diff_truth.py", "--all"]
diff_cmd.extend(["--truth-dir", str(truth_dir)])
ok, _ = run(diff_cmd, "2d diff truth")
results["diff_truth"] = ok
all_ok = all_ok and ok
else:
results["diff_truth"] = SKIPPED
# Step 2e: Export native formats
if args.with_export:
export_cmd = [
sys.executable,
"scripts/export_native.py",
"--all",
"--output-dir",
str(Path(args.output_dir) / "upstream"),
"--truth-dir",
str(Path(args.output_dir) / "truth"),
]
# Seven of the formats carry code, so the export patches the
# platform's own file rather than regenerating it. Offline that
# file has to be in the cache already.
if not args.offline:
export_cmd.append("--fetch")
ok, _ = run(export_cmd, "2e export native")
results["export_native"] = ok
all_ok = all_ok and ok
else:
results["export_native"] = SKIPPED
all_ok = _truth_steps(args, results) and all_ok
# Step 3: Verify
verify_cmd = [sys.executable, "scripts/verify.py", "--all"]
@@ -444,10 +480,14 @@ def main():
# Step 3b: Destinations both layers claim, and how each was settled. The
# ones the pack settles by itself must stay at zero cost; the rest name an
# upstream declaration no build can repair, so this reports and never gates.
run(
# A crash is not a report: without --strict slots.py exits 0 on every
# conflict, so a non-zero code means it never finished reading.
ok, _ = run(
[sys.executable, "scripts/slots.py"],
"3b/8 slot arbitration",
)
results["slots"] = ok
all_ok &= ok
# Step 4: Generate packs
pack_output = ""
@@ -539,8 +579,14 @@ def main():
print("\n--- 4c/8 generate target manifests: SKIPPED (--skip-packs) ---")
results["generate_target_manifests"] = SKIPPED
# Step 5: Consistency check
if pack_output and verify_output:
# Step 5: Consistency check. verify describes the full pack; a run that
# built only a reduced one (--source platform or truth) has nothing to
# compare it with, and says so instead of failing on an empty parse.
full_pack_built = args.source == "full" or args.all_variants
if pack_output and verify_output and not full_pack_built:
print("\n--- 5/8 consistency check: SKIPPED (no full pack built) ---")
results["consistency"] = SKIPPED
elif pack_output and verify_output:
ok = check_consistency(verify_output, pack_output)
results["consistency"] = ok
all_ok = all_ok and ok
@@ -549,7 +595,12 @@ def main():
results["consistency"] = SKIPPED
# Step 6: Pack integrity (extract + hash verification)
if not args.skip_packs:
if not args.skip_packs and not full_pack_built:
# The check reads the full pack's name; a reduced pack holds fewer
# files than the platform declares by design.
print("\n--- 6/8 pack integrity: SKIPPED (no full pack built) ---")
results["pack_integrity"] = SKIPPED
elif not args.skip_packs:
integrity_cmd = [
sys.executable,
"scripts/generate_pack.py",
+180 -86
View File
@@ -11,6 +11,7 @@ from __future__ import annotations
import argparse
import contextlib
import difflib
import itertools
import json
import os
import posixpath
@@ -18,7 +19,7 @@ import re
import subprocess
import sys
import tempfile
from dataclasses import asdict, dataclass
from dataclasses import asdict, dataclass, field
from datetime import date
from pathlib import Path
@@ -998,19 +999,26 @@ class EntryReport:
class ProfileReport:
name: str
repo: str | None = None
repos: list[str] = None
repos: list[str] = field(default_factory=list)
pinned_tag: str | None = None
host: str | None = None
pin: str | None = None
pin_origin: str | None = None
head: str | None = None
entries: list[EntryReport] = None
entries: list[EntryReport] = field(default_factory=list)
skipped: str | None = None
counts: dict[str, int] = None
counts: dict[str, int] = field(default_factory=dict)
# Declared repositories whose forge the tool cannot read. They are named
# rather than dropped: a `source` on an unknown host used to fall back to
# `upstream` in silence, and a divergence between the two went unseen.
unread: list[str] = None
unread: list[str] = field(default_factory=list)
# The upstream view when the profile declares one: its pin and HEAD. A
# recale moves its parts onto upstream HEAD lines, so a bump advances
# upstream_commit with source_commit, or the profile describes two
# revisions at once (geolith jg.c drifted 213 -> 221 -> 229).
upstream_pin: str | None = None
upstream_head: str | None = None
upstream_origin: str | None = None
def needs_review(self) -> int:
counts = self.counts or {}
@@ -1194,22 +1202,32 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
)
def reconcile_self_check(parts: list[PartResult]) -> list[PartResult]:
def reconcile_self_check(
parts: list[PartResult], eligible: list[bool] | None = None
) -> list[PartResult]:
"""Judge a self-checked entry whole rather than part by part.
A ref often cites both the table carrying the value and the code that
loads the file. Once one part anchors on the value, the others describe
behaviour and cannot be judged by value: the checker would only rediscover
the value where the first part already points. A structurally absent part
still counts, since that verdict does not rest on the value.
loads the file. Once one part anchors on the value, the others in the
SAME file describe behaviour and cannot be judged by value: the checker
would only rediscover the value where the first part already points. A
part in another file carries its own value and is judged on it: geolith's
jg.c part, sixteen lines off, was settled by an anchored libretro.c part.
A structurally absent part still counts, and a part that was compared
against HEAD rather than self-checked is never rewritten.
"""
if not any(part.status == "ANCHORED" for part in parts):
return parts
eligible = eligible if eligible is not None else [True] * len(parts)
anchored_files = {
part.part.path
for part, ok in zip(parts, eligible)
if ok and part.status == "ANCHORED"
}
kept = ("ANCHORED", "GONE", "EXTERNAL", "BINARY")
return [
part if part.status in kept
else PartResult(part.part, "ANCHORED", None, None, None, [])
for part in parts
PartResult(part.part, "ANCHORED", None, None, None, [])
if ok and part.status not in kept and part.part.path in anchored_files
else part
for part, ok in zip(parts, eligible)
]
@@ -1256,6 +1274,28 @@ def detect_pinned_tag(
return None
def _resolve_project_words(refs: list, tree_order) -> list:
"""Point `project file` parts at the declared tree that vendors them.
tree_order() yields the trees to search, pin before HEAD; it is only
called for a part that needs it, since reading a HEAD tree costs a
request. Parts no tree carries stay external.
"""
resolved: dict[str, str | None] = {}
out = []
for display, ref, tokens, hashes, parts, citation in refs:
rewritten = []
for part in parts:
if is_external_citation(part.path):
if part.path not in resolved:
resolved[part.path] = resolve_project_word(part.path, tree_order())
if resolved[part.path]:
part = RefPart(resolved[part.path], part.start, part.end, part.raw)
rewritten.append(part)
out.append((display, ref, tokens, hashes, rewritten, citation))
return out
def build_report(
name: str, profile: dict, cache_dir: str, offline: bool = False
) -> ProfileReport:
@@ -1341,6 +1381,11 @@ def build_report(
report.repos = [v.repo.slug for v in views]
report.pin, report.pin_origin = primary.pin, primary.origin
report.head = primary.head
for view in views[1:]:
if view.field == "upstream":
report.upstream_pin = view.pin
report.upstream_head = view.head
report.upstream_origin = view.origin
# A profile can hold its pin on purpose, documenting a build that is that
# revision. Comparing it to HEAD says nothing and recaling would repoint
# its refs at code the build never had, so it is judged like a frozen tag:
@@ -1545,26 +1590,13 @@ def build_report(
candidates = near
return None, candidates
def _tree_order():
for view in views:
yield _pin_tree_for(view)
for view in views:
yield _context_for(view)[1]
resolved_words: dict[str, str | None] = {}
for index, (display, ref, tokens, hashes, parts, citation) in enumerate(refs):
rewritten = []
for part in parts:
if is_external_citation(part.path):
if part.path not in resolved_words:
resolved_words[part.path] = resolve_project_word(
part.path, _tree_order()
)
found = resolved_words[part.path]
if found:
part = RefPart(found, part.start, part.end, part.raw)
rewritten.append(part)
refs[index] = (display, ref, tokens, hashes, rewritten, citation)
# Pin trees before HEAD trees, each read only when reached.
refs = _resolve_project_words(
refs,
lambda: itertools.chain(
map(_pin_tree_for, views), map(lambda v: _context_for(v)[1], views)
),
)
lines_cache: dict[tuple[str, str, int | None], list[str] | None] = {}
@@ -1629,26 +1661,24 @@ def build_report(
)
# Comparing a revision with itself anchors every ref whatever it cites, so
# a profile already sitting on HEAD is judged on self-consistency instead.
self_check = bool(report.pinned_tag) or primary.pin == primary.head
if self_check:
staged = [
(entry_name, ref, citation, reconcile_self_check([
verify_at_pin(
part, fetch(PIN, part.path, part.start, tokens), tokens, hashes
)
for part in ref_parts
]))
for entry_name, ref, tokens, hashes, ref_parts, citation in refs
]
else:
staged = [
(entry_name, ref, citation, [
anchor_across_views(part, tokens)
for part in ref_parts
])
for entry_name, ref, tokens, hashes, ref_parts, citation in refs
# a part whose repository already sits on HEAD is judged on
# self-consistency instead. Decided per part, by the repository that owns
# it: a primary on HEAD says nothing about an upstream that moved on.
def self_checked(part, tokens) -> bool:
if report.pinned_tag:
return True
view, _ = resolve_path(part.path, part.start, tuple(tokens))
return view.pin == view.head
staged = []
for entry_name, ref, tokens, hashes, ref_parts, citation in refs:
flags = [self_checked(part, tokens) for part in ref_parts]
judged = [
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)))
shifts = dominant_shifts([p for _, _, _, parts in staged for p in parts])
for entry_name, ref, citation, parts in staged:
@@ -2409,39 +2439,58 @@ def pending_recale(
return pending
def _needs_review(report: ProfileReport, accept_changed: bool) -> bool:
"""Whether the report leaves a ref to read before the pin may move."""
if report.skipped or not report.head or report.pinned_tag:
return True
blocking = REVIEW_STATUSES if not accept_changed else (
s for s in REVIEW_STATUSES if s != "CHANGED"
)
return any((report.counts or {}).get(s) for s in blocking)
def bump_commit(
path: Path, report: ProfileReport, accept_changed: bool = False
) -> bool:
"""Advance source_commit to HEAD when nothing needs a read again."""
if report.skipped or not report.head or report.pinned_tag:
return False
blocking = REVIEW_STATUSES if not accept_changed else (
s for s in REVIEW_STATUSES if s != "CHANGED"
)
if any((report.counts or {}).get(s) for s in blocking):
if _needs_review(report, accept_changed):
return False
text = path.read_text(encoding="utf-8")
if pending_recale(report, accept_changed, text):
return False
document = yaml.safe_load(text)
if document.get("source_commit") == report.head:
upstream_moves = bool(
report.upstream_head and report.upstream_pin != report.upstream_head
)
if document.get("source_commit") == report.head and not upstream_moves:
# 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
if document.get("source_commit"):
new_text, _ = replace_field_line(
text, "source_commit", str(document["source_commit"]), report.head
)
else:
new_text = insert_after_line(
text, "profiled_date", f'source_commit: "{report.head}"'
new_text = _set_pin(text, document, "source_commit", report.head, "profiled_date")
if upstream_moves:
expected["upstream_commit"] = report.upstream_head
new_text = _set_pin(
new_text, document, "upstream_commit", report.upstream_head, "source_commit"
)
apply_edit(path, new_text, expected)
return True
def _set_pin(text: str, document: dict, field: str, value: str, after: str) -> str:
"""Write *field* as *value*, replacing its line or adding one after *after*."""
current = document.get(field)
if current == value:
return text
if current:
return replace_field_line(text, field, str(current), value)[0]
return insert_after_line(text, after, f'{field}: "{value}"')
def _git_history(path: Path) -> list[str]:
"""Commits touching the profile, newest first, empty outside a repo."""
result = subprocess.run(
@@ -2571,6 +2620,25 @@ def _realign_part(
return "skip", f"{part.path} absent at the writing revision"
def _writing_pairs(
document: dict, repos: list, revisions: list[tuple[str, dict]], intro_sha: str
) -> 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")
if not isinstance(current, str) or not current:
continue
written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit")
if len(written) > 1:
ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}")
elif written and written[0] != current:
pairs.append((repo, written[0], current))
return pairs, ambiguous
def realign_prose(
path: Path, cache_dir: str, offline: bool = False, dry_run: bool = False
) -> list[str]:
@@ -2629,17 +2697,7 @@ def realign_prose(
if intro_sha is None:
# The scalar is not committed yet: written now, under this pin.
continue
pairs = []
ambiguous: list[str] = []
for pin_field, repo in repos:
current = document.get(f"{pin_field}_commit")
if not isinstance(current, str) or not current:
continue
written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit")
if len(written) > 1:
ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}")
elif written and written[0] != current:
pairs.append((repo, written[0], current))
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.
@@ -2985,9 +3043,8 @@ def _print_triage(args, selected: dict, reports: list[ProfileReport]) -> None:
print(f"{score:5d} {report.name:30s} {state}")
def main() -> None:
"""Build one report per selected profile, then apply writes and output."""
args = build_parser().parse_args()
def _refuse_flag_combinations(args) -> None:
"""Exit on a flag the chosen mode would not apply, before any request."""
if args.accept_changed and not args.emulator:
print(
"--accept-changed applies to one profile at a time: its diff has "
@@ -3027,6 +3084,37 @@ def main() -> None:
file=sys.stderr,
)
raise SystemExit(1)
machine = [
flag for flag, on in (
("--json", args.as_json),
("--markdown", args.markdown),
("--fetch-plan", args.fetch_plan),
("--triage", args.triage),
) if on
]
extras = [
flag for flag, on in (
("--check-version", args.check_version),
("--detect-new-files", args.detect_new_files),
("--watch-hashes", args.watch_hashes),
("--full-diff", args.full_diff),
("--tree-diff", args.tree_diff),
) if on
]
if machine and extras:
# The extra sections only exist in the text report; the other outputs
# returned before reaching them and printed as if never asked.
print(
f"{', '.join(machine)} does not carry {', '.join(extras)}",
file=sys.stderr,
)
raise SystemExit(1)
def main() -> None:
"""Build one report per selected profile, then apply writes and output."""
args = build_parser().parse_args()
_refuse_flag_combinations(args)
profiles = load_emulator_profiles(args.emulators_dir, skip_aliases=False)
selected = select_profiles(profiles, args)
_check_quota(len(selected), args.offline)
@@ -3094,22 +3182,28 @@ def main() -> None:
# The guard already left the file untouched; keep going.
print(f"{name}: write refused: {exc}", file=sys.stderr)
# --changed-only narrows every output, not the text report alone: the
# documented `--all --changed-only --json` listed every profile.
shown = [
report for report in reports
if not args.changed_only or report.needs_review()
]
if args.as_json:
print(json.dumps([report_to_dict(r) for r in reports], indent=2))
print(json.dumps([report_to_dict(r) for r in shown], indent=2))
return
if args.markdown:
target = Path(args.report_dir) / f"profile-sync-{date.today().isoformat()}.md"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(format_markdown(reports), encoding="utf-8")
target.write_text(format_markdown(shown), encoding="utf-8")
print(f"written: {target}")
return
if args.fetch_plan:
for report in reports:
for report in shown:
for url in fetch_plan(report):
print(url)
return
if args.triage:
_print_triage(args, selected, reports)
_print_triage(args, selected, shown)
return
printed = 0
for report in reports:
+58 -6
View File
@@ -15,15 +15,65 @@ Usage:
from __future__ import annotations
import argparse
import hashlib
import json
import os
import sys
import zipfile
sys.path.insert(0, os.path.dirname(__file__))
from common import DEFAULT_PROVENANCE_DIR, load_database, load_provenance_snapshots
def build_report(db: dict, snapshots: dict) -> dict:
def archive_members(db: dict) -> dict[tuple[str, int], list[tuple[str, str]]]:
"""(crc32, size) of every member of the collection's ZIPs -> (zip, member).
Read from the central directories alone: a romset member is a dump the
collection already holds, and listing it as an acquisition target sent
searches after astrocdw.zip's bioswhit.bin and the Gamate BIOS.
"""
members: dict[tuple[str, int], list[tuple[str, str]]] = {}
for entry in db.get("files", {}).values():
path = entry.get("path", "")
if not path.endswith(".zip") or not os.path.exists(path):
continue
try:
with zipfile.ZipFile(path) as archive:
for info in archive.infolist():
if info.is_dir():
continue
key = (f"{info.CRC:08x}", info.file_size)
members.setdefault(key, []).append((path, info.filename))
except (zipfile.BadZipFile, OSError) as exc:
print(f" WARNING: {path}: {exc}", file=sys.stderr)
return members
def _held_in_archive(entry: dict, members: dict) -> bool:
"""Whether a catalog entry is a member of one of the collection's ZIPs.
crc32 and size only nominate candidates; a declared sha1 must match the
member's bytes.
"""
crc = str(entry.get("crc32") or "").lower()
size = entry.get("size")
if not crc or size is None:
return False
candidates = members.get((crc.zfill(8), int(size)), [])
sha1 = str(entry.get("sha1") or "").lower()
if not sha1:
return bool(candidates)
for path, name in candidates:
try:
with zipfile.ZipFile(path) as archive:
if hashlib.sha1(archive.read(name)).hexdigest() == sha1:
return True
except (zipfile.BadZipFile, OSError, KeyError) as exc:
print(f" WARNING: {path}:{name}: {exc}", file=sys.stderr)
return False
def build_report(db: dict, snapshots: dict, members: dict | None = None) -> dict:
"""Compare each snapshot against the collection.
A DAT counts as covered when the collection holds at least one of
@@ -34,6 +84,7 @@ def build_report(db: dict, snapshots: dict) -> dict:
split the target list is swamped by content the project never ships.
"""
by_sha1 = db.get("files", {})
members = members or {}
by_md5_size = {
(entry.get("md5", ""), entry.get("size", 0)) for entry in by_sha1.values()
}
@@ -44,10 +95,11 @@ def build_report(db: dict, snapshots: dict) -> dict:
covered_dats = set()
unmatched = []
for entry in snapshot["entries"]:
if entry.get("sha1") in by_sha1 or (
entry.get("md5"),
entry.get("size"),
) in by_md5_size:
if (
entry.get("sha1") in by_sha1
or (entry.get("md5"), entry.get("size")) in by_md5_size
or _held_in_archive(entry, members)
):
matched += 1
covered_dats.add(entry.get("dat", ""))
else:
@@ -84,7 +136,7 @@ def main() -> int:
print(f"No provenance snapshots in {args.provenance_dir}/")
return 0
report = build_report(db, snapshots)
report = build_report(db, snapshots, archive_members(db))
if args.json:
print(json.dumps(report, indent=2))
+15 -6
View File
@@ -52,8 +52,8 @@ def load_registry(registry_path: str = DEFAULT_REGISTRY) -> dict[str, dict]:
@contextlib.contextmanager
def _file_lock(lock_path: Path):
"""Hold an exclusive lock on lock_path, waiting for it if taken.
def _file_lock(lock_path: Path, shared: bool = False):
"""Hold a lock on lock_path, waiting for it if taken.
Several sessions refresh the same data directories: without it, two
swaps of one tree interleave and the second lands inside the first.
@@ -65,14 +65,24 @@ def _file_lock(lock_path: Path):
yield
return
lock_path.parent.mkdir(parents=True, exist_ok=True)
with open(lock_path, "w") as handle:
fcntl.flock(handle, fcntl.LOCK_EX)
with open(lock_path, "a") as handle:
fcntl.flock(handle, fcntl.LOCK_SH if shared else fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(handle, fcntl.LOCK_UN)
def cache_lock(cache_dir: str | Path, shared: bool = False):
"""The lock a refresh holds while it swaps cache_dir.
A reader takes it shared: a pack walking data/sdlpal while another run
swapped the tree shipped part of it, or nothing.
"""
cache_dir = Path(cache_dir)
return _file_lock(cache_dir.with_name(f".{cache_dir.name}.lock"), shared=shared)
def _load_versions(versions_path: str = VERSIONS_FILE) -> dict[str, dict]:
path = Path(versions_path)
if not path.exists():
@@ -381,8 +391,7 @@ def refresh_entry(
"""
if dry_run:
return _refresh_entry(key, entry, force, dry_run, versions_path)
cache_dir = Path(entry["local_cache"])
with _file_lock(cache_dir.with_name(f".{cache_dir.name}.lock")):
with cache_lock(entry["local_cache"]):
return _refresh_entry(key, entry, force, dry_run, versions_path)
+3
View File
@@ -442,6 +442,9 @@ class Scraper(BaseScraper):
"hash_type": "md5",
"verification_mode": "md5",
"cores": cores,
# Each component is one emulator RetroDECK ships and launches on
# its own; retroarch is the one that runs cores.
"standalone_cores": [c for c in cores if c != "retroarch"],
"systems": systems,
}
@@ -49,7 +49,7 @@ _NAME_OVERRIDES: dict[str, str] = {
_SKIP = {"retroarch_maincfg", "retroarch"}
def _fetch(url: str) -> str | None:
def _fetch(url: str) -> str:
headers = {"User-Agent": "retrobios-scraper/1.0"}
token = os.environ.get("GITHUB_TOKEN")
if token and "api.github.com" in url:
@@ -59,21 +59,18 @@ def _fetch(url: str) -> str | None:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
# A target written from a failed request loses its cores in silence.
raise RuntimeError(f"cannot fetch {url}: {e}") from e
def _list_emuscripts(api_url: str) -> list[str]:
"""List emulator script filenames from GitHub API."""
raw = _fetch(api_url)
if not raw:
return []
entries = json.loads(raw)
names = []
for e in entries:
name = e.get("name", "")
if name.endswith(".sh") or name.endswith(".ps1"):
names.append(name)
entries = json.loads(_fetch(api_url))
names = [
e["name"] for e in entries if e.get("name", "").endswith((".sh", ".ps1"))
]
if not names:
raise RuntimeError(f"no emulator scripts listed at {api_url}")
return names
@@ -136,15 +133,13 @@ class Scraper(BaseTargetScraper):
import os
target_path = os.path.join("platforms", "targets", "retroarch.yml")
if not os.path.exists(target_path):
return []
with open(target_path) as f:
data = yaml_load(f) or {}
# Find a target matching the architecture
for tname, tinfo in data.get("targets", {}).items():
if tinfo.get("architecture") == arch:
return tinfo.get("cores", [])
return []
for tinfo in data.get("targets", {}).values():
if tinfo.get("architecture") == arch and tinfo.get("cores"):
return tinfo["cores"]
raise RuntimeError(f"no {arch} core list in {target_path}")
def fetch_targets(self) -> dict:
steamos_cores = self._fetch_cores_for_target(STEAMOS_API, "SteamOS")
@@ -87,7 +87,7 @@ class Scraper(BaseTargetScraper):
def __init__(self, url: str = BUILDBOT_URL):
super().__init__(url=url)
def _fetch_url(self, url: str) -> str | None:
def _fetch_url(self, url: str) -> str:
try:
req = urllib.request.Request(
url, headers={"User-Agent": "retrobios-scraper/1.0"}
@@ -95,14 +95,12 @@ class Scraper(BaseTargetScraper):
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
# A target written from a failed request loses its cores in silence.
raise RuntimeError(f"cannot fetch {url}: {e}") from e
def _fetch_cores_for_target(self, path: str) -> list[str]:
url = f"{self.url}{path}/"
html = self._fetch_url(url)
if html is None:
return []
cores: list[str] = []
seen: set[str] = set()
for match in _HREF_RE.finditer(html):
@@ -134,10 +132,7 @@ class Scraper(BaseTargetScraper):
def _fetch_cores_for_recipe(self, recipe_path: str) -> list[str]:
url = f"{RECIPE_BASE_URL}{recipe_path}"
text = self._fetch_url(url)
if text is None:
return []
return self._parse_recipe_cores(text)
return self._parse_recipe_cores(self._fetch_url(url))
def fetch_targets(self) -> dict:
targets: dict[str, dict] = {}
@@ -145,8 +140,7 @@ class Scraper(BaseTargetScraper):
print(f" fetching {target_name}...", file=sys.stderr)
cores = self._fetch_cores_for_target(path)
if not cores:
print(f" warning: no cores found for {target_name}", file=sys.stderr)
continue
raise RuntimeError(f"no cores listed for {target_name}")
targets[target_name] = {
"architecture": arch,
"cores": cores,
@@ -156,8 +150,7 @@ class Scraper(BaseTargetScraper):
print(f" fetching {target_name} (recipe)...", file=sys.stderr)
cores = self._fetch_cores_for_recipe(recipe_path)
if not cores:
print(f" warning: no cores found for {target_name}", file=sys.stderr)
continue
raise RuntimeError(f"no cores listed for {target_name}")
targets[target_name] = {
"architecture": arch,
"cores": cores,
@@ -1,8 +1,9 @@
"""Scraper for RetroPie libretro core availability per platform.
"""Scraper for RetroPie package availability per platform.
Source: https://github.com/RetroPie/RetroPie-Setup/tree/master/scriptmodules/libretrocores
Parses rp_module_id and rp_module_flags from each scriptmodule to determine
which platforms each core supports.
Source: https://github.com/RetroPie/RetroPie-Setup/tree/master/scriptmodules
Parses rp_module_id and rp_module_flags from each scriptmodule of the
package sections the platform list reads (emulators, libretrocores, ports)
to determine which platforms each package supports.
"""
from __future__ import annotations
@@ -23,23 +24,31 @@ from . import BaseTargetScraper
PLATFORM_NAME = "retropie"
GITHUB_API_URL = (
"https://api.github.com/repos/RetroPie/RetroPie-Setup/contents"
"/scriptmodules/libretrocores"
"https://api.github.com/repos/RetroPie/RetroPie-Setup/contents/scriptmodules"
)
RAW_BASE_URL = (
"https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master"
"/scriptmodules/libretrocores/"
"https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/scriptmodules/"
)
# The sections the platform scraper reads: a standalone package missing here
# is filtered out of every targeted pack.
PACKAGE_DIRS = ("emulators", "libretrocores", "ports")
# Platform flag sets: flags that the platform possesses
# The flags RetroPie's set_platform_defaults, cpu_* and platform_* functions
# give each platform (scriptmodules/system.sh), with the video stack of the
# images it ships: videocore and dispmanx on rpi1-3, kms with fkms dispmanx
# on rpi4 (buster), kms on rpi5.
PLATFORM_FLAGS: dict[str, set[str]] = {
"rpi1": {"arm", "armv6", "rpi", "gles"},
"rpi2": {"arm", "armv7", "neon", "rpi", "gles"},
"rpi3": {"arm", "armv8", "neon", "rpi", "gles"},
"rpi4": {"arm", "armv8", "neon", "rpi", "gles", "gles3", "gles31"},
"rpi5": {"arm", "armv8", "neon", "rpi", "gles", "gles3", "gles31"},
"x86": {"x86"},
"x86_64": {"x86"},
"rpi1": {"rpi1", "32bit", "arm", "armv6", "rpi", "gles", "videocore", "dispmanx"},
"rpi2": {"rpi2", "32bit", "arm", "armv7", "neon", "rpi", "gles", "videocore",
"dispmanx"},
"rpi3": {"rpi3", "32bit", "arm", "armv8", "neon", "rpi", "gles", "videocore",
"dispmanx"},
"rpi4": {"rpi4", "32bit", "arm", "armv8", "neon", "rpi", "gles", "gles3", "gles31",
"mesa", "kms", "dispmanx"},
"rpi5": {"rpi5", "32bit", "arm", "armv8", "neon", "rpi", "gles", "gles3", "gles31",
"mesa", "kms"},
"x86": {"x86", "32bit", "gl", "vulkan", "x11"},
"x86_64": {"x86", "64bit", "gl", "vulkan", "x11"},
}
ARCH_MAP: dict[str, str] = {
@@ -52,14 +61,11 @@ ARCH_MAP: dict[str, str] = {
"x86_64": "x86_64",
}
# Flags that are build directives, not platform restrictions
_BUILD_FLAGS = {"nodistcc"}
_MODULE_ID_RE = re.compile(r'rp_module_id\s*=\s*["\']([^"\']+)["\']')
_MODULE_FLAGS_RE = re.compile(r'rp_module_flags\s*=\s*["\']([^"\']*)["\']')
def _fetch(url: str, accept: str = "text/plain") -> str | None:
def _fetch(url: str, accept: str = "text/plain") -> str:
headers = {"User-Agent": "retrobios-scraper/1.0", "Accept": accept}
token = os.environ.get("GITHUB_TOKEN")
if token and "api.github.com" in url:
@@ -69,29 +75,30 @@ def _fetch(url: str, accept: str = "text/plain") -> str | None:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8")
except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr)
return None
# A target written from a failed request loses its cores in silence.
raise RuntimeError(f"cannot fetch {url}: {e}") from e
def _is_available(flags_str: str, platform: str) -> bool:
"""Return True if the core is available on the given platform."""
"""Whether RetroPie enables a module on *platform*.
Port of rp_registerModule (scriptmodules/packages.sh): flags are read in
order from an enabled default; !all disables, a flag the platform has
enables, !flag disables when the platform has it. A flag the platform
does not know (sdl1, nodistcc) changes nothing, and a comparison against
the build host (:$__gcc_version:-lt:7) cannot be decided here, so it
leaves the module as it was.
"""
platform_has = PLATFORM_FLAGS.get(platform, set())
tokens = flags_str.split() if flags_str.strip() else []
for token in tokens:
if token in _BUILD_FLAGS:
continue
if token.startswith("!"):
# Exclusion: if platform has this flag, core is excluded
flag = token[1:]
if flag in platform_has:
return False
else:
# Requirement: platform must have this flag
if token not in platform_has:
return False
return True
enabled = True
for token in flags_str.split():
if token == "!all":
enabled = False
elif token in platform_has:
enabled = True
elif token.startswith("!") and token[1:] in platform_has:
enabled = False
return enabled
def _parse_module(content: str) -> tuple[str | None, str]:
@@ -104,52 +111,51 @@ def _parse_module(content: str) -> tuple[str | None, str]:
class Scraper(BaseTargetScraper):
"""Fetches RetroPie libretro core availability by parsing scriptmodules."""
"""Fetches RetroPie package availability by parsing scriptmodules."""
def __init__(self, url: str = GITHUB_API_URL):
super().__init__(url=url)
def _list_scriptmodules(self) -> list[str]:
"""Return list of .sh filenames from the libretrocores directory."""
raw = _fetch(self.url, accept="application/vnd.github+json")
if raw is None:
return []
try:
entries = json.loads(raw)
except json.JSONDecodeError as e:
print(f" JSON parse error: {e}", file=sys.stderr)
return []
return [e["name"] for e in entries if e.get("name", "").endswith(".sh")]
"""Return section/filename for every .sh of the package sections."""
names: list[str] = []
for section in PACKAGE_DIRS:
url = f"{self.url}/{section}"
entries = json.loads(_fetch(url, accept="application/vnd.github+json"))
listed = [
f"{section}/{e['name']}" for e in entries if e.get("name", "").endswith(".sh")
]
if not listed:
raise RuntimeError(f"no scriptmodules listed at {url}")
names.extend(listed)
return names
def _fetch_module(self, filename: str) -> str | None:
def _fetch_module(self, filename: str) -> str:
return _fetch(f"{RAW_BASE_URL}{filename}")
def fetch_targets(self) -> dict:
print(" listing RetroPie scriptmodules...", file=sys.stderr)
filenames = self._list_scriptmodules()
if not filenames:
print(" warning: no scriptmodules found", file=sys.stderr)
# {platform: [core_id, ...]}
platform_cores: dict[str, list[str]] = {p: [] for p in PLATFORM_FLAGS}
# A libretro and a standalone package can share a name (lr-dosbox,
# dosbox): a core list names each once.
platform_cores: dict[str, set[str]] = {p: set() for p in PLATFORM_FLAGS}
for filename in filenames:
content = self._fetch_module(filename)
if content is None:
continue
module_id, flags = _parse_module(content)
if not module_id:
print(f" warning: no rp_module_id in {filename}", file=sys.stderr)
continue
# Normalize: strip lr- prefix and convert hyphens to underscores
# to match emulator profile keys (lr-beetle-psx -> beetle_psx)
# A libretro package is lr-<core> with the buildbot name hyphenated
# (lr-beetle-psx -> beetle_psx); a standalone package keeps the id
# the platform list carries (dosbox-staging).
core_name = module_id
if core_name.startswith("lr-"):
core_name = core_name[3:]
core_name = core_name.replace("-", "_")
core_name = core_name[3:].replace("-", "_")
for platform in PLATFORM_FLAGS:
if _is_available(flags, platform):
platform_cores[platform].append(core_name)
platform_cores[platform].add(core_name)
print(f" parsed {len(filenames)} scriptmodules", file=sys.stderr)
@@ -171,7 +177,7 @@ class Scraper(BaseTargetScraper):
def main() -> None:
parser = argparse.ArgumentParser(
description="Scrape RetroPie libretro core targets from scriptmodules"
description="Scrape RetroPie package targets from scriptmodules"
)
parser.add_argument("--dry-run", action="store_true", help="Show target summary")
parser.add_argument("--output", "-o", help="Output YAML file")
+33 -4
View File
@@ -319,6 +319,15 @@ def pack_overrides(
return overrides
def _strongest(claims: list[Claim]) -> Claim:
"""The profile claim with the strongest proof; the first emulator breaks ties.
yaps2 pins its GameIndex.yaml by sha1 while armsx2 and lrps2 only match a
path: alphabetical order served armsx2's copy to all three.
"""
return max(claims, key=lambda claim: (_claim_rank(claim), -claims.index(claim)))
def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Decision:
"""Decide a contested destination for the pack being built.
@@ -333,7 +342,7 @@ def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Dec
addressee decides; the loss is reported rather than absorbed, because the
cause is an upstream declaration that needs fixing at its source.
"""
profile = conflict.profile_claims[0]
profile = _strongest(conflict.profile_claims)
if addressee == "emulator":
return Decision(conflict, profile, ADDRESSEE)
if not nativemode.reads_file_contents(mode):
@@ -341,15 +350,35 @@ def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Dec
return Decision(conflict, conflict.platform_claim, FRONTEND_CHECKS_CONTENT)
def _pinned_elsewhere(claims: list[Claim], winner: str | None) -> set[str]:
"""Emulators that pin content the served file does not carry."""
return {
c.emulator for c in claims
if c.emulator and c.local_path != winner and resolution_is_hash_exact(c.status)
}
def format_decision(decision: Decision) -> str:
"""One line naming the contested slot, the winner and the ground for it."""
conflict = decision.conflict
if decision.serves_both:
return (
f"{conflict.destination}: serve {decision.winner.local_path} "
f"({', '.join(conflict.emulators) or 'profile'}); the frontend only "
winner = decision.winner.local_path
# A claim that only matched a path is answered by whatever file sits
# there; only one that pins content can be left wanting.
pinned_elsewhere = _pinned_elsewhere(conflict.profile_claims, winner)
served = sorted({
c.emulator for c in conflict.profile_claims if c.emulator
} - pinned_elsewhere)
others = sorted(pinned_elsewhere)
line = (
f"{conflict.destination}: serve {winner} "
f"({', '.join(served) or 'profile'}); the frontend only "
"checks the path, so both are satisfied"
)
if others:
# One path holds one file: an emulator pinning another is not served.
line += f"; {', '.join(others)} expected another file"
return line
if decision.reason == ADDRESSEE:
return (
f"{conflict.destination}: serve {decision.winner.local_path}, "
+243 -203
View File
@@ -8,7 +8,7 @@ from __future__ import annotations
import sys
from common import _norm_system_id, resolve_platform_cores
from common import _norm_system_id, resolve_platform_cores, runs_standalone
from validation import filter_files_by_mode
@@ -22,28 +22,6 @@ def _serialize_source_ref(sr: object) -> str:
return str(sr)
def _determine_core_mode(
emu_name: str,
profile: dict,
cores_config: str | list | None,
standalone_set: set[str] | None,
) -> str:
"""Determine effective mode (libretro/standalone) for a resolved core."""
if cores_config == "all_libretro":
return "libretro"
if standalone_set is not None:
profile_names = {emu_name} | {str(c) for c in profile.get("cores", [])}
if profile_names & standalone_set:
return "standalone"
return "libretro"
ptype = profile.get("type", "libretro")
if "standalone" in ptype and "libretro" in ptype:
return "both"
if "standalone" in ptype:
return "standalone"
return "libretro"
def _enrich_hashes(entry: dict, db: dict) -> None:
"""Fill missing sibling hashes from the database, ground-truth preserving.
@@ -119,134 +97,166 @@ def _enrich_hashes(entry: dict, db: dict) -> None:
entry["size"] = record["size"]
_FILLED_FIELDS = (
"size",
"min_size",
"max_size",
"path",
"validation",
"description",
"category",
"hle_fallback",
"note",
"aliases",
"contents",
"region",
)
_COPIED_FIELDS = (
"sha1",
"md5",
"sha256",
"crc32",
"size",
"path",
"description",
"hle_fallback",
"category",
"note",
"validation",
"min_size",
"max_size",
"aliases",
"contents",
"priority",
"region",
)
_HASH_FIELDS = ("sha1", "md5", "sha256", "crc32")
def _merge_file_into_system(
system: dict,
file_entry: dict,
emu_name: str,
db: dict | None,
) -> None:
"""Merge a file entry into a system's file list, deduplicating by name."""
"""Merge a file entry into a system's file list, deduplicating by name.
A name is one file across cores, whatever path each core gives it. A
core that declares the same name under two paths declares two files:
Dolphin's three IPL.bin differ only by GC/USA, GC/EUR, GC/JAP. Under
one path or none, its declarations are revisions of one file.
"""
files = system.setdefault("files", [])
existing = _same_file(files, file_entry, emu_name)
if existing is None:
files.append(_new_truth_entry(file_entry, emu_name, db))
else:
_fold_into(existing, file_entry, emu_name)
def _same_file(files: list[dict], file_entry: dict, emu_name: str) -> dict | None:
"""The entry already standing for this file, if any."""
name_lower = file_entry["name"].lower()
path = str(file_entry.get("path") or "").casefold()
existing = None
for f in files:
if f["name"].lower() == name_lower:
existing = f
break
def other_path(f: dict) -> str:
return str(f.get("path") or "").casefold()
if existing is not None:
existing["_cores"] = existing.get("_cores", set()) | {emu_name}
sr = file_entry.get("source_ref")
if sr is not None:
sr_key = _serialize_source_ref(sr)
existing["_source_refs"] = existing.get("_source_refs", set()) | {sr_key}
else:
existing.setdefault("_source_refs", set())
if file_entry.get("required") and not existing.get("required"):
existing["required"] = True
for h in ("sha1", "md5", "sha256", "crc32"):
theirs = file_entry.get(h, "")
ours = existing.get(h, "")
# Skip empty strings
if not theirs or theirs == "":
continue
if not ours or ours == "":
existing[h] = theirs
continue
# Normalize to sets for multi-hash comparison
t_list = theirs if isinstance(theirs, list) else [theirs]
o_list = ours if isinstance(ours, list) else [ours]
t_set = {str(v).lower() for v in t_list}
o_set = {str(v).lower() for v in o_list}
if not t_set & o_set:
print(
f"WARNING: hash conflict for {file_entry['name']} "
f"({h}: {ours} vs {theirs}, core {emu_name})",
file=sys.stderr,
)
# Merge non-hash data fields if existing lacks them.
# A core that creates an entry without size/path/validation may be
# enriched by a sibling core that has those fields.
for field in (
"size",
"min_size",
"max_size",
"path",
"validation",
"description",
"category",
"hle_fallback",
"note",
"aliases",
"contents",
"region",
):
if file_entry.get(field) is not None and existing.get(field) is None:
existing[field] = file_entry[field]
# Search order is a fact of the code, so it travels with the entry.
# The best rank any core gives it is kept and the disagreement is
# recorded beside it, the way slot.py already does: the rank says
# how early to read the file, the conflict says the order cannot
# decide which single file to keep.
theirs = file_entry.get("priority")
if theirs is not None:
ours = existing.get("priority")
if ours is None:
existing["priority"] = theirs
else:
if ours != theirs:
existing["priority_conflict"] = True
existing["priority"] = min(ours, theirs)
same_name = [f for f in files if f["name"].lower() == name_lower]
return (
next((f for f in same_name if path and other_path(f) == path), None)
or next((f for f in same_name if emu_name not in f.get("_cores", ())), None)
# Revisions accepted under one name and no path fill one slot.
or next((f for f in same_name if not path or not other_path(f)), None)
)
def _fold_into(existing: dict, file_entry: dict, emu_name: str) -> None:
"""Add one core's declaration to the entry that already stands for it."""
existing["_cores"] = existing.get("_cores", set()) | {emu_name}
sr = file_entry.get("source_ref")
if sr is not None:
sr_key = _serialize_source_ref(sr)
existing["_source_refs"] = existing.get("_source_refs", set()) | {sr_key}
else:
existing.setdefault("_source_refs", set())
if file_entry.get("required"):
existing["required"] = True
# Which cores need it: `required` above is the union, true when
# any core needs it, and a package of one core must not read it.
existing["_required_by"] = existing.get("_required_by", set()) | {emu_name}
_merge_hashes(existing, file_entry, emu_name)
# Merge non-hash data fields if existing lacks them.
# A core that creates an entry without size/path/validation may be
# enriched by a sibling core that has those fields.
for field in _FILLED_FIELDS:
if file_entry.get(field) is not None and existing.get(field) is None:
existing[field] = file_entry[field]
_merge_priority(existing, file_entry.get("priority"))
def _merge_hashes(existing: dict, file_entry: dict, emu_name: str) -> None:
for h in _HASH_FIELDS:
theirs = file_entry.get(h, "")
ours = existing.get(h, "")
if not theirs:
continue
if not ours:
existing[h] = theirs
continue
# Normalize to sets for multi-hash comparison
t_list = theirs if isinstance(theirs, list) else [theirs]
o_list = ours if isinstance(ours, list) else [ours]
if not {str(v).lower() for v in t_list} & {str(v).lower() for v in o_list}:
print(
f"WARNING: hash conflict for {file_entry['name']} "
f"({h}: {ours} vs {theirs}, core {emu_name})",
file=sys.stderr,
)
def _merge_priority(existing: dict, theirs: int | None) -> None:
"""Search order is a fact of the code, so it travels with the entry.
The best rank any core gives it is kept and the disagreement is recorded
beside it, the way slot.py already does: the rank says how early to read
the file, the conflict says the order cannot decide which single file to
keep.
"""
if theirs is None:
return
ours = existing.get("priority")
if ours is None:
existing["priority"] = theirs
return
if ours != theirs:
existing["priority_conflict"] = True
existing["priority"] = min(ours, theirs)
def _new_truth_entry(file_entry: dict, emu_name: str, db: dict | None) -> dict:
entry: dict = {"name": file_entry["name"]}
if file_entry.get("required") is not None:
entry["required"] = file_entry["required"]
for field in (
"sha1",
"md5",
"sha256",
"crc32",
"size",
"path",
"description",
"hle_fallback",
"category",
"note",
"validation",
"min_size",
"max_size",
"aliases",
"contents",
"priority",
"region",
):
for field in _COPIED_FIELDS:
val = file_entry.get(field)
if val is not None:
entry[field] = val
# Strip empty string hashes (profile says "" when hash is unknown)
for h in ("sha1", "md5", "sha256", "crc32"):
for h in _HASH_FIELDS:
if entry.get(h) == "":
del entry[h]
# Normalize CRC32: strip 0x prefix, lowercase
crc = entry.get("crc32")
if isinstance(crc, str) and crc.startswith("0x"):
entry["crc32"] = crc[2:].lower()
elif isinstance(crc, str) and crc != crc.lower():
entry["crc32"] = crc.lower()
if isinstance(crc, str):
entry["crc32"] = crc.removeprefix("0x").lower()
entry["_cores"] = {emu_name}
entry["_required_by"] = {emu_name} if file_entry.get("required") else set()
sr = file_entry.get("source_ref")
if sr is not None:
sr_key = _serialize_source_ref(sr)
entry["_source_refs"] = {sr_key}
else:
entry["_source_refs"] = set()
entry["_source_refs"] = {_serialize_source_ref(sr)} if sr is not None else set()
if db:
_enrich_hashes(entry, db)
files.append(entry)
return entry
def _has_exploitable_data(entry: dict) -> bool:
@@ -288,13 +298,10 @@ def generate_platform_truth(
Returns a dict with platform metadata, systems, and per-file details
including which cores reference each file.
"""
cores_config = config.get("cores")
# Resolve standalone set for mode determination
standalone_set: set[str] | None = None
standalone_cores = config.get("standalone_cores")
if isinstance(standalone_cores, list):
standalone_set = {str(c) for c in standalone_cores}
# The layout rule verify and the pack builder apply: truth guessed its
# own from the profile type where the platform names no standalone
# emulator, and kept files the pack never carries.
standalone_set = {str(c) for c in config.get("standalone_cores") or []}
resolved = resolve_platform_cores(config, profiles, target_cores)
@@ -347,14 +354,10 @@ def generate_platform_truth(
continue
cores_profiled.add(emu_name)
mode = _determine_core_mode(emu_name, profile, cores_config, standalone_set)
raw_files = profile.get("files", [])
if mode == "both":
filtered = raw_files
else:
filtered = filter_files_by_mode(
raw_files, standalone=(mode == "standalone")
)
filtered = filter_files_by_mode(
profile.get("files", []),
standalone=runs_standalone(emu_name, profile, standalone_set),
)
for fe in filtered:
profile_sid = fe.get("system", "")
@@ -416,6 +419,7 @@ def generate_platform_truth(
for sys_id, sys_data in systems.items():
for fe in sys_data.get("files", []):
fe["_cores"] = sorted(fe.get("_cores", set()))
fe["_required_by"] = sorted(fe.get("_required_by", set()))
fe["_source_refs"] = sorted(fe.get("_source_refs", set()))
# Add per-system coverage
cov = system_cores.get(sys_id, {})
@@ -441,7 +445,7 @@ def generate_platform_truth(
def _match_renames(
unmatched_truth: list[dict], unmatched_scraped: dict
) -> tuple[set[str], set[str]]:
) -> tuple[set[int], set]:
"""Pair files a platform renamed with the truth entry they came from.
A platform is free to call a file whatever it likes -- Batocera ships
@@ -450,51 +454,103 @@ def _match_renames(
is one file under two names, and counting it as both missing and extra
would report a gap that does not exist.
Returns the truth names and scraped keys that pair up.
Returns the positions in unmatched_truth and the scraped keys that pair
up: a name does not identify a truth entry, two can share it.
"""
# Hash-based fallback: detect platform renames (e.g. Batocera ROM → ROM1)
# If an unmatched scraped file shares a hash with an unmatched truth file,
# it's the same file under a different name: a platform rename, not a gap.
rename_matched_truth: set[str] = set()
rename_matched_scraped: set[str] = set()
rename_matched_truth: set[int] = set()
rename_matched_scraped: set = set()
if unmatched_truth and unmatched_scraped:
# Build hash → truth file index for unmatched truth files
truth_hash_index: dict[str, dict] = {}
for fe in unmatched_truth:
truth_hash_index: dict[str, int] = {}
for position, fe in enumerate(unmatched_truth):
for h in ("sha1", "md5", "crc32"):
val = fe.get(h)
if val and isinstance(val, str):
truth_hash_index[val.lower()] = fe
truth_hash_index[val.lower()] = position
for s_key, s_entry in unmatched_scraped.items():
for h in ("sha1", "md5", "crc32"):
s_val = s_entry.get(h)
if not s_val or not isinstance(s_val, str):
continue
t_entry = truth_hash_index.get(s_val.lower())
if t_entry is not None:
position = truth_hash_index.get(s_val.lower())
if position is not None:
# Rename detected. Count as matched
rename_matched_truth.add(t_entry["name"].lower())
rename_matched_truth.add(position)
rename_matched_scraped.add(s_key)
break
return rename_matched_truth, rename_matched_scraped
def _diff_system(truth_sys: dict, scraped_sys: dict) -> dict:
"""Compare files between truth and scraped for a single system."""
# Build truth index: name.lower() -> entry, alias.lower() -> entry
truth_index: dict[str, dict] = {}
for fe in truth_sys.get("files", []):
truth_index[fe["name"].lower()] = fe
for alias in fe.get("aliases", []):
truth_index[alias.lower()] = fe
def _hash_set(entry: dict) -> set[str]:
values: set[str] = set()
for h in ("sha1", "md5", "crc32"):
value = entry.get(h) or []
values.update(str(v).lower() for v in (value if isinstance(value, list) else [value]))
return values
# Build scraped index: name.lower() -> entry
scraped_index: dict[str, dict] = {}
for fe in scraped_sys.get("files", []):
scraped_index[fe["name"].lower()] = fe
def _path_tail(value: object) -> str:
return str(value or "").replace("\\", "/").casefold()
def _pair_rank(truth_entry: dict, scraped_entry: dict) -> tuple[bool, bool, bool, bool]:
"""How well a same-named truth entry describes a scraped one.
Exact path suffix, then same directory, then primary name over alias,
then a shared hash.
"""
destination = _path_tail(scraped_entry.get("destination"))
t_path = _path_tail(truth_entry.get("path"))
return (
bool(t_path) and destination.endswith(t_path),
"/" in t_path and t_path.rsplit("/", 1)[0] == destination.rpartition("/")[0],
truth_entry["name"].lower() == scraped_entry["name"].lower(),
bool(_hash_set(scraped_entry) & _hash_set(truth_entry)),
)
def _hash_mismatch(t_entry: dict, s_entry: dict) -> dict | None:
"""The first hash both sides declare without a value in common."""
for h in ("sha1", "md5", "crc32"):
t_hash = t_entry.get(h, "")
s_hash = s_entry.get(h, "")
if not t_hash or not s_hash:
continue
# Normalize to list for multi-hash support
t_list = t_hash if isinstance(t_hash, list) else [t_hash]
s_list = s_hash if isinstance(s_hash, list) else [s_hash]
if not {v.lower() for v in t_list} & {v.lower() for v in s_list}:
return {
"name": s_entry["name"],
"hash_type": h,
f"truth_{h}": t_hash,
f"scraped_{h}": s_hash,
"truth_cores": list(t_entry.get("_cores", [])),
}
return None
def _diff_system(truth_sys: dict, scraped_sys: dict) -> dict:
"""Compare files between truth and scraped for a single system.
A name can stand for several files of one system (Dolphin's IPL.bin
under GC/USA, GC/EUR and GC/JAP), so each scraped entry is paired with
the same-named truth entry at its destination first, and an entry
never answers for two.
"""
truth_files = truth_sys.get("files", [])
truth_index: dict[str, list[int]] = {}
for position, fe in enumerate(truth_files):
for key in [fe["name"], *fe.get("aliases", [])]:
truth_index.setdefault(key.lower(), []).append(position)
scraped_files = scraped_sys.get("files", [])
missing: list[dict] = []
hash_mismatch: list[dict] = []
@@ -502,39 +558,25 @@ def _diff_system(truth_sys: dict, scraped_sys: dict) -> dict:
extra_phantom: list[dict] = []
extra_unprofiled: list[dict] = []
matched_truth_names: set[str] = set()
# Compare scraped files against truth
for s_key, s_entry in scraped_index.items():
t_entry = truth_index.get(s_key)
if t_entry is None:
matched: set[int] = set()
unmatched_scraped: dict[int, dict] = {}
for s_position, s_entry in enumerate(scraped_files):
candidates = [
p for p in truth_index.get(s_entry["name"].lower(), []) if p not in matched
]
if not candidates:
if s_entry["name"].lower() not in truth_index:
unmatched_scraped[s_position] = s_entry
continue
matched_truth_names.add(t_entry["name"].lower())
# The first best candidate wins, as max() keeps the first maximum.
ranked = [(_pair_rank(truth_files[p], s_entry), p) for p in candidates]
t_position = max(ranked, key=lambda pair: pair[0])[1]
matched.add(t_position)
t_entry = truth_files[t_position]
# Hash comparison
for h in ("sha1", "md5", "crc32"):
t_hash = t_entry.get(h, "")
s_hash = s_entry.get(h, "")
if not t_hash or not s_hash:
continue
# Normalize to list for multi-hash support
t_list = t_hash if isinstance(t_hash, list) else [t_hash]
s_list = s_hash if isinstance(s_hash, list) else [s_hash]
t_set = {v.lower() for v in t_list}
s_set = {v.lower() for v in s_list}
if not t_set & s_set:
hash_mismatch.append(
{
"name": s_entry["name"],
"hash_type": h,
f"truth_{h}": t_hash,
f"scraped_{h}": s_hash,
"truth_cores": list(t_entry.get("_cores", [])),
}
)
break
# Required mismatch
mismatch = _hash_mismatch(t_entry, s_entry)
if mismatch:
hash_mismatch.append(mismatch)
t_req = t_entry.get("required")
s_req = s_entry.get("required")
if t_req is not None and s_req is not None and t_req != s_req:
@@ -548,23 +590,16 @@ def _diff_system(truth_sys: dict, scraped_sys: dict) -> dict:
# Collect unmatched files from both sides
unmatched_truth = [
fe
for fe in truth_sys.get("files", [])
if fe["name"].lower() not in matched_truth_names
fe for position, fe in enumerate(truth_files) if position not in matched
]
unmatched_scraped = {
s_key: s_entry
for s_key, s_entry in scraped_index.items()
if s_key not in truth_index
}
rename_matched_truth, rename_matched_scraped = _match_renames(
unmatched_truth, unmatched_scraped
)
# Truth files not matched (by name, alias, or hash) -> missing
for fe in unmatched_truth:
if fe["name"].lower() not in rename_matched_truth:
for position, fe in enumerate(unmatched_truth):
if position not in rename_matched_truth:
missing.append(
{
"name": fe["name"],
@@ -576,9 +611,14 @@ def _diff_system(truth_sys: dict, scraped_sys: dict) -> dict:
# Scraped files not in truth -> extra
coverage = truth_sys.get("_coverage", {})
has_unprofiled = bool(coverage.get("cores_unprofiled"))
# An archive declared once per ROM it holds (zipped_file) is still one
# file.
seen_extra: set[tuple[str, str]] = set()
for s_key, s_entry in unmatched_scraped.items():
if s_key in rename_matched_scraped:
key = (s_entry["name"].lower(), _path_tail(s_entry.get("destination")))
if s_key in rename_matched_scraped or key in seen_extra:
continue
seen_extra.add(key)
entry = {"name": s_entry["name"]}
if has_unprofiled:
extra_unprofiled.append(entry)
+22 -13
View File
@@ -22,10 +22,15 @@ import json
import os
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from common import compute_hashes, list_registered_platforms, load_database, yaml_load
from common import (
compute_hashes,
list_registered_platforms,
load_database,
load_platform_config,
parse_md5_list,
)
try:
import yaml
@@ -109,20 +114,24 @@ def load_platform_hashes(platforms_dir: str) -> dict:
if not os.path.isdir(platforms_dir) or yaml is None:
return known
# Read the way verify reads them: inheritance and shared groups resolved,
# and a Recalbox md5 field split into the revisions it lists. The raw
# file kept "a,b,c" as one value, so a PR adding the seventh accepted
# falcon.img was told its hash differs.
for name in list_registered_platforms(platforms_dir, include_archived=True):
f = Path(platforms_dir) / f"{name}.yml"
with open(f) as fh:
try:
config = yaml_load(fh) or {}
except yaml.YAMLError:
continue
try:
config = load_platform_config(name, platforms_dir)
except (OSError, yaml.YAMLError) as exc:
print(f"WARNING: {name}: {exc}", file=sys.stderr)
continue
for sys_id, system in config.get("systems", {}).items():
for system in config.get("systems", {}).values():
for file_entry in system.get("files", []):
if "sha1" in file_entry:
known["sha1"].add(file_entry["sha1"])
if "md5" in file_entry:
known["md5"].add(file_entry["md5"])
sha1 = file_entry.get("sha1")
for value in sha1 if isinstance(sha1, list) else [sha1]:
if value:
known["sha1"].add(str(value).lower())
known["md5"].update(parse_md5_list(file_entry.get("md5")))
if "name" in file_entry:
known["names"].add(file_entry["name"])
+33
View File
@@ -165,6 +165,38 @@ def _unreachable_citations(path: Path, document: object) -> list[str]:
return out
def _unknown_data_dir_refs() -> list[str]:
"""data_directories refs the registry does not define.
A ref nobody registers is never fetched: yaps2 named yaps2-resources and
every run printed "not cached" while refresh_data_dirs had nothing to
refresh.
"""
registry_path = ROOT / "platforms" / "_data_dirs.yml"
with registry_path.open(encoding="utf-8") as handle:
registered = set((yaml_load(handle) or {}).get("data_directories", {}))
out: list[str] = []
for directory in (ROOT / "emulators", ROOT / "platforms"):
for path in sorted(directory.glob("*.yml")):
if path.name.startswith("_"):
continue
with path.open(encoding="utf-8") as handle:
document = yaml_load(handle) or {}
systems = document.get("systems")
holders = [document, *(systems.values() if isinstance(systems, dict) else [])]
for holder in holders:
if not isinstance(holder, dict):
continue
for entry in holder.get("data_directories") or []:
ref = entry.get("ref") if isinstance(entry, dict) else None
if ref and ref not in registered:
out.append(
f"{path.relative_to(ROOT)}: data directory {ref!r} "
f"is not in platforms/_data_dirs.yml"
)
return out
def _semantic_envelope_checks(path: Path, document: dict) -> list[str]:
if document.get("count") != len(document.get("items", [])):
return [f"{path.relative_to(ROOT)}: count does not equal len(items)"]
@@ -240,6 +272,7 @@ def main() -> int:
ROOT / "platforms", "platform.schema.json", skip_private=True
)
)
errors.extend(_unknown_data_dir_refs())
if not args.source_only:
database_path = ROOT / "database.json"
+99 -1
View File
@@ -9,8 +9,10 @@ from __future__ import annotations
import os
from common import compute_hashes
from common import compute_hashes, size_fits
from hashing import parse_md5_list
from nativemode import digest_algorithm, hash_mismatch_excludes_file
from ziptools import check_inside_zip
# Validation types that require console-specific cryptographic keys.
# verify.py cannot reproduce these -size checks still apply if combined.
@@ -392,3 +394,99 @@ def find_validated_variant(
if check_file_validation(path, fname, validation_index, bios_dir) is None:
return path
return None
def agnostic_substitute(
file_entry: dict, sys_id: str, db: dict, platform_profiles: dict[str, dict]
) -> tuple[str, str] | None:
"""(path, directory) of a held file a filename-agnostic core can boot.
A core with `bios_mode: agnostic` (PCSX2 picks any image in its BIOS
folder) is served by any image of the right size, renamed to what the
platform declares. That only satisfies a frontend that checks existence:
a digest frontend compares the bytes. Only the platform's own cores are
asked, and the pack and verify read this one answer. An entry that
declares a content hash names one file: no substitute stands for it.
"""
if any(file_entry.get(h) for h in ("sha1", "md5", "sha256", "crc32")):
return None
by_name = db.get("indexes", {}).get("by_name", {})
files_db = db.get("files", {})
for profile in platform_profiles.values():
if profile.get("bios_mode") != "agnostic":
continue
if sys_id not in set(profile.get("systems", [])):
continue
for entry in profile.get("files", []):
for sha1 in by_name.get(entry.get("name", ""), []):
path = files_db.get(sha1, {}).get("path", "")
if not path:
continue
prefix = path.rsplit("/", 1)[0] + "/"
for candidate in files_db.values():
held = candidate.get("path", "")
if (
held.startswith(prefix)
and size_fits(entry, candidate.get("size", 0))
and os.path.exists(held)
):
return held, prefix
break
return None
def frontend_digest_matches(file_entry: dict, local_path: str, algorithm: str) -> bool:
"""Whether the frontend's own digest accepts the file.
resolve_local_file calls a file `hash_mismatch` when any declared hash
disagrees, but a frontend compares one digest. RomM's list carries md5,
sha1 and crc32: a mistyped crc32 next to the right md5 must not withhold
a file RomM accepts. Same comparison as verify_entry_md5/_sha1.
"""
if algorithm == "md5":
declared = parse_md5_list(file_entry.get("md5"))
else:
value = file_entry.get(algorithm)
values = value if isinstance(value, list) else [value]
declared = [str(v).strip().lower() for v in values if v]
if not declared or not local_path:
return False
return compute_hashes(local_path)[algorithm].lower() in declared
def inner_rom_check(file_entry: dict, local_path: str) -> str:
"""How an archive answers an entry that pins a ROM inside it.
Batocera, RetroBat and ROCKNIX hash a member of the ZIP, never the ZIP:
the resolver hands back the archive as a mismatch and this decides it.
Returns check_inside_zip's answer for the first accepted MD5 that
matches, else for the last one tried. The pack and the install manifest
both read it, so a file one of them ships is a file the other lists.
"""
declared = [m.strip() for m in file_entry.get("md5", "").split(",") if m.strip()]
result = "not_in_zip"
for candidate in declared or [""]:
result = check_inside_zip(local_path, file_entry["zipped_file"], candidate)
if result == "ok":
break
return result
def settle_mismatch(
file_entry: dict, local_path: str | None, status: str, verification_mode: str
) -> str:
"""Upgrade a hash mismatch the frontend itself would accept.
Batocera pins the md5 of a ROM inside the archive (zipped_file), and a
frontend that hashes one digest accepts a file whose other declared
hashes disagree.
"""
if status != "hash_mismatch" or not local_path:
return status
if file_entry.get("zipped_file"):
return "zip_exact" if inner_rom_check(file_entry, local_path) == "ok" else status
if hash_mismatch_excludes_file(verification_mode) and frontend_digest_matches(
file_entry, local_path, digest_algorithm(verification_mode)
):
return "frontend_digest_exact"
return status
+122 -52
View File
@@ -34,6 +34,9 @@ import slots
sys.path.insert(0, os.path.dirname(__file__))
from common import (
PROFILE_IDENTITY_FIELDS,
list_available_targets,
list_platform_system_ids,
build_target_cores_cache,
build_zip_contents_index,
check_inside_zip,
@@ -66,6 +69,7 @@ from nativemode import (
reads_file_contents,
)
from validation import (
agnostic_substitute,
_build_validation_index,
_parse_validation,
build_ground_truth,
@@ -399,6 +403,15 @@ def _candidate_verdict(
return "keep"
def _identity_of(entry: dict) -> dict:
"""The fields that tell a profile's file apart from a same-named one."""
return {
field: entry.get(field)
for field in PROFILE_IDENTITY_FIELDS
if entry.get(field) not in (None, "", [])
}
def find_undeclared_files(
config: dict,
emulators_dir: str,
@@ -464,9 +477,15 @@ def find_undeclared_files(
profile.get("files", []), db, is_standalone
):
fname = f.get("name", "")
effective_path = (
f.get("standalone_path") if is_standalone else f.get("path")
) or fname
# One destination rule for the dedup key and for the entry: a
# standalone build without standalone_path falls back on path.
# The key used to skip that fallback, so clk's Acorn/basic.rom and
# Electron/basic.rom both keyed as "basic.rom" under Batocera's
# standalone clk and the second was dropped.
if is_standalone:
dest = f.get("standalone_path") or f.get("path") or fname
else:
dest = f.get("path") or fname
raw_regions = f.get("region") or []
region_key = tuple(
str(value) for value in (
@@ -478,7 +497,7 @@ def find_undeclared_files(
seen_key = (
fname,
f.get("archive"),
effective_path,
dest,
f.get("system"),
f.get("variant_group"),
region_key,
@@ -539,12 +558,6 @@ def find_undeclared_files(
entry["required"] = True
continue
# Determine destination path based on mode
if is_standalone:
dest = f.get("standalone_path") or f.get("path") or fname
else:
dest = f.get("path") or fname
# Resolution: storage flag, then name, then path basename
storage = f.get("storage", "")
if storage in ("release", "large_file"):
@@ -602,6 +615,7 @@ def find_undeclared_files(
"checks": sorted(checks) if checks else [],
"source_ref": f.get("source_ref"),
"expected": _build_expected(f, checks),
**_identity_of(f),
"sha1": f.get("sha1"),
"md5": f.get("md5"),
}
@@ -701,6 +715,36 @@ def find_exclusion_notes(
# Platform verification
def _resolve_for_platform(
file_entry: dict,
sys_id: str,
db: dict,
zip_contents: dict,
data_dir_registry: dict | None,
slot_overrides: dict[str, str],
mode: str,
platform_profiles: dict,
) -> tuple[str | None, str]:
"""Resolve one platform entry the way the pack builder does.
A slot the arbitration decided wins over the resolver; a frontend that
never reads the bytes takes the filename-free core's own image.
"""
override = slot_overrides.get(
sanitize_pack_path(file_entry.get("destination", file_entry.get("name", "")))
)
if override:
return override, "slot_arbitrated"
local_path, status = resolve_local_file(
file_entry, db, zip_contents, data_dir_registry=data_dir_registry
)
if local_path is None and not reads_file_contents(mode):
found = agnostic_substitute(file_entry, sys_id, db, platform_profiles)
if found:
return found[0], "agnostic_fallback"
return local_path, status
def verify_platform(
config: dict,
db: dict,
@@ -744,19 +788,20 @@ def verify_platform(
if emu_profiles is not None
else load_emulator_profiles(emulators_dir)
)
hle_index: dict[str, bool] = {}
for profile in profiles.values():
for f in profile.get("files", []):
if f.get("hle_fallback"):
hle_index[f.get("name", "")] = True
# Ground truth comes from the emulators the platform runs. A standalone
# profile that loads a same-named file of its own (ZEsarUX's 48K
# cpc6128.rom against cap32's 32K one) has nothing to say about a
# RetroArch pack.
# RetroArch pack, nor does its HLE: dosbox-x's FONT.ROM fallback turned a
# missing required Batocera BIOS into INFO.
plat_cores = resolve_platform_cores(config, profiles)
validation_index = _build_validation_index(
{name: profiles[name] for name in plat_cores}
)
platform_profiles = {name: profiles[name] for name in plat_cores}
hle_index = {
f.get("name", ""): True
for profile in platform_profiles.values()
for f in profile.get("files", [])
if f.get("hle_fallback")
}
validation_index = _build_validation_index(platform_profiles)
# Filter systems by target
if not target_cores:
@@ -808,19 +853,10 @@ def verify_platform(
in region_drops
):
continue
local_path, resolve_status = resolve_local_file(
file_entry,
db,
zip_contents,
data_dir_registry=data_dir_registry,
local_path, resolve_status = _resolve_for_platform(
file_entry, sys_id, db, zip_contents, data_dir_registry,
slot_overrides, mode, platform_profiles,
)
override = slot_overrides.get(
sanitize_pack_path(
file_entry.get("destination", file_entry.get("name", ""))
)
)
if override:
local_path, resolve_status = override, "slot_arbitrated"
if not reads_file_contents(mode):
result = verify_entry_existence(
file_entry,
@@ -1656,6 +1692,58 @@ def print_emulator_result(result: dict, verbose: bool = False) -> None:
print(f" {gt_cov['platform_only']} without declared validation")
def _refuse_listing_narrowings(
args: argparse.Namespace, parser: argparse.ArgumentParser
) -> None:
"""A listing mode reads --platform at most; anything else is refused.
Printing past a narrowing flag lets the user believe it applied
(generate_pack applies the same table).
"""
listing = next(
(flag for flag, on in (
("--list-emulators", args.list_emulators),
("--list-systems", args.list_systems),
("--list-targets", args.list_targets),
) if on),
None,
)
if not listing:
return
reads_platform = listing != "--list-emulators"
for flag, on in (
("--platform", args.platform and not reads_platform),
("--all", args.all),
("--emulator", args.emulator),
("--system", args.system),
("--region", getattr(args, "region", None)),
("--target", getattr(args, "target", None)),
):
if on:
parser.error(f"{flag} is incompatible with {listing}")
def _run_listing(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
if args.list_emulators:
list_emulator_profiles(args.emulators_dir)
elif args.list_systems and args.platform:
list_platform_system_ids(args.platform, args.platforms_dir)
elif args.list_systems:
list_system_ids(args.emulators_dir)
else:
if not args.platform:
parser.error("--list-targets requires --platform")
targets = list_available_targets(args.platform, args.platforms_dir)
if not targets:
print(f"No targets configured for platform '{args.platform}'")
return
for t in targets:
aliases = f" (aliases: {', '.join(t['aliases'])})" if t["aliases"] else ""
print(
f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}"
)
def main():
parser = argparse.ArgumentParser(description="Platform-native BIOS verification")
parser.add_argument("--platform", "-p", help="Platform name")
@@ -1704,27 +1792,9 @@ def main():
except ValueError as exc:
parser.error(str(exc))
if args.list_emulators:
list_emulator_profiles(args.emulators_dir)
return
if args.list_systems:
list_system_ids(args.emulators_dir)
return
if args.list_targets:
if not args.platform:
parser.error("--list-targets requires --platform")
from common import list_available_targets
targets = list_available_targets(args.platform, args.platforms_dir)
if not targets:
print(f"No targets configured for platform '{args.platform}'")
return
for t in targets:
aliases = f" (aliases: {', '.join(t['aliases'])})" if t["aliases"] else ""
print(
f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}"
)
_refuse_listing_narrowings(args, parser)
if args.list_emulators or args.list_systems or args.list_targets:
_run_listing(args, parser)
return
# Mutual exclusion
+60
View File
@@ -0,0 +1,60 @@
"""Pack, verify and manifest answer the same way for a filename-agnostic core.
The pack alone renamed any PS2 image into a missing slot, even on an md5
platform whose frontend then rejects it, and counted it OK; verify called
the slot missing and the manifest omitted it.
"""
from __future__ import annotations
import re
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from validation import agnostic_substitute # noqa: E402
class AgnosticSubstitute(unittest.TestCase):
def test_only_the_platform_cores_answer(self):
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
held = Path(tmp) / "ps2" / "scph39001.bin"
held.parent.mkdir()
held.write_bytes(b"x" * 16)
db = {
"files": {"a": {"path": str(held), "name": "scph39001.bin", "size": 16}},
"indexes": {"by_name": {"scph39001.bin": ["a"]}},
}
agnostic = {
"bios_mode": "agnostic",
"systems": ["sony-playstation-2"],
"files": [{"name": "scph39001.bin", "min_size": 8}],
}
self.assertEqual(
agnostic_substitute({}, "sony-playstation-2", db, {"pcsx2": agnostic}),
(str(held), f"{held.parent}/"),
)
self.assertIsNone(agnostic_substitute({}, "sony-playstation-2", db, {}))
self.assertIsNone(agnostic_substitute({}, "nintendo-nes", db, {"pcsx2": agnostic}))
self.assertIsNone(
agnostic_substitute({"md5": "0" * 32}, "sony-playstation-2", db, {"pcsx2": agnostic})
)
def test_every_reader_calls_it_behind_the_existence_gate(self):
builder = (REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8")
verify = (REPO_ROOT / "scripts" / "verify.py").read_text(encoding="utf-8")
self.assertEqual(builder.count("agnostic_substitute(file_entry, sys_id"), 2)
self.assertEqual(verify.count("agnostic_substitute(file_entry, sys_id"), 1)
for source in (builder, verify):
for call in re.finditer(r"agnostic_substitute\(file_entry, sys_id", source):
window = source[max(0, call.start() - 300):call.start()]
self.assertIn("reads_file_contents", window)
self.assertNotIn('_emu_prof.get("bios_mode")', builder)
if __name__ == "__main__":
unittest.main()
+68
View File
@@ -20,6 +20,13 @@ from unittest import mock
import yaml
try:
import jsonschema # noqa: F401
except ImportError:
HAS_JSONSCHEMA = False
else:
HAS_JSONSCHEMA = True
ROOT = Path(__file__).resolve().parent.parent
TMP_ROOT = ROOT / "tmp" / "tests"
TMP_ROOT.mkdir(parents=True, exist_ok=True)
@@ -229,6 +236,7 @@ class UnreachableCitationRegressions(unittest.TestCase):
check is offline and runs on every push.
"""
@unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema")
def _hits(self, text: str) -> bool:
import validate_schemas
@@ -255,6 +263,7 @@ class UnreachableCitationRegressions(unittest.TestCase):
with self.subTest(citation=good):
self.assertFalse(self._hits(good))
@unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema")
def test_the_corpus_carries_none(self):
import validate_schemas
@@ -473,6 +482,39 @@ class PipelineRegressions(unittest.TestCase):
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(pipeline.check_consistency(verify, pack))
def test_nothing_parsed_is_not_consistent(self):
"""A changed output format must not pass with no platform compared."""
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(pipeline.check_consistency("RetroArch — 2/2 OK", ""))
self.assertFalse(pipeline.check_consistency("", ""))
def test_pack_without_verify_result_is_a_failure(self):
verify = "RetroArch: 2/2 OK"
pack = "\n".join(
[
"Generating pack for RetroArch [source=full]...",
" a.zip: 2 files packed (2 baseline + 0 from cores), 2/2 files OK",
"Generating pack for Batocera [source=full]...",
" b.zip: 2 files packed (2 baseline + 0 from cores), 2/2 files OK",
]
)
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(pipeline.check_consistency(verify, pack))
def test_only_the_full_variant_feeds_the_comparison(self):
"""--all-variants printed six packs under one label; the last won."""
output = "\n".join([
"Generating pack for RetroArch [source=full]...",
" a.zip: 5 files packed (5 baseline + 0 from cores), 535/535 files OK",
"Generating pack for RetroArch [source=platform, required]...",
" b.zip: 5 files packed (5 baseline + 0 from cores), 530/530 files OK",
])
self.assertEqual(pipeline.parse_pack_counts(output), {"RetroArch": (535, 535)})
def test_slot_step_reaches_the_summary(self):
source = Path(pipeline.__file__).read_text(encoding="utf-8")
self.assertIn('results["slots"] = ok', source)
def test_every_refresh_failure_reaches_pipeline_exit_status(self):
for failed_label in (
"2/8 refresh data directories",
@@ -494,6 +536,30 @@ class PipelineRegressions(unittest.TestCase):
self.assertEqual(raised.exception.code, 1)
def test_a_targeted_export_reads_a_full_model(self):
"""--target wrote truth/<target>/ only; the export read truth/."""
commands: list[list[str]] = []
def fake_run(command, _label):
commands.append([str(part) for part in command])
return True, "RetroArch: 1/1 OK\n"
argv = ["pipeline.py", "--offline", "--skip-packs", "--skip-docs",
"--with-export", "--target", "switch"]
with mock.patch.object(sys, "argv", argv), mock.patch.object(
pipeline, "run", side_effect=fake_run
), contextlib.redirect_stdout(io.StringIO()), contextlib.suppress(SystemExit):
pipeline.main()
truth_runs = [c for c in commands if "scripts/generate_truth.py" in c]
export = next(c for c in commands if "scripts/export_native.py" in c)
read_dir = export[export.index("--truth-dir") + 1]
full_models = [
c for c in truth_runs
if "--target" not in c and c[c.index("--output-dir") + 1] == read_dir
]
self.assertTrue(full_models, truth_runs)
class ResolverRegressions(unittest.TestCase):
def _database(self, entries: dict[str, Path], suffix: str | None = None) -> dict:
files = {}
@@ -1006,6 +1072,7 @@ class InstallerBoundaryRegressions(unittest.TestCase):
with self.assertRaisesRegex(ValueError, "unsafe"):
install._validate_manifest(manifest, "retroarch")
@unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema")
def test_target_schema_accepts_the_null_the_generator_emits(self):
"""The schema and generate_target_manifests must agree on null.
@@ -1023,6 +1090,7 @@ class InstallerBoundaryRegressions(unittest.TestCase):
document = {"windows": None, "switch": ["a5200"]}
self.assertEqual(list(validator.iter_errors(document)), [])
@unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema")
def test_pack_manifests_are_read_from_inside_the_archive(self):
"""generate_pack writes manifest.json into the ZIP, not beside it.
+55
View File
@@ -0,0 +1,55 @@
"""Companions travel with the BIOS image they belong to.
PCSX2 opens <bios>.rom1 or <stem>.rom1, and nvm and mec the same way. Its
profile named them rom1.bin, ROM2.BIN and EROM.BIN, names the code never
opens; ROM2.BIN found a misfiled Galaksija ROM inside the PS2 folder, and a
4 MB size bound then swept GameIndex.yaml and cheat archives to the root of
the Batocera BIOS folder.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from packextras import _companion_extensions, _companions # noqa: E402
class Companions(unittest.TestCase):
PROFILE = {"files": [
{"name": "ps2-0230a-20080220.bin"},
{"name": "ps2-0230a-20080220.rom1"},
{"name": "ps2-0230a-20080220.nvm"},
{"name": "GameIndex.yaml"},
]}
def test_extensions_come_from_entries_named_after_the_bios(self):
self.assertEqual(
_companion_extensions(self.PROFILE, "ps2-0230a-20080220.bin"), {"rom1", "nvm"}
)
def test_only_files_named_after_the_image_follow_it(self):
files_db = {
"a": {"path": "bios/PS2/SCPH-70004.ROM1", "name": "SCPH-70004.ROM1"},
"b": {"path": "bios/PS2/scph70004.bin.nvm", "name": "scph70004.bin.nvm"},
"c": {"path": "bios/PS2/GameIndex.yaml", "name": "GameIndex.yaml"},
"d": {"path": "bios/Other/SCPH-70004.ROM1", "name": "SCPH-70004.ROM1"},
}
found = _companions("bios/PS2/SCPH-70004.BIN", "SCPH-70004.BIN", {"rom1", "nvm"}, files_db)
self.assertEqual([sha for sha, _ in found], ["a"])
def test_pcsx2_profiles_name_no_free_standing_companion(self):
for name in ("pcsx2", "pcsx2-legacy"):
profile = yaml.safe_load((REPO_ROOT / "emulators" / f"{name}.yml").read_text())
names = {str(f.get("name", "")).lower() for f in profile.get("files", [])}
with self.subTest(profile=name):
self.assertFalse({"rom1.bin", "rom2.bin", "erom.bin"} & names)
if __name__ == "__main__":
unittest.main()
+33
View File
@@ -0,0 +1,33 @@
"""A platform the installer targets on Windows or macOS packs case-insensitively.
RetroBat shipped quasi88/N88.ROM (the md5 it pins) beside quasi88/n88.rom;
extracted on NTFS the second overwrote the first and RetroBat's own check
went red. The flag lived in retrobat.yml and vanished at the next scrape.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import load_platform_config # noqa: E402
class CaseInsensitiveWhereTheFilesystemIs(unittest.TestCase):
def test_windows_and_macos_platforms_carry_the_flag(self):
registry = yaml.safe_load((REPO_ROOT / "platforms" / "_registry.yml").read_text())
for name, data in registry["platforms"].items():
systems = {str(d.get("os")) for d in (data.get("install") or {}).get("detect", [])}
if systems & {"windows", "darwin"}:
with self.subTest(platform=name):
config = load_platform_config(name, str(REPO_ROOT / "platforms"))
self.assertTrue(config.get("case_insensitive_fs"))
if __name__ == "__main__":
unittest.main()
+30
View File
@@ -0,0 +1,30 @@
"""The case-insensitive name index answers for the database it was asked about."""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import _casefold_name_index # noqa: E402
class CasefoldIndexFollowsItsSource(unittest.TestCase):
def test_a_new_dict_gets_its_own_index(self):
first = {"BIOS.ROM": ["a"]}
self.assertEqual(_casefold_name_index(first)["bios.rom"], ["a"])
second = {"Bios.Rom": ["b"]}
self.assertEqual(_casefold_name_index(second)["bios.rom"], ["b"])
def test_a_grown_dict_is_reindexed(self):
index = {"A.BIN": ["a"]}
_casefold_name_index(index)
index["B.BIN"] = ["b"]
self.assertIn("b.bin", _casefold_name_index(index))
if __name__ == "__main__":
unittest.main()
+2 -2
View File
@@ -149,8 +149,8 @@ class CoreResolutionTests(unittest.TestCase):
def test_index_maps_key_and_every_core_alias(self):
index = cf.profile_name_index(self.PROFILES)
self.assertEqual(index["mednafen_psx"], "beetle_psx")
self.assertEqual(index["beetle_psx"], "beetle_psx")
self.assertEqual(index["mednafen_psx"], {"beetle_psx"})
self.assertEqual(index["beetle_psx"], {"beetle_psx"})
def test_unresolved_honours_remove_cores_per_target(self):
index = cf.profile_name_index(self.PROFILES)
+54
View File
@@ -0,0 +1,54 @@
"""otp.bin decrypts with the standard library alone.
The build promises stdlib + pyyaml. AES needed cryptography, pycryptodome
or an openssl binary, and verify.py raised RuntimeError on otp.bin for a
contributor with none of them.
"""
from __future__ import annotations
import ast
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import crypto_verify # noqa: E402
STDLIB_OR_LOCAL = {"__future__", "hashlib", "struct", "collections", "pathlib", "sect233r1"}
class StdlibAes(unittest.TestCase):
def test_fips_197_vector(self):
key = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
cipher = bytes.fromhex("69c4e0d86a7b0430d8cdb78070b4c55a")
plain = crypto_verify._decrypt_block(cipher, crypto_verify._expand_key(key))
self.assertEqual(plain.hex(), "00112233445566778899aabbccddeeff")
def test_cbc_chains_blocks(self):
# NIST SP 800-38A F.2.2, CBC-AES128 decrypt, first two blocks.
key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")
iv = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
cipher = bytes.fromhex(
"7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"
)
self.assertEqual(
crypto_verify._aes_128_cbc_decrypt(cipher, key, iv).hex(),
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51",
)
def test_no_third_party_import(self):
tree = ast.parse((REPO_ROOT / "scripts" / "crypto_verify.py").read_text())
modules = {
(node.module if isinstance(node, ast.ImportFrom) else alias.name).split(".")[0]
for node in ast.walk(tree)
if isinstance(node, (ast.Import, ast.ImportFrom))
for alias in node.names
}
self.assertLessEqual(modules, STDLIB_OR_LOCAL)
if __name__ == "__main__":
unittest.main()
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more