mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
Compare commits
94
Commits
27610c1d33
...
8fe961a1ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe961a1ea | ||
|
|
ce239d32c1 | ||
|
|
13995047fc | ||
|
|
31e86b6db5 | ||
|
|
da5e95cd0f | ||
|
|
3dce7b5f09 | ||
|
|
97ccf9027b | ||
|
|
1caf5c4b5e | ||
|
|
acfd80c928 | ||
|
|
4d8ea05494 | ||
|
|
3df423e84f | ||
|
|
8a9924f1d8 | ||
|
|
52fbea5faa | ||
|
|
421a7afb3f | ||
|
|
0b7acf1e7f | ||
|
|
c4dab556e7 | ||
|
|
0390f2d19b | ||
|
|
e6afd06191 | ||
|
|
03929ddaf0 | ||
|
|
2b71e47510 | ||
|
|
cd90f46937 | ||
|
|
d02672c17b | ||
|
|
c6ba139493 | ||
|
|
f0bab9766c | ||
|
|
c55e3af5ac | ||
|
|
1c34503f4e | ||
|
|
72834837a5 | ||
|
|
ae0bbb9d3c | ||
|
|
e4cc978614 | ||
|
|
f51c3a3fc7 | ||
|
|
c62a34b1b2 | ||
|
|
ed18e61580 | ||
|
|
2cdb98239e | ||
|
|
623b0a6d47 | ||
|
|
82583fe17a | ||
|
|
85501c41e8 | ||
|
|
7273f5c8b9 | ||
|
|
e723c8c1b9 | ||
|
|
4db5866bbe | ||
|
|
dc3a35bdc8 | ||
|
|
0c881820c7 | ||
|
|
d839ce0a1c | ||
|
|
1b885632a6 | ||
|
|
ad9f834f55 | ||
|
|
49e0f83ce9 | ||
|
|
2b6dbb5e92 | ||
|
|
3d31a887cc | ||
|
|
e3f773dd82 | ||
|
|
c1b3e1fc33 | ||
|
|
b3c7e76adf | ||
|
|
b2208d889e | ||
|
|
af357a2931 | ||
|
|
1b40211366 | ||
|
|
a484754c76 | ||
|
|
46268bea47 | ||
|
|
0fbe99a14a | ||
|
|
16f4c071fe | ||
|
|
a8415c33c3 | ||
|
|
7e054fbcaf | ||
|
|
5784d45531 | ||
|
|
c526180351 | ||
|
|
36f7ee0b2b | ||
|
|
cf7d9ecb89 | ||
|
|
ccc51b861b | ||
|
|
3ae9729143 | ||
|
|
26ce416163 | ||
|
|
bd3bcc36ea | ||
|
|
f631174c85 | ||
|
|
22648e8a5f | ||
|
|
0de03b5e4c | ||
|
|
3bd19b9e97 | ||
|
|
ccf1a1573a | ||
|
|
ca86236c12 | ||
|
|
994836938c | ||
|
|
71cbbd9b21 | ||
|
|
65c0f5ddc1 | ||
|
|
ef16ed2af2 | ||
|
|
da0c843bdd | ||
|
|
e32df743d5 | ||
|
|
72d3654245 | ||
|
|
bdcf134877 | ||
|
|
aec1755c61 | ||
|
|
53ee1bfd0e | ||
|
|
d2d88cf6b8 | ||
|
|
fbdee43e6d | ||
|
|
284f1aa6b7 | ||
|
|
10f3379751 | ||
|
|
76387d1962 | ||
|
|
9465a250cc | ||
|
|
3bc80b2e06 | ||
|
|
dcdfe5936e | ||
|
|
ed47f437fc | ||
|
|
15abbec663 | ||
|
|
077dcda775 |
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+10
-8
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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/
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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."
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 --
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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: >-
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+33
-44
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+361
-431
File diff suppressed because it is too large.
Load diff
+252
-213
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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:
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"])
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in new issue
Block a user