83 Commits
Author SHA1 Message Date
Abdessamad Derraz 913ba09600 chore: hoist test imports to module level 2026-10-06 13:59:50 +02:00
Abdessamad Derraz 38655d1d23 refactor: split helpers out of grown functions 2026-10-06 13:59:50 +02:00
Abdessamad Derraz 9af89cb120 refactor: share the gap scope rule 2026-10-06 13:59:50 +02:00
Abdessamad Derraz fbc92c99cf chore: regenerate database, manifests and readme 2026-10-06 13:06:27 +02:00
Abdessamad Derraz 49b3cffa58 fix: cite lrps2 bios loaders at the pin 2026-10-06 13:01:09 +02:00
Abdessamad Derraz af4f980bab fix: cite pokemini's bios reload at the pin 2026-10-06 13:00:09 +02:00
Abdessamad Derraz 3582101af4 fix: cite the msx-emu fdc mapper at the pin 2026-10-06 13:00:00 +02:00
Abdessamad Derraz a4f3f016f8 fix: cite the casloopy rom block at the pin 2026-10-06 12:59:43 +02:00
Abdessamad Derraz a252e2d158 fix: a cited range past the file end is gone 2026-10-06 12:59:23 +02:00
Abdessamad Derraz 067d962210 fix: cite dolphin's NAND import where it is 2026-10-06 12:59:23 +02:00
Abdessamad Derraz 0d81f19831 fix: pin kronos on the branch the buildbot builds 2026-10-06 12:56:44 +02:00
Abdessamad Derraz 52d128bcf5 fix: name a file's readers by resolution 2026-10-06 12:55:41 +02:00
Abdessamad Derraz 0b46880604 fix: show unsourceable entries apart on the site 2026-10-06 12:53:10 +02:00
Abdessamad Derraz 0f293b6c14 fix: slot emulator entries where the pack puts them 2026-10-06 12:52:24 +02:00
Abdessamad Derraz 9ca14bb77e fix: keep save and content files out of the truth 2026-10-06 12:48:58 +02:00
Abdessamad Derraz 443639c33d fix: cross-reference the build a platform runs 2026-10-06 12:47:50 +02:00
Abdessamad Derraz 0ea8840f14 fix: default each platform to the folder it reads 2026-10-06 12:46:35 +02:00
Abdessamad Derraz 53c5504330 fix: read extract folders from the registry 2026-10-06 12:44:32 +02:00
Abdessamad Derraz 7164e8a125 fix: an untagged bios serves every region 2026-10-06 12:43:09 +02:00
Abdessamad Derraz cea6e516a4 fix: unmark files of standalone-only profiles 2026-10-06 12:39:34 +02:00
Abdessamad Derraz 8d2df0b651 fix: lock the pack directory while splitting 2026-10-06 12:34:31 +02:00
Abdessamad Derraz b20c38d1c1 fix: hold dist/ for the whole pipeline run 2026-10-06 12:33:59 +02:00
Abdessamad Derraz 45ede873b3 fix: refetch a large file the release replaced 2026-10-06 12:30:51 +02:00
Abdessamad Derraz 14e4dd919c fix: check targeted packs the way --all builds 2026-10-06 12:29:40 +02:00
Abdessamad Derraz dc5cdb8d70 docs: split parts are named, not renamed 2026-10-06 12:29:10 +02:00
Abdessamad Derraz 87ef905c44 fix: name split parts directly, version their dir 2026-10-06 12:29:03 +02:00
Abdessamad Derraz e3173b1fc5 fix: drop the --include-extras flag nothing read 2026-10-06 12:27:21 +02:00
Abdessamad Derraz 514becd784 fix: run every scraper contract class directly 2026-10-06 12:27:11 +02:00
Abdessamad Derraz 0cc6ab5990 fix: refuse --include-archived without --all 2026-10-06 12:26:47 +02:00
Abdessamad Derraz 4609102772 fix: fail the fileless check on real errors 2026-10-06 12:25:18 +02:00
Abdessamad Derraz 92594b1751 fix: read profile tree status from its own repo 2026-10-06 12:24:31 +02:00
Abdessamad Derraz 2499a0eabc fix: refuse a libretro scrape without core info 2026-10-06 12:23:48 +02:00
Abdessamad Derraz 91e113a7d3 fix: refuse an emudeck scrape with no cores 2026-10-06 12:23:48 +02:00
Abdessamad Derraz e723f346f9 fix: refuse batocera targets with unread boards 2026-10-06 12:22:51 +02:00
Abdessamad Derraz 139bb99088 fix: stop a scrape whose release tag is unread 2026-10-06 12:22:51 +02:00
Abdessamad Derraz e35aa9a323 fix: quote system.dat names as the original does 2026-10-06 12:16:23 +02:00
Abdessamad Derraz be2c325b53 fix: leave bizhawk's declared sha1 to bizhawk 2026-10-06 12:15:50 +02:00
Abdessamad Derraz b39b187243 fix: refuse a one-md5 addition of several accepted 2026-10-06 12:15:49 +02:00
Abdessamad Derraz 6380a92e6c fix: never pair files whose contents or dirs differ 2026-10-06 12:15:40 +02:00
Abdessamad Derraz 22f018995e feat: collect emucorex and dojo bundled resources 2026-10-06 12:13:27 +02:00
Abdessamad Derraz 5d5170dc12 fix: prefer the owner's copy among equal path tails 2026-10-06 12:13:27 +02:00
Abdessamad Derraz 64ed737ec6 fix: rank the owner's copy above shared tree names 2026-10-06 12:09:51 +02:00
Abdessamad Derraz 87addda6e1 fix: look inside every zipped_file archive 2026-10-06 12:07:34 +02:00
Abdessamad Derraz b8a7aa74ff fix: report contradicted hashes in existence verify 2026-10-06 12:05:56 +02:00
Abdessamad Derraz d4ae1a76e8 fix: state no size for several accepted sizes 2026-10-06 12:04:24 +02:00
Abdessamad Derraz eb8c7e5267 refactor: split this round's fixes into small helpers 2026-10-06 11:16:06 +02:00
Abdessamad Derraz 6748982d94 fix: count variant choices made through validated_choice 2026-10-06 10:23:20 +02:00
Abdessamad Derraz e335511b72 chore: regenerate database, manifests and readme 2026-10-06 09:59:51 +02:00
Abdessamad Derraz 3088db7088 fix: count core archives among the core files 2026-10-06 09:54:33 +02:00
Abdessamad Derraz 204637cfeb fix: pack undistributed core extras in a split part 2026-10-06 09:53:09 +02:00
Abdessamad Derraz 519c02646a fix: compute coverage from the profiles the site reads 2026-10-06 09:48:15 +02:00
Abdessamad Derraz 72e47becb4 refactor: write one contributing text for both pages 2026-10-06 09:47:09 +02:00
Abdessamad Derraz 4a1a9dd861 docs: name jsonschema where the build needs it 2026-10-06 09:46:10 +02:00
Abdessamad Derraz d610aad990 fix: require the jsonschema format checkers 2026-10-06 09:45:25 +02:00
Abdessamad Derraz a0af1b141e fix: diff every declaration of a repeated destination 2026-10-06 09:43:58 +02:00
Abdessamad Derraz 4d5340c5f4 fix: count site cores with the builder's resolution 2026-10-06 09:43:38 +02:00
Abdessamad Derraz 4923873a7f fix: refuse an unknown mame or fbneo version 2026-10-06 09:42:33 +02:00
Abdessamad Derraz ced7588d17 fix: count mister corrections only where it writes 2026-10-06 09:41:53 +02:00
Abdessamad Derraz 44cf638e06 fix: count bizhawk corrections only where it writes 2026-10-06 09:41:28 +02:00
Abdessamad Derraz a9b33484ed fix: write emudeck corrections, count only what lands 2026-10-06 09:41:04 +02:00
Abdessamad Derraz 39d02b3b18 fix: never let a romm addition replace their entry 2026-10-06 09:40:09 +02:00
Abdessamad Derraz e5420a9dba fix: keep platform hashes the truth does not contradict 2026-10-06 09:39:07 +02:00
Abdessamad Derraz 9604e933ed fix: recale prose onto the code the pins carry 2026-10-06 09:38:27 +02:00
Abdessamad Derraz dcc12e62b7 fix: tag regions only where the code picks by area 2026-10-06 09:37:44 +02:00
Abdessamad Derraz 25e8eeb37b fix: read gaps by normalized system, sha256, file names 2026-10-06 09:32:32 +02:00
Abdessamad Derraz 5d5befc002 fix: keep refs for another version out of the pin check 2026-10-06 09:28:51 +02:00
Abdessamad Derraz c4e16e583e fix: realign stale prose before a pin moves 2026-10-06 09:27:33 +02:00
Abdessamad Derraz b0f2c3bb88 fix: leave mode-keyed pins to a hand edit 2026-10-06 09:25:23 +02:00
Abdessamad Derraz d89b193780 fix: move a ref only on a hash found elsewhere 2026-10-06 09:24:49 +02:00
Abdessamad Derraz afdd38826c fix: refuse --refresh-data where nothing refreshes 2026-10-06 09:24:24 +02:00
Abdessamad Derraz 7ddb849c89 fix: hold the data dir lock in emulator packs 2026-10-06 09:20:42 +02:00
Abdessamad Derraz 8e3f3fe057 fix: build system packs beside, not over, emulator packs 2026-10-06 09:19:18 +02:00
Abdessamad Derraz af030fdd6d fix: reduce emulator packs by region after required-only 2026-10-06 09:13:15 +02:00
Abdessamad Derraz f159d35f18 fix: read the data registry from --platforms-dir 2026-10-06 09:10:57 +02:00
Abdessamad Derraz dce43a1281 fix: keep display names out of pack file paths 2026-10-06 09:09:02 +02:00
Abdessamad Derraz d7e7ec13ae fix: install the variant the pack ships 2026-10-06 09:08:33 +02:00
Abdessamad Derraz 098b2f0b70 fix: restore release assets through a scratch file 2026-10-06 08:58:40 +02:00
Abdessamad Derraz 6c9a41327b fix: refuse unread verify flags, read platforms dir 2026-10-06 08:58:21 +02:00
Abdessamad Derraz fb3c577521 fix: anchor the slot mode test on the override lookup 2026-10-06 08:53:58 +02:00
Abdessamad Derraz 821cbf4717 fix: count exclusions from the full pack only 2026-10-06 08:52:51 +02:00
Abdessamad Derraz 9c84b7990a fix: give np2kai scsi.rom a read ceiling, not a size 2026-10-06 08:52:26 +02:00
Abdessamad Derraz 4f8fbdfabb fix: read numbered master keys in the switch test 2026-10-06 08:51:16 +02:00
Abdessamad Derraz eccd0b0a28 fix: file the older switch keys as variants 2026-10-06 08:50:38 +02:00
139 changed files with 95294 additions and 1816 deletions

No files matched your search

+1 -1
View File
@@ -84,7 +84,7 @@ jobs:
# mkdocs-material >= 9.7.5 caps mkdocs < 2; the upper bound keeps a major # mkdocs-material >= 9.7.5 caps mkdocs < 2; the upper bound keeps a major
# theme release from changing the site without a deliberate bump here. # theme release from changing the site without a deliberate bump here.
- run: pip install pyyaml jsonschema==4.26.0 "mkdocs-material>=9.7.5,<10" "pymdown-extensions>=10.14" - run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0" "mkdocs-material>=9.7.5,<10" "pymdown-extensions>=10.14"
- name: Validate data contracts - name: Validate data contracts
run: python scripts/validate_schemas.py run: python scripts/validate_schemas.py
+2 -2
View File
@@ -112,7 +112,7 @@ jobs:
python-version: "3.12" python-version: "3.12"
- name: Install dependencies - name: Install dependencies
run: pip install pyyaml jsonschema==4.26.0 run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0"
- name: Validate platform configs and emulator profiles - name: Validate platform configs and emulator profiles
run: python scripts/validate_schemas.py --source-only run: python scripts/validate_schemas.py --source-only
@@ -127,7 +127,7 @@ jobs:
python-version: "3.12" python-version: "3.12"
- name: Install dependencies - name: Install dependencies
run: pip install pyyaml jsonschema==4.26.0 run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0"
# The exporter fidelity cases compare what we hand back against the # The exporter fidelity cases compare what we hand back against the
# file each platform publishes, so they need those files. They skip # file each platform publishes, so they need those files. They skip
+17 -17
View File
@@ -55,9 +55,9 @@ Every release ships `SHA256SUMS.txt` and a detached signature of it, checkable a
| RetroBat | 1,466 | 4.4 GB | `bios/` | [Download](../../releases/latest) | | RetroBat | 1,466 | 4.4 GB | `bios/` | [Download](../../releases/latest) |
| RetroDECK | 3,519 | 6.2 GB | `~/retrodeck/` | [Download](../../releases/latest) | | RetroDECK | 3,519 | 6.2 GB | `~/retrodeck/` | [Download](../../releases/latest) |
| RetroPie * | 4,525 | 5.5 GB | `~/RetroPie/BIOS/` | [Download](../../releases/latest) | | RetroPie * | 4,525 | 5.5 GB | `~/RetroPie/BIOS/` | [Download](../../releases/latest) |
| RomM | 617 | 1.3 GB | `bios/{platform_slug}/` | [Download](../../releases/latest) | | RomM | 617 | 1.3 GB | `library/bios/` | [Download](../../releases/latest) |
The RetroDECK pack already contains its own `bios/` folder, so it extracts into `~/retrodeck/` rather than into the BIOS folder. The RetroDECK pack already carries its own `bios/` folder, so it extracts into `~/retrodeck/` rather than into the BIOS folder.
\* Archived: the configuration is kept and packs are still built, but upstream is no longer scraped on a schedule. \* Archived: the configuration is kept and packs are still built, but upstream is no longer scraped on a schedule.
@@ -72,9 +72,9 @@ Each file is checked the way your platform checks it. Most compare a checksum, t
- **12 platforms** supported with platform-specific verification - **12 platforms** supported with platform-specific verification
- **506 emulators** profiled from source (RetroArch cores + standalone) - **506 emulators** profiled from source (RetroArch cores + standalone)
- **546 systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...) - **546 systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **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 - **18,997 files in the collection**, each with its SHA1, MD5, SHA256, CRC32 and Adler-32 fingerprints: 5,261 system files, 2,808 arcade ROM sets, 10,928 game and engine data files. That is every platform and emulator together, so no pack holds them all: each carries what its own emulators load, counted in the download table
- **562 of 5,252 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index - **562 of 5,261 system files** matched to dump-preservation catalogs (No-Intro, Redump, TOSEC); arcade sets and engine data fall outside what those catalogs index
- **16672 MB** total collection size - **16677 MB** total collection size
## Supported systems ## Supported systems
@@ -86,18 +86,18 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
| Platform | On its BIOS list | Files its emulators load | Checked by | | Platform | On its BIOS list | Files its emulators load | Checked by |
|----------|-----------------:|-------------------------:|------------| |----------|-----------------:|-------------------------:|------------|
| Batocera | 353/353 | 12,307/12,738 | MD5 hash | | Batocera | 353/353 | 12,306/12,737 | MD5 hash |
| BizHawk | 118/118 | 649/650 | SHA1 hash | | BizHawk | 118/118 | 806/807 | SHA1 hash |
| EmuDeck | 168/168 | 381/383 | MD5 hash | | EmuDeck | 168/168 | 431/433 | MD5 hash |
| Lakka | 527/527 | 5,263/5,293 | file presence | | Lakka | 527/527 | 5,264/5,294 | file presence |
| MiSTer FPGA | 81/81 | - | MD5 hash | | MiSTer FPGA | 81/81 | - | MD5 hash |
| ROCKNIX | 38/38 | 5,702/5,732 | MD5 hash | | ROCKNIX | 38/38 | 5,698/5,728 | MD5 hash |
| Recalbox | 351/351 | 2,728/2,736 | MD5 hash | | Recalbox | 351/351 | 2,730/2,738 | MD5 hash |
| RetroArch | 527/527 | 5,263/5,293 | file presence | | RetroArch | 527/527 | 5,264/5,294 | file presence |
| RetroBat | 348/348 | 11,749/12,179 | MD5 hash | | RetroBat | 348/348 | 11,889/12,319 | MD5 hash |
| RetroDECK | 2,008/2,008 | 5,296/5,346 | MD5 hash | | RetroDECK | 2,008/2,008 | 5,407/5,457 | MD5 hash |
| RetroPie * | 527/527 | 9,182/9,324 | file presence | | RetroPie * | 527/527 | 9,183/9,325 | file presence |
| RomM | 381/381 | 624/630 | MD5 hash | | RomM | 381/381 | 751/757 | MD5 hash |
Each fraction is what the pack has over what is needed, counting required and optional files alike since both ship. The first column is the BIOS list the platform publishes. The second counts files its emulators load that this list never mentions, found by reading their source code, and it is routinely several times larger. A short fraction means files are still missing, and they are named in the [gap analysis](https://abdess.github.io/retrobios/gaps/). Each fraction is what the pack has over what is needed, counting required and optional files alike since both ship. The first column is the BIOS list the platform publishes. The second counts files its emulators load that this list never mentions, found by reading their source code, and it is routinely several times larger. A short fraction means files are still missing, and they are named in the [gap analysis](https://abdess.github.io/retrobios/gaps/).
That second number is a floor, not a ceiling: an emulator that accepts any file handed to it names none in its code, so nothing there can be counted. That second number is a floor, not a ceiling: an emulator that accepts any file handed to it names none in its code, so nothing there can be counted.
@@ -180,4 +180,4 @@ The scripts and tooling are released under the [MIT License](LICENSE).
The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed. The 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). 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-06T03:31:49Z* *Auto-generated on 2026-10-06T11:05:39Z*
File diff suppressed because it is too large. Load diff
Binary file not shown.
@@ -0,0 +1,498 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#ifndef FXAA_HLSL
#define FXAA_HLSL 0
#endif
#ifndef FXAA_GLSL_130
#define FXAA_GLSL_130 0
#endif
#ifndef FXAA_GLSL_VK
#define FXAA_GLSL_VK 0
#endif
#define UHQ_FXAA 1 //High Quality Fast Approximate Anti Aliasing. Adapted for GS from Timothy Lottes FXAA 3.11.
#define FxaaSubpixMax 0.0 //[0.00 to 1.00] Amount of subpixel aliasing removal. 0.00: Edge only antialiasing (no blurring)
#define FxaaEarlyExit 1 //[0 or 1] Use Fxaa early exit pathing. When disabled, the entire scene is antialiased(FSAA). 0 is off, 1 is on.
/*------------------------------------------------------------------------------
[GLOBALS|FUNCTIONS]
------------------------------------------------------------------------------*/
#if (FXAA_GLSL_130 == 1)
in vec2 PSin_t;
layout(location = 0) out vec4 SV_Target0;
layout(binding = 0) uniform sampler2D TextureSampler;
#elif (FXAA_GLSL_VK == 1)
layout(location = 0) in vec2 PSin_t;
layout(location = 0) out vec4 SV_Target0;
layout(set = 0, binding = 0) uniform sampler2D TextureSampler;
#elif (FXAA_HLSL == 1)
Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);
struct VS_INPUT
{
float4 p : POSITION;
float2 t : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 p : SV_Position;
float2 t : TEXCOORD0;
};
struct PS_OUTPUT
{
float4 c : SV_Target0;
};
#elif defined(__METAL_VERSION__)
static constexpr sampler MAIN_SAMPLER(coord::normalized, address::clamp_to_edge, filter::linear);
#endif
/*------------------------------------------------------------------------------
[FXAA CODE SECTION]
------------------------------------------------------------------------------*/
#if (FXAA_HLSL == 1)
struct FxaaTex { SamplerState smpl; Texture2D tex; };
#define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0)
#define FxaaTexOff(t, p, o, r) t.tex.SampleLevel(t.smpl, p, 0.0, o)
#define FxaaDiscard clip(-1)
#define FxaaSat(x) saturate(x)
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
#define int2 ivec2
#define float2 vec2
#define float3 vec3
#define float4 vec4
#define FxaaDiscard discard
#define FxaaSat(x) clamp(x, 0.0, 1.0)
#define FxaaTex sampler2D
#define FxaaTexTop(t, p) textureLod(t, p, 0.0)
#define FxaaTexOff(t, p, o, r) textureLodOffset(t, p, 0.0, o)
#elif defined(__METAL_VERSION__)
#define FxaaTex texture2d<float>
#define FxaaTexTop(t, p) t.sample(MAIN_SAMPLER, p)
#define FxaaTexOff(t, p, o, r) t.sample(MAIN_SAMPLER, p, o)
#define FxaaDiscard discard_fragment()
#define FxaaSat(x) saturate(x)
#endif
#define FxaaEdgeThreshold 0.063
#define FxaaEdgeThresholdMin 0.00
#define FXAA_QUALITY_P0 1.0
#define FXAA_QUALITY_P1 1.5
#define FXAA_QUALITY_P2 2.0
#define FXAA_QUALITY_P3 2.0
#define FXAA_QUALITY_P4 2.0
#define FXAA_QUALITY_P5 2.0
#define FXAA_QUALITY_P6 2.0
#define FXAA_QUALITY_P7 2.0
#define FXAA_QUALITY_P8 2.0
#define FXAA_QUALITY_P9 2.0
#define FXAA_QUALITY_P10 4.0
#define FXAA_QUALITY_P11 8.0
#define FXAA_QUALITY_P12 8.0
/*------------------------------------------------------------------------------
[GAMMA PREPASS CODE SECTION]
------------------------------------------------------------------------------*/
float RGBLuminance(float3 color)
{
const float3 lumCoeff = float3(0.2126729, 0.7151522, 0.0721750);
return dot(color.rgb, lumCoeff);
}
float3 RGBGammaToLinear(float3 color, float gamma)
{
color = FxaaSat(color);
color.r = (color.r <= 0.0404482362771082) ?
color.r / 12.92 : pow((color.r + 0.055) / 1.055, gamma);
color.g = (color.g <= 0.0404482362771082) ?
color.g / 12.92 : pow((color.g + 0.055) / 1.055, gamma);
color.b = (color.b <= 0.0404482362771082) ?
color.b / 12.92 : pow((color.b + 0.055) / 1.055, gamma);
return color;
}
float3 LinearToRGBGamma(float3 color, float gamma)
{
color = FxaaSat(color);
color.r = (color.r <= 0.00313066844250063) ?
color.r * 12.92 : 1.055 * pow(color.r, 1.0 / gamma) - 0.055;
color.g = (color.g <= 0.00313066844250063) ?
color.g * 12.92 : 1.055 * pow(color.g, 1.0 / gamma) - 0.055;
color.b = (color.b <= 0.00313066844250063) ?
color.b * 12.92 : 1.055 * pow(color.b, 1.0 / gamma) - 0.055;
return color;
}
float4 PreGammaPass(float4 color)
{
const float GammaConst = 2.233;
color.rgb = RGBGammaToLinear(color.rgb, GammaConst);
color.rgb = LinearToRGBGamma(color.rgb, GammaConst);
color.a = RGBLuminance(color.rgb);
return color;
}
/*------------------------------------------------------------------------------
[FXAA CODE SECTION]
------------------------------------------------------------------------------*/
float FxaaLuma(float4 rgba)
{
rgba.w = RGBLuminance(rgba.xyz);
return rgba.w;
}
float4 FxaaPixelShader(float2 pos, FxaaTex tex, float2 fxaaRcpFrame, float fxaaSubpix, float fxaaEdgeThreshold, float fxaaEdgeThresholdMin)
{
float2 posM = pos;
float4 rgbyM = FxaaTexTop(tex, posM);
rgbyM.w = RGBLuminance(rgbyM.xyz);
#define lumaM rgbyM.w
float lumaS = FxaaLuma(FxaaTexOff(tex, posM, int2( 0, 1), fxaaRcpFrame.xy));
float lumaE = FxaaLuma(FxaaTexOff(tex, posM, int2( 1, 0), fxaaRcpFrame.xy));
float lumaN = FxaaLuma(FxaaTexOff(tex, posM, int2( 0,-1), fxaaRcpFrame.xy));
float lumaW = FxaaLuma(FxaaTexOff(tex, posM, int2(-1, 0), fxaaRcpFrame.xy));
float maxSM = max(lumaS, lumaM);
float minSM = min(lumaS, lumaM);
float maxESM = max(lumaE, maxSM);
float minESM = min(lumaE, minSM);
float maxWN = max(lumaN, lumaW);
float minWN = min(lumaN, lumaW);
float rangeMax = max(maxWN, maxESM);
float rangeMin = min(minWN, minESM);
float range = rangeMax - rangeMin;
float rangeMaxScaled = rangeMax * fxaaEdgeThreshold;
float rangeMaxClamped = max(fxaaEdgeThresholdMin, rangeMaxScaled);
#if (FxaaEarlyExit == 1)
// Potential optimization, early exit.
if (range < rangeMaxClamped)
return rgbyM;
#endif
float lumaNW = FxaaLuma(FxaaTexOff(tex, posM, int2(-1,-1), fxaaRcpFrame.xy));
float lumaSE = FxaaLuma(FxaaTexOff(tex, posM, int2( 1, 1), fxaaRcpFrame.xy));
float lumaNE = FxaaLuma(FxaaTexOff(tex, posM, int2( 1,-1), fxaaRcpFrame.xy));
float lumaSW = FxaaLuma(FxaaTexOff(tex, posM, int2(-1, 1), fxaaRcpFrame.xy));
float lumaNS = lumaN + lumaS;
float lumaWE = lumaW + lumaE;
float subpixRcpRange = 1.0/range;
float subpixNSWE = lumaNS + lumaWE;
float edgeHorz1 = (-2.0 * lumaM) + lumaNS;
float edgeVert1 = (-2.0 * lumaM) + lumaWE;
float lumaNESE = lumaNE + lumaSE;
float lumaNWNE = lumaNW + lumaNE;
float edgeHorz2 = (-2.0 * lumaE) + lumaNESE;
float edgeVert2 = (-2.0 * lumaN) + lumaNWNE;
float lumaNWSW = lumaNW + lumaSW;
float lumaSWSE = lumaSW + lumaSE;
float edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2);
float edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2);
float edgeHorz3 = (-2.0 * lumaW) + lumaNWSW;
float edgeVert3 = (-2.0 * lumaS) + lumaSWSE;
float edgeHorz = abs(edgeHorz3) + edgeHorz4;
float edgeVert = abs(edgeVert3) + edgeVert4;
float subpixNWSWNESE = lumaNWSW + lumaNESE;
float lengthSign = fxaaRcpFrame.x;
bool horzSpan = edgeHorz >= edgeVert;
float subpixA = subpixNSWE * 2.0 + subpixNWSWNESE;
if(!horzSpan) lumaN = lumaW;
if(!horzSpan) lumaS = lumaE;
if(horzSpan) lengthSign = fxaaRcpFrame.y;
float subpixB = (subpixA * (1.0/12.0)) - lumaM;
float gradientN = lumaN - lumaM;
float gradientS = lumaS - lumaM;
float lumaNN = lumaN + lumaM;
float lumaSS = lumaS + lumaM;
bool pairN = abs(gradientN) >= abs(gradientS);
float gradient = max(abs(gradientN), abs(gradientS));
if(pairN) lengthSign = -lengthSign;
float subpixC = FxaaSat(abs(subpixB) * subpixRcpRange);
float2 posB;
posB.x = posM.x;
posB.y = posM.y;
float2 offNP;
offNP.x = (!horzSpan) ? 0.0 : fxaaRcpFrame.x;
offNP.y = ( horzSpan) ? 0.0 : fxaaRcpFrame.y;
if(!horzSpan) posB.x += lengthSign * 0.5;
if( horzSpan) posB.y += lengthSign * 0.5;
float2 posN;
posN.x = posB.x - offNP.x * FXAA_QUALITY_P0;
posN.y = posB.y - offNP.y * FXAA_QUALITY_P0;
float2 posP;
posP.x = posB.x + offNP.x * FXAA_QUALITY_P0;
posP.y = posB.y + offNP.y * FXAA_QUALITY_P0;
float subpixD = ((-2.0)*subpixC) + 3.0;
float lumaEndN = FxaaLuma(FxaaTexTop(tex, posN));
float subpixE = subpixC * subpixC;
float lumaEndP = FxaaLuma(FxaaTexTop(tex, posP));
if(!pairN) lumaNN = lumaSS;
float gradientScaled = gradient * 1.0/4.0;
float lumaMM = lumaM - lumaNN * 0.5;
float subpixF = subpixD * subpixE;
bool lumaMLTZero = lumaMM < 0.0;
lumaEndN -= lumaNN * 0.5;
lumaEndP -= lumaNN * 0.5;
bool doneN = abs(lumaEndN) >= gradientScaled;
bool doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P1;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P1;
bool doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P1;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P1;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P2;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P2;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P2;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P2;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P3;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P3;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P3;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P3;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P4;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P4;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P4;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P4;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P5;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P5;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P5;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P5;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P6;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P6;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P6;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P6;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P7;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P7;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P7;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P7;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P8;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P8;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P8;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P8;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P9;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P9;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P9;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P9;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P10;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P10;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P10;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P10;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P11;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P11;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P11;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P11;
if(doneNP) {
if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
doneN = abs(lumaEndN) >= gradientScaled;
doneP = abs(lumaEndP) >= gradientScaled;
if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P12;
if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P12;
doneNP = (!doneN) || (!doneP);
if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P12;
if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P12;
}}}}}}}}}}}
float dstN = posM.x - posN.x;
float dstP = posP.x - posM.x;
if(!horzSpan) dstN = posM.y - posN.y;
if(!horzSpan) dstP = posP.y - posM.y;
bool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero;
float spanLength = (dstP + dstN);
bool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero;
float spanLengthRcp = 1.0/spanLength;
bool directionN = dstN < dstP;
float dst = min(dstN, dstP);
bool goodSpan = directionN ? goodSpanN : goodSpanP;
float subpixG = subpixF * subpixF;
float pixelOffset = (dst * (-spanLengthRcp)) + 0.5;
float subpixH = subpixG * fxaaSubpix;
float pixelOffsetGood = goodSpan ? pixelOffset : 0.0;
float pixelOffsetSubpix = max(pixelOffsetGood, subpixH);
if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;
if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;
return float4(FxaaTexTop(tex, posM).xyz, lumaM);
}
#if (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
float4 FxaaPass(float4 FxaaColor, float2 uv0)
#elif (FXAA_HLSL == 1)
float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
#elif defined(__METAL_VERSION__)
float4 FxaaPass(float4 FxaaColor, float2 uv0, texture2d<float> tex)
#endif
{
#if (FXAA_HLSL == 1)
FxaaTex tex;
tex.tex = Texture;
tex.smpl = TextureSampler;
float2 PixelSize;
Texture.GetDimensions(PixelSize.x, PixelSize.y);
FxaaColor = FxaaPixelShader(uv0, tex, 1.0/PixelSize.xy, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
vec2 PixelSize = vec2(textureSize(TextureSampler, 0));
FxaaColor = FxaaPixelShader(uv0, TextureSampler, 1.0/PixelSize.xy, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#elif defined(__METAL_VERSION__)
float2 PixelSize = float2(tex.get_width(), tex.get_height());
FxaaColor = FxaaPixelShader(uv0, tex, 1.f/PixelSize, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#endif
return FxaaColor;
}
/*------------------------------------------------------------------------------
[MAIN() & COMBINE PASS CODE SECTION]
------------------------------------------------------------------------------*/
#if (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
void main()
{
vec4 color = texture(TextureSampler, PSin_t);
color = PreGammaPass(color);
color = FxaaPass(color, PSin_t);
SV_Target0 = float4(color.rgb, 1.0);
}
#elif (FXAA_HLSL == 1)
PS_OUTPUT main(VS_OUTPUT input)
{
PS_OUTPUT output;
float4 color = Texture.Sample(TextureSampler, input.t);
color = PreGammaPass(color);
color = FxaaPass(color, input.t);
output.c = float4(color.rgb, 1.0);
return output;
}
// Metal main function in in fxaa.metal
#endif
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,470 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
//#version 420 // Keep it for text editor detection
layout(std140, binding = 1) uniform cb20
{
vec2 VertexScale;
vec2 VertexOffset;
vec2 TextureScale;
vec2 TextureOffset;
vec2 PointSize;
uint MaxDepth;
float LineAA1Width;
};
#ifdef VERTEX_SHADER
#ifndef VS_EXPAND_NONE
#define VS_EXPAND_NONE 0
#define VS_EXPAND_POINT 1
#define VS_EXPAND_LINE 2
#define VS_EXPAND_SPRITE 3
#define VS_EXPAND_LINE_AA1 4
#define VS_EXPAND_TRIANGLE_AA1 5
#endif
out SHADER
{
vec4 t_float;
vec4 t_int;
#if VS_IIP != 0
vec4 c;
#else
flat vec4 c;
#endif
float inv_cov; // We use the inverse to make it simpler to interpolate.
flat uint interior; // 1 for triangle interior; 0 for edge.
} VSout;
const float exp_min32 = exp2(-32.0f);
#if VS_EXPAND == VS_EXPAND_NONE
layout(location = 0) in vec2 i_st;
layout(location = 2) in vec4 i_c;
layout(location = 3) in float i_q;
layout(location = 4) in uvec2 i_p;
layout(location = 5) in uint i_z;
layout(location = 6) in uvec2 i_uv;
layout(location = 7) in vec4 i_f;
void texture_coord()
{
vec2 uv = vec2(i_uv) - TextureOffset;
vec2 st = i_st - TextureOffset;
// Float coordinate
VSout.t_float.xy = st;
VSout.t_float.w = i_q;
// Integer coordinate => normalized
VSout.t_int.xy = uv * TextureScale;
#if VS_FST
// Integer coordinate => integral
VSout.t_int.zw = uv;
#else
// Some games uses float coordinate for post-processing effect
VSout.t_int.zw = st / TextureScale;
#endif
}
void vs_main()
{
// Clamp to max depth, gs doesn't wrap
highp uint z = min(i_z, MaxDepth);
// pos -= 0.05 (1/320 pixel) helps avoiding rounding problems (integral part of pos is usually 5 digits, 0.05 is about as low as we can go)
// example: ceil(afterseveralvertextransformations(y = 133)) => 134 => line 133 stays empty
// input granularity is 1/16 pixel, anything smaller than that won't step drawing up/left by one pixel
// example: 133.0625 (133 + 1/16) should start from line 134, ceil(133.0625 - 0.05) still above 133
gl_Position.xy = vec2(i_p) - vec2(0.05f, 0.05f);
gl_Position.xy = gl_Position.xy * VertexScale - VertexOffset;
#if HAS_CLIP_CONTROL
gl_Position.z = float(z) * exp_min32;
#else
gl_Position.z = (float(z) * exp_min32) * 2.0f - 1.0f;
#endif
gl_Position.w = 1.0f;
texture_coord();
VSout.c = i_c;
VSout.t_float.z = i_f.x; // pack for with texture
#if VS_POINT_SIZE
gl_PointSize = PointSize.x;
#endif
}
#else // VS_EXPAND
struct RawVertex
{
vec2 ST;
uint RGBA;
float Q;
uint XY;
uint Z;
uint UV;
uint FOG;
};
layout(std140, binding = 4) uniform cb22
{
uint BaseVertex;
uint BaseIndex;
uint pad_cb22_0;
uint pad_cb22_1;
};
layout(std140, binding = 2) readonly buffer VertexBuffer {
RawVertex vertex_buffer[];
};
// Warning: use std430 instead of std140 so that the ints are tightly packed.
layout(std430, binding = 3) readonly buffer IndexBuffer {
uint index_buffer[];
};
struct ProcessedVertex
{
vec4 p;
vec4 t_float;
vec4 t_int;
vec4 c;
};
uint load_index(uint _i)
{
uint i = _i + BaseIndex;
// i is even => load lower 16 bits; i odd => load upper 16 bits.
uint shift = (i & 1u) << 4u;
return (index_buffer[i >> 1u] >> shift) & 0xFFFFu;
}
ProcessedVertex load_vertex(uint index)
{
RawVertex rvtx = vertex_buffer[BaseVertex + index];
vec2 i_st = rvtx.ST;
vec4 i_c = vec4(uvec4(bitfieldExtract(rvtx.RGBA, 0, 8), bitfieldExtract(rvtx.RGBA, 8, 8),
bitfieldExtract(rvtx.RGBA, 16, 8), bitfieldExtract(rvtx.RGBA, 24, 8)));
float i_q = rvtx.Q;
uvec2 i_p = uvec2(bitfieldExtract(rvtx.XY, 0, 16), bitfieldExtract(rvtx.XY, 16, 16));
uint i_z = rvtx.Z;
uvec2 i_uv = uvec2(bitfieldExtract(rvtx.UV, 0, 16), bitfieldExtract(rvtx.UV, 16, 16));
vec4 i_f = unpackUnorm4x8(rvtx.FOG);
ProcessedVertex vtx;
uint z = min(i_z, MaxDepth);
vtx.p.xy = vec2(i_p) - vec2(0.05f, 0.05f);
vtx.p.xy = vtx.p.xy * VertexScale - VertexOffset;
#if HAS_CLIP_CONTROL
vtx.p.z = float(z) * exp_min32;
#else
vtx.p.z = (float(z) * exp_min32) * 2.0f - 1.0f;
#endif
vtx.p.w = 1.0f;
vec2 uv = vec2(i_uv) - TextureOffset;
vec2 st = i_st - TextureOffset;
vtx.t_float.xy = st;
vtx.t_float.w = i_q;
vtx.t_int.xy = uv * TextureScale;
#if VS_FST
vtx.t_int.zw = uv;
#else
vtx.t_int.zw = st / TextureScale;
#endif
vtx.c = i_c;
vtx.t_float.z = i_f.x;
return vtx;
}
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
vec2 get_xy_unscaled(vec2 xy)
{
return round(xy / VertexScale) / 16.0f;
}
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
mat2 get_xy_deltas_unscaled(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
vec2 xy0 = get_xy_unscaled(v0.p.xy);
vec2 xy1 = get_xy_unscaled(v1.p.xy);
vec2 xy2 = get_xy_unscaled(v2.p.xy);
return mat2(xy1 - xy0, xy2 - xy0);
}
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
// expand on on the side towards the outside of the triangle.
vec2 get_aa1_triangle_expand_dir(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
mat2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2);
vec2 line_delta = xy_deltas[0];
vec2 line_opposite = xy_deltas[1];
vec2 line_normal = vec2(line_delta.y, -line_delta.x);
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 1.0f) : vec2(1.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
return line_expand;
}
mat2 get_inverse(mat2 mat, float det)
{
return mat2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1.0f / det);
}
// Extrapolate triangle attributes from the first vertex along the given direction.
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
void extrapolate_aa1_triangle_edge(inout ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2, mat2 dp_mat, vec2 dp)
{
// Get texture deltas
#if VS_TME
#if VS_FST
mat2 dt = mat2(v1.t_int.zw - v0.t_int.zw, v2.t_int.zw - v0.t_int.zw);
#else
mat2 dt = mat2(v1.t_float.xy - v0.t_float.xy, v2.t_float.xy - v0.t_float.xy);
#endif
#endif
// Get color delta if interpolating
#if VS_IIP
mat2x4 dc = mat2x4(v1.c - v0.c, v2.c - v0.c);
#endif
vec2 dz = vec2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
vec2 df = vec2(v1.t_float.z - v0.t_float.z, v2.t_float.z - v0.t_float.z); // Fog deltas
vec2 dq = vec2(v1.t_float.w - v0.t_float.w, v2.t_float.w - v0.t_float.w); // Q deltas
// To prevent unstable extrapolation, do not extrapolate if the
// minimum perpendicular length of the triangle is < 2 pixels.
float dp_det = determinant(dp_mat); // Twice signed triangle area.
float len0 = length(dp_mat[0]);
float len1 = length(dp_mat[1]);
float len2 = length(dp_mat[1] - dp_mat[0]);
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
// Get the position -> barycentric weight matrix
mat2 inv_dp_mat = get_inverse(dp_mat, dp_det);
vec2 weights = min_perp_length < 2.0f ? vec2(0) : inv_dp_mat * dp;
v0.p.xy += dp * PointSize; // Extrapolate position
// Extrapolate texture coords
#if VS_TME
#if VS_FST
v0.t_int.zw += dt * weights;
v0.t_int.xy = v0.t_int.zw * TextureScale;
#else
v0.t_float.xy += dt * weights;
v0.t_int.zw = v0.t_float.xy / TextureScale;
v0.t_float.w += dot(dq, weights);
#endif
#endif
// Extrapolate and clamp color
#if VS_IIP
v0.c += dc * weights;
v0.c = clamp(v0.c, vec4(0), vec4(255));
#endif
v0.p.z += dot(dz, weights); // Extrapolate depth
v0.t_float.z += dot(df, weights); // Extrapolate fog
}
void main()
{
ProcessedVertex vtx;
uint vid = uint(gl_VertexID);
#if VS_EXPAND == VS_EXPAND_POINT
vtx = load_vertex(vid >> 2);
vtx.p.x += ((vid & 1u) != 0u) ? PointSize.x : 0.0f;
vtx.p.y += ((vid & 2u) != 0u) ? PointSize.y : 0.0f;
#elif (VS_EXPAND == VS_EXPAND_LINE) || (VS_EXPAND == VS_EXPAND_LINE_AA1)
uint vid_base = vid >> 2;
bool is_bottom = (vid & 2u) != 0u;
bool is_right = (vid & 1u) != 0u;
uint vid_other = is_bottom ? vid_base - 1u : vid_base + 1u;
vtx = load_vertex(vid_base);
ProcessedVertex other = load_vertex(vid_other);
// Use bottom minus top for delta regardless of which vertex we are expanding.
vec2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
vec2 line_vector = normalize(line_delta / VertexScale);
vec2 line_expand = vec2(line_vector.y, -line_vector.x);
#if VS_EXPAND == VS_EXPAND_LINE_AA1
line_expand *= 2.0f * LineAA1Width;
#endif
vec2 line_width = (line_expand * PointSize) / 2.0f;
vec2 offset = is_right ? line_width : -line_width;
vtx.p.xy += offset;
#if VS_EXPAND == VS_EXPAND_LINE_AA1
VSout.inv_cov = is_right ? 1.0f : -1.0f;
#endif
// Lines will be run as (0 1 2) (1 2 3)
// This means that both triangles will have a point based off the top line point as their first point
// So we don't have to do anything for !IIP
#elif VS_EXPAND == VS_EXPAND_SPRITE
// Sprite points are always in pairs
uint vid_base = vid >> 1;
uint vid_lt = vid_base & ~1u;
uint vid_rb = vid_base | 1u;
ProcessedVertex lt = load_vertex(vid_lt);
ProcessedVertex rb = load_vertex(vid_rb);
vtx = rb;
bool is_right = ((vid & 1u) != 0u);
vtx.p.x = is_right ? lt.p.x : vtx.p.x;
vtx.t_float.x = is_right ? lt.t_float.x : vtx.t_float.x;
vtx.t_int.xz = is_right ? lt.t_int.xz : vtx.t_int.xz;
bool is_bottom = ((vid & 2u) != 0u);
vtx.p.y = is_bottom ? lt.p.y : vtx.p.y;
vtx.t_float.y = is_bottom ? lt.t_float.y : vtx.t_float.y;
vtx.t_int.yw = is_bottom ? lt.t_int.yw : vtx.t_int.yw;
#elif VS_EXPAND == VS_EXPAND_TRIANGLE_AA1
// Triangles with AA1 are expanded as follows:
// - Vertices 0-2: Interior of triangle (1 triangle).
// - Vertices 3-8: First edge expanded (2 triangles).
// - Vertices 9-14: Second edge expanded (2 triangles).
// - Vertices 15-20: Third edge expanded (2 triangles).
// - Vertices 21-26: First corner cap (2 triangles).
// - Vertices 27-32: Second corner cap (2 triangles).
// - Vertices 33-38: Third corner cap (2 triangles).
uint prim_id = vid / 39u;
uint prim_offset = vid - 39u * prim_id; // range: 0-38
bool interior = prim_offset < 3u;
bool edge = 3u <= prim_offset && prim_offset < 21u;
if (interior)
{
vtx = load_vertex(load_index(3u * prim_id + prim_offset));
VSout.inv_cov = 0.0f; // Full coverage
VSout.interior = 1u;
}
else if (edge)
{
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
uint prim_offset_edges = prim_offset - 3u; // range: 0-17
uint i0 = prim_offset_edges / 6u;
uint i1 = (i0 >= 2u) ? i0 - 2u : i0 + 1u;
uint i2 = (i0 >= 1u) ? i0 - 1u : i0 + 2u;
uint edge_offset = prim_offset_edges - 6u * i0; // range: 0-5
// Note: order of top/bottom, inside/outside is arbitrary,
// as long as it assembles into two triangles forming a quad.
bool is_bottom = (2u <= edge_offset) && (edge_offset <= 4u);
bool is_outside = (edge_offset & 1u) != 0u;
vtx = load_vertex(load_index(3u * prim_id + (is_bottom ? i1 : i0)));
ProcessedVertex other = load_vertex(load_index(3u * prim_id + (is_bottom ? i0 : i1)));
ProcessedVertex opposite = load_vertex(load_index(3u * prim_id + i2));
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
vec2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(vtx, other, opposite) : vec2(0);
// Do actual extrapolation, or no-op if expand_dir == 0.
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
VSout.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
VSout.interior = 0u;
}
else // Corner cap
{
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
uint prim_offset_cap = prim_offset - 21u; // range: 0-17
uint i0 = prim_offset_cap / 6u;
uint i1 = (i0 >= 2u) ? i0 - 2u : i0 + 1u;
uint i2 = (i0 >= 1u) ? i0 - 1u : i0 + 2u;
uint cap_offset = prim_offset_cap - 6u * i0; // range: 0-5
bool is_near_corner = cap_offset == 0u || cap_offset == 3u;
bool is_far_corner = cap_offset == 2u || cap_offset == 5u;
bool is_first_tri = cap_offset < 3u;
// First triangle is on the side of vertex i1 and second is on the side of vertex i2.
vtx = load_vertex(load_index(3u * prim_id + i0));
ProcessedVertex other = load_vertex(load_index(3u * prim_id + (is_first_tri ? i1 : i2)));
ProcessedVertex opposite = load_vertex(load_index(3u * prim_id + (is_first_tri ? i2 : i1)));
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
// Get the edge expansion directions of both incident edges.
vec2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(vtx, other, opposite);
vec2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(vtx, opposite, other);
// Check if the corner is already filled by the expanded edges.
// This happens if the expand directions are the same.
// If so we output a degenerate triangle at this corner.
bool corner_filled = all(equal(edge_expand_dir_0, edge_expand_dir_1));
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
vec2 far_corner_dir = corner_filled ? vec2(0) : -normalize((pos_deltas[0] + pos_deltas[1]) / 2.0f);
// Determine the expand direction.
vec2 expand_dir = is_near_corner ? vec2(0) : // No extrapolation
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
edge_expand_dir_0; // Standard AA1 edge expansion
// Do the actual extrapolation (no-op if expand_dir == 0).
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
VSout.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
VSout.interior = 0u;
}
#endif
gl_Position = vtx.p;
VSout.t_float = vtx.t_float;
VSout.t_int = vtx.t_int;
VSout.c = vtx.c;
}
#endif // VS_EXPAND
#endif // VERTEX_SHADER
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+273 -108
View File
@@ -1,8 +1,8 @@
{ {
"schema_version": 1, "schema_version": 1,
"generated_at": "2026-10-06T03:26:07Z", "generated_at": "2026-10-06T10:11:17Z",
"total_files": 18990, "total_files": 18997,
"total_size": 17482351547, "total_size": 17487414657,
"files": { "files": {
"738c7f5fd0c8b57ee2e87ae7a97bf8e21a821d07": { "738c7f5fd0c8b57ee2e87ae7a97bf8e21a821d07": {
"path": "bios/3D Realms/Duke Nukem 3D/DUKE.RTS", "path": "bios/3D Realms/Duke Nukem 3D/DUKE.RTS",
@@ -9040,26 +9040,6 @@
"crc32": "772caf31", "crc32": "772caf31",
"adler32": "7fd6387e" "adler32": "7fd6387e"
}, },
"d6cedce971a2a4ab2588e39285a9d2fb37b62924": {
"path": "bios/Arcade/Arcade/switch/prod.keys",
"name": "prod.keys",
"size": 15039,
"sha1": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"md5": "4ed853d4a52e6b9b9e11954f155ecb8a",
"sha256": "76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990",
"crc32": "6d74253f",
"adler32": "c8696e75"
},
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10": {
"path": "bios/Arcade/Arcade/switch/title.keys",
"name": "title.keys",
"size": 19584,
"sha1": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"md5": "b5890c3d737880f6dd3751ffec5b128b",
"sha256": "dc6e31aae8daa9067dafcf2bca6d9d3aa691a63474eb698ad8c54be3e360753b",
"crc32": "04387ea8",
"adler32": "74fffc45"
},
"1f424e6329bc1dfc254a60b752e0c2ad1d1f6126": { "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126": {
"path": "bios/Arcade/Arcade/sx1541.zip", "path": "bios/Arcade/Arcade/sx1541.zip",
"name": "sx1541.zip", "name": "sx1541.zip",
@@ -129419,6 +129399,26 @@
"crc32": "4c866029", "crc32": "4c866029",
"adler32": "a75265c4" "adler32": "a75265c4"
}, },
"d6cedce971a2a4ab2588e39285a9d2fb37b62924": {
"path": "bios/Nintendo/Switch/.variants/prod.keys.d6cedce9",
"name": "prod.keys",
"size": 15039,
"sha1": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"md5": "4ed853d4a52e6b9b9e11954f155ecb8a",
"sha256": "76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990",
"crc32": "6d74253f",
"adler32": "c8696e75"
},
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10": {
"path": "bios/Nintendo/Switch/.variants/title.keys.8e6c2873",
"name": "title.keys",
"size": 19584,
"sha1": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"md5": "b5890c3d737880f6dd3751ffec5b128b",
"sha256": "dc6e31aae8daa9067dafcf2bca6d9d3aa691a63474eb698ad8c54be3e360753b",
"crc32": "04387ea8",
"adler32": "74fffc45"
},
"9d4488178fe93d053d44f836e849d3d6b3e5974e": { "9d4488178fe93d053d44f836e849d3d6b3e5974e": {
"path": "bios/Nintendo/Switch/Firmware_batocera43.zip", "path": "bios/Nintendo/Switch/Firmware_batocera43.zip",
"name": "Firmware_batocera43.zip", "name": "Firmware_batocera43.zip",
@@ -135666,6 +135666,36 @@
"crc32": "ca72899c", "crc32": "ca72899c",
"adler32": "f932ede3" "adler32": "f932ede3"
}, },
"2d9878fdd70979ecf6dd0da7652efd3797c26039": {
"path": "bios/Other/emucorex/resources/GameIndex.yaml",
"name": "GameIndex.yaml",
"size": 2697551,
"sha1": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"md5": "602ea57b1d141e81b2a96f20a4b8098c",
"sha256": "d5558a0e0aa033482b098cc8a6449e78137f5069beabf421ab60d9eccbb62d8f",
"crc32": "e8038ec9",
"adler32": "933aaa5d"
},
"a3d939af27fe0929e61b8e20d49ef58316c0a66c": {
"path": "bios/Other/emucorex/resources/patches.zip",
"name": "patches.zip",
"size": 2091144,
"sha1": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"md5": "ed4d4e8f81d9707dbf4931a786b6b56d",
"sha256": "aa14a46908c1114e0c0f1c06accbfa7efc83c1768fb501699e043cbdb1af797f",
"crc32": "8820b708",
"adler32": "ec343d8e"
},
"81c56a54bde692fb15fca4efe6db508b7d09bf6e": {
"path": "bios/Other/emucorex/resources/shaders/common/fxaa.fx",
"name": "fxaa.fx",
"size": 17432,
"sha1": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"md5": "efa6392d3dd6e66abf94542159889e4c",
"sha256": "817eb2a258e44bb730b94ab77b00cac2480c2e27449078949e2897bad3cb3bc3",
"crc32": "ea1fd68a",
"adler32": "f2f86cbf"
},
"cb0d4276f9947762af0841a8af75efa6cbe9109c": { "cb0d4276f9947762af0841a8af75efa6cbe9109c": {
"path": "bios/Other/emucorex/resources/shaders/opengl/cas.glsl", "path": "bios/Other/emucorex/resources/shaders/opengl/cas.glsl",
"name": "cas.glsl", "name": "cas.glsl",
@@ -135736,6 +135766,26 @@
"crc32": "54c435f2", "crc32": "54c435f2",
"adler32": "c42ff6d7" "adler32": "c42ff6d7"
}, },
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528": {
"path": "bios/Other/emucorex/resources/shaders/opengl/tfx_fs.glsl",
"name": "tfx_fs.glsl",
"size": 37479,
"sha1": "f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"md5": "0ae47ac28b7183aca6e74cc18559ee8d",
"sha256": "7194fda8019aeee6d74c70876bf39586996e594e5c85b53136d6cce3118e4a23",
"crc32": "9a1419e3",
"adler32": "7c34a5b0"
},
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943": {
"path": "bios/Other/emucorex/resources/shaders/opengl/tfx_vgs.glsl",
"name": "tfx_vgs.glsl",
"size": 14156,
"sha1": "cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"md5": "9e537d9c29b5278a47412ace793a648d",
"sha256": "1f63be246613c1459891ea1cf9faca74161bf97a44ad75d305a035c319d478b3",
"crc32": "68bbb4a2",
"adler32": "87949cf8"
},
"005cecf1a5e20b05407d55d0aad22cf81ad66c31": { "005cecf1a5e20b05407d55d0aad22cf81ad66c31": {
"path": "bios/Other/emucorex/resources/shaders/vulkan/cas.glsl", "path": "bios/Other/emucorex/resources/shaders/vulkan/cas.glsl",
"name": "cas.glsl", "name": "cas.glsl",
@@ -135806,6 +135856,16 @@
"crc32": "69dc25d7", "crc32": "69dc25d7",
"adler32": "9f4b472e" "adler32": "9f4b472e"
}, },
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80": {
"path": "bios/Other/emucorex/resources/shaders/vulkan/tfx.glsl",
"name": "tfx.glsl",
"size": 52705,
"sha1": "373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"md5": "f88711bf5b4476782fab23bef3f7aa98",
"sha256": "db3c84c36729bd6a321566fae796f2ef3645f9889188f2af7e223d5f42d46755",
"crc32": "348f482e",
"adler32": "257a6143"
},
"407f0fde89ac1a628fed0329072dfae1f8d304b1": { "407f0fde89ac1a628fed0329072dfae1f8d304b1": {
"path": "bios/Other/epsxe/.variants/libopenglplugin.so.0e506076", "path": "bios/Other/epsxe/.variants/libopenglplugin.so.0e506076",
"name": "libopenglplugin.so", "name": "libopenglplugin.so",
@@ -154911,6 +154971,16 @@
"crc32": "94468d79", "crc32": "94468d79",
"adler32": "97dfa90c" "adler32": "97dfa90c"
}, },
"681e3362f0251d2e26e6bc2e06157c027f5f3116": {
"path": "bios/Other/supermodel-dojo/Config/Games.xml",
"name": "Games.xml",
"size": 152643,
"sha1": "681e3362f0251d2e26e6bc2e06157c027f5f3116",
"md5": "d5c1f69e9910622b4ca6beae4c23d68a",
"sha256": "a73b9f1c37d64532bf82d1d790a3e0cb81a9b2accb04d866901bb7ae1970473a",
"crc32": "a7b8bc61",
"adler32": "03e1bc30"
},
"bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22": { "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22": {
"path": "bios/Other/swf-player/assets/META-INF/AIR/application.xml", "path": "bios/Other/swf-player/assets/META-INF/AIR/application.xml",
"name": "application.xml", "name": "application.xml",
@@ -195970,8 +196040,6 @@
"733af5c6422ec0d77423047c1401b8f1": "60c6e393295188b8937bf975394b799c64d87e70", "733af5c6422ec0d77423047c1401b8f1": "60c6e393295188b8937bf975394b799c64d87e70",
"3ec5ebb9a9d65cccedb73d5d326194eb": "85acdc4646039d919c6125d715109685865376ab", "3ec5ebb9a9d65cccedb73d5d326194eb": "85acdc4646039d919c6125d715109685865376ab",
"3088997dfc4798cfc86a7eb357d2186c": "05b91dc8dd6fdbbf982388fd0673073363be9ce6", "3088997dfc4798cfc86a7eb357d2186c": "05b91dc8dd6fdbbf982388fd0673073363be9ce6",
"4ed853d4a52e6b9b9e11954f155ecb8a": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"b5890c3d737880f6dd3751ffec5b128b": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"601203108bc1c3c8e46d08eb247a92fc": "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126", "601203108bc1c3c8e46d08eb247a92fc": "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126",
"20c241b5bf924f2338d79dd1d37769d5": "fbba87e9d6852862252d31dd919f943c8edd7eb7", "20c241b5bf924f2338d79dd1d37769d5": "fbba87e9d6852862252d31dd919f943c8edd7eb7",
"8c11c3ead979857c38177a270382bd84": "721621a353051fea6de484c26b534cdec4de616e", "8c11c3ead979857c38177a270382bd84": "721621a353051fea6de484c26b534cdec4de616e",
@@ -207772,6 +207840,8 @@
"8ecd73eb4edf7ed7e81aef1be80031d5": "e5b2922ca137051059e4269b236d07a22c07bc84", "8ecd73eb4edf7ed7e81aef1be80031d5": "e5b2922ca137051059e4269b236d07a22c07bc84",
"6d507dcf3b61ce45a5717421cdd0ae95": "f282b3aaf98f8423dab7d77f1aa0192be630f2fb", "6d507dcf3b61ce45a5717421cdd0ae95": "f282b3aaf98f8423dab7d77f1aa0192be630f2fb",
"a43cbd0ab271990f434412fc5c500eff": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de", "a43cbd0ab271990f434412fc5c500eff": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de",
"4ed853d4a52e6b9b9e11954f155ecb8a": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"b5890c3d737880f6dd3751ffec5b128b": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"89049432c168d28f649348cd31c057cd": "9d4488178fe93d053d44f836e849d3d6b3e5974e", "89049432c168d28f649348cd31c057cd": "9d4488178fe93d053d44f836e849d3d6b3e5974e",
"31fda6c3678507b12631dfd01b0415ef": "c146b651696d25287241f4bab18226e488bc45b3", "31fda6c3678507b12631dfd01b0415ef": "c146b651696d25287241f4bab18226e488bc45b3",
"f65367650f88124362922994b78ede77": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de", "f65367650f88124362922994b78ede77": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
@@ -208396,6 +208466,9 @@
"22e156b807f3147bb7dcafadc52ab721": "4d0f8dfc5980195ee905fa39e125cd418b8a9599", "22e156b807f3147bb7dcafadc52ab721": "4d0f8dfc5980195ee905fa39e125cd418b8a9599",
"f4ae6809bd8c31573370e8da72514012": "e18b9aabbda0cc178436855ef8d04bce96533049", "f4ae6809bd8c31573370e8da72514012": "e18b9aabbda0cc178436855ef8d04bce96533049",
"bf5e583b038933c5f7bc4f0a6f70617c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c", "bf5e583b038933c5f7bc4f0a6f70617c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"602ea57b1d141e81b2a96f20a4b8098c": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"ed4d4e8f81d9707dbf4931a786b6b56d": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"efa6392d3dd6e66abf94542159889e4c": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"b56eb507fac5ac64a7079514c02b8ca7": "cb0d4276f9947762af0841a8af75efa6cbe9109c", "b56eb507fac5ac64a7079514c02b8ca7": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
"b8f058ec01942f596c890a060a32f08b": "35910b67070342cdd6ec4d68eb2e56b39d350543", "b8f058ec01942f596c890a060a32f08b": "35910b67070342cdd6ec4d68eb2e56b39d350543",
"9338b9a25372178b4ecbf0215697e9be": "ca149058af89d8f612aaa529661e8267d9569875", "9338b9a25372178b4ecbf0215697e9be": "ca149058af89d8f612aaa529661e8267d9569875",
@@ -208403,6 +208476,8 @@
"1cbf28d65b0d6fe727b571d3e46871b8": "c8eb9c1926baad8bbba25f97be54abae233fe715", "1cbf28d65b0d6fe727b571d3e46871b8": "c8eb9c1926baad8bbba25f97be54abae233fe715",
"480b029f6c357fb683a6720e2b78d990": "4abe45e59dc9286321a115c76c31cc7fe85e58d5", "480b029f6c357fb683a6720e2b78d990": "4abe45e59dc9286321a115c76c31cc7fe85e58d5",
"23a5e91beb2072f48e1011e7a9b6e901": "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564", "23a5e91beb2072f48e1011e7a9b6e901": "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564",
"0ae47ac28b7183aca6e74cc18559ee8d": "f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"9e537d9c29b5278a47412ace793a648d": "cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"deb7394a31184990c78c9753a293ff60": "005cecf1a5e20b05407d55d0aad22cf81ad66c31", "deb7394a31184990c78c9753a293ff60": "005cecf1a5e20b05407d55d0aad22cf81ad66c31",
"669f55ffa59d7b754ab11f96c42f206f": "705d20d39a910d58a070fb86780ac58c8007591c", "669f55ffa59d7b754ab11f96c42f206f": "705d20d39a910d58a070fb86780ac58c8007591c",
"b79725bff4fecb48d68ad575e13950d1": "276d984150c2fd2fb5ba287f2e5ceb7262dc5a95", "b79725bff4fecb48d68ad575e13950d1": "276d984150c2fd2fb5ba287f2e5ceb7262dc5a95",
@@ -208410,6 +208485,7 @@
"8fbeeb5c09477bcaf614087003df61a4": "808b7b46bfb033c7fe4d94d0dbf5a271de30dedc", "8fbeeb5c09477bcaf614087003df61a4": "808b7b46bfb033c7fe4d94d0dbf5a271de30dedc",
"b0bdec53b1c50873ba45539565f1327c": "a848aad1840c7d85bfcdd552521a7181b00331cc", "b0bdec53b1c50873ba45539565f1327c": "a848aad1840c7d85bfcdd552521a7181b00331cc",
"ec6b58f723279afe732b7f9660acf27d": "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5", "ec6b58f723279afe732b7f9660acf27d": "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5",
"f88711bf5b4476782fab23bef3f7aa98": "373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"0e506076654afda89187c33481b1bfef": "407f0fde89ac1a628fed0329072dfae1f8d304b1", "0e506076654afda89187c33481b1bfef": "407f0fde89ac1a628fed0329072dfae1f8d304b1",
"1ca91fb5e46147945fd924b13e868d97": "f0168fbad73f8c0589c070f3e2071c1d62967a40", "1ca91fb5e46147945fd924b13e868d97": "f0168fbad73f8c0589c070f3e2071c1d62967a40",
"b28dd2574fb0d7dea4950bde882bd537": "e17ecabba25dc797bf7a0e9979b18e5189e410e6", "b28dd2574fb0d7dea4950bde882bd537": "e17ecabba25dc797bf7a0e9979b18e5189e410e6",
@@ -210317,6 +210393,7 @@
"fc26e2deaa532794d91fe33ca73f8e2c": "b41150f8cff409dde89939b80a980a20e853df0b", "fc26e2deaa532794d91fe33ca73f8e2c": "b41150f8cff409dde89939b80a980a20e853df0b",
"a468e45e005caa89356bd67b70e0d37d": "6c38cc6fba14863bfbdc730e53d3274fd79a4544", "a468e45e005caa89356bd67b70e0d37d": "6c38cc6fba14863bfbdc730e53d3274fd79a4544",
"e8da2d033e90d6fefa884ee78690b4e0": "cb6be8206308470ecaa8b9cc882d82abe0ef3562", "e8da2d033e90d6fefa884ee78690b4e0": "cb6be8206308470ecaa8b9cc882d82abe0ef3562",
"d5c1f69e9910622b4ca6beae4c23d68a": "681e3362f0251d2e26e6bc2e06157c027f5f3116",
"de36d0dc1b74a39be2313749138d4d00": "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22", "de36d0dc1b74a39be2313749138d4d00": "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22",
"39c997db73db15a6807b35f148fcc85a": "0aaf20633888d2d0a2da38e82773cb5d44e840eb", "39c997db73db15a6807b35f148fcc85a": "0aaf20633888d2d0a2da38e82773cb5d44e840eb",
"834f85979115fd740943a782887d0ab0": "d27da38e4d37727a44bf04d7690a09135fad7115", "834f85979115fd740943a782887d0ab0": "d27da38e4d37727a44bf04d7690a09135fad7115",
@@ -216839,14 +216916,6 @@
"firmare installation.txt": [ "firmare installation.txt": [
"05b91dc8dd6fdbbf982388fd0673073363be9ce6" "05b91dc8dd6fdbbf982388fd0673073363be9ce6"
], ],
"prod.keys": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"c146b651696d25287241f4bab18226e488bc45b3"
],
"title.keys": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de"
],
"sx1541.zip": [ "sx1541.zip": [
"1f424e6329bc1dfc254a60b752e0c2ad1d1f6126" "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126"
], ],
@@ -246560,6 +246629,14 @@
"sgb_boot.bin": [ "sgb_boot.bin": [
"369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de" "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de"
], ],
"prod.keys": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"c146b651696d25287241f4bab18226e488bc45b3"
],
"title.keys": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de"
],
"Firmware_batocera43.zip": [ "Firmware_batocera43.zip": [
"9d4488178fe93d053d44f836e849d3d6b3e5974e" "9d4488178fe93d053d44f836e849d3d6b3e5974e"
], ],
@@ -247590,6 +247667,7 @@
], ],
"fxaa.fx": [ "fxaa.fx": [
"9d7012f8bb7bb4599ed2518f12ed540655cdd8d8", "9d7012f8bb7bb4599ed2518f12ed540655cdd8d8",
"81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a" "3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a"
], ],
"imgui.fx": [ "imgui.fx": [
@@ -247739,6 +247817,7 @@
], ],
"tfx.glsl": [ "tfx.glsl": [
"229d91d96b6dc338421e696fe8cdcfcaa093f40d", "229d91d96b6dc338421e696fe8cdcfcaa093f40d",
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"aa06cffa086126bb4972674562a5f5c8dc6082f3", "aa06cffa086126bb4972674562a5f5c8dc6082f3",
"1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329", "1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329",
"b4ebf3f7f20fc88f91cda033552a7489a9ff179c", "b4ebf3f7f20fc88f91cda033552a7489a9ff179c",
@@ -247746,12 +247825,14 @@
], ],
"tfx_fs.glsl": [ "tfx_fs.glsl": [
"d67bfc4ab5c7e550ea41a0491f45b95f395bacac", "d67bfc4ab5c7e550ea41a0491f45b95f395bacac",
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"00eb0418e7a25c89aaf7c526922fe45d83243ec5", "00eb0418e7a25c89aaf7c526922fe45d83243ec5",
"919c3308c8850852d529d0127ce94e873375bfd2", "919c3308c8850852d529d0127ce94e873375bfd2",
"554160d31bb97aca0e9f367f4de811dd8ad1fb21" "554160d31bb97aca0e9f367f4de811dd8ad1fb21"
], ],
"tfx_vgs.glsl": [ "tfx_vgs.glsl": [
"f8200a0f4bd284670f0f0191fa9340684e94f66d", "f8200a0f4bd284670f0f0191fa9340684e94f66d",
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"0e9e98f766c9250b465af776d0077f49f797fed2", "0e9e98f766c9250b465af776d0077f49f797fed2",
"fc40fc49de1cd8ed7d60df0394dd6367ae82b85f" "fc40fc49de1cd8ed7d60df0394dd6367ae82b85f"
], ],
@@ -248407,6 +248488,22 @@
"games.db": [ "games.db": [
"0e2ee74d1f9ebc3a6faf9dbaebbacc2ff053f4df" "0e2ee74d1f9ebc3a6faf9dbaebbacc2ff053f4df"
], ],
"GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"997fdfb1c1873c1a43972b2984ac0da61fd47b14",
"fca26d4f7c1fe0abf6534b470da2ee5ad461eba5",
"58fb1f8ea6cf2d526904349640453e682fe388a1",
"7efb2ed721cf0c0f0677324e09be311a8337e793",
"57369df9ca82a460e596cea7835c5a0797dee668",
"b22389650f6c0a1e276b48213fed8a9c1c6476ce",
"6eddec30056cde7c664a0cf508dcad29353a12bb",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"2562e542d66db945ccf158d7a221c2a28e3b6be4",
"541a74165496fb7ef35ce6ec03f711ebbe263e11"
],
"libopenglplugin.so": [ "libopenglplugin.so": [
"407f0fde89ac1a628fed0329072dfae1f8d304b1", "407f0fde89ac1a628fed0329072dfae1f8d304b1",
"f0168fbad73f8c0589c070f3e2071c1d62967a40", "f0168fbad73f8c0589c070f3e2071c1d62967a40",
@@ -249537,16 +249634,6 @@
"3rdparty.html": [ "3rdparty.html": [
"7743bf1d7e6e09a02f1b2972aa63bd8a9f26050d" "7743bf1d7e6e09a02f1b2972aa63bd8a9f26050d"
], ],
"GameIndex.yaml": [
"997fdfb1c1873c1a43972b2984ac0da61fd47b14",
"fca26d4f7c1fe0abf6534b470da2ee5ad461eba5",
"58fb1f8ea6cf2d526904349640453e682fe388a1",
"7efb2ed721cf0c0f0677324e09be311a8337e793",
"57369df9ca82a460e596cea7835c5a0797dee668",
"b22389650f6c0a1e276b48213fed8a9c1c6476ce",
"6eddec30056cde7c664a0cf508dcad29353a12bb",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"cheats_ni.zip": [ "cheats_ni.zip": [
"0b219dc8b3b98c1705b14758bb7950064b928ebb", "0b219dc8b3b98c1705b14758bb7950064b928ebb",
"31cbff66380f255d359ff9ee09b6edc06102579a", "31cbff66380f255d359ff9ee09b6edc06102579a",
@@ -253818,6 +253905,7 @@
"Games.xml": [ "Games.xml": [
"ccbc64e2f5b9aefd89ff9acd7227eff230aed8ed", "ccbc64e2f5b9aefd89ff9acd7227eff230aed8ed",
"b41150f8cff409dde89939b80a980a20e853df0b", "b41150f8cff409dde89939b80a980a20e853df0b",
"681e3362f0251d2e26e6bc2e06157c027f5f3116",
"6871a47de90ca67f9ff8e4e280416851a8164563", "6871a47de90ca67f9ff8e4e280416851a8164563",
"57500ad35b883acb5e7766c04f5b42e65185715c", "57500ad35b883acb5e7766c04f5b42e65185715c",
"fa334ab5ba6366bd28168860c913e6365b9fec96" "fa334ab5ba6366bd28168860c913e6365b9fec96"
@@ -262936,10 +263024,6 @@
"1d3caaed7411425c16aee8c35db47b928e31f60b", "1d3caaed7411425c16aee8c35db47b928e31f60b",
"62f5a0b54a174daf2bf66634b86c6f2a3cd297ce" "62f5a0b54a174daf2bf66634b86c6f2a3cd297ce"
], ],
"patches.zip": [
"2562e542d66db945ccf158d7a221c2a28e3b6be4",
"541a74165496fb7ef35ce6ec03f711ebbe263e11"
],
"ps2-dvd-110u.bin": [ "ps2-dvd-110u.bin": [
"0bc9143cb6839471d62b8fdd0b2fc05e941527d3" "0bc9143cb6839471d62b8fdd0b2fc05e941527d3"
], ],
@@ -271760,8 +271844,6 @@
"621a057a": "60c6e393295188b8937bf975394b799c64d87e70", "621a057a": "60c6e393295188b8937bf975394b799c64d87e70",
"4860e4b4": "85acdc4646039d919c6125d715109685865376ab", "4860e4b4": "85acdc4646039d919c6125d715109685865376ab",
"772caf31": "05b91dc8dd6fdbbf982388fd0673073363be9ce6", "772caf31": "05b91dc8dd6fdbbf982388fd0673073363be9ce6",
"6d74253f": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"04387ea8": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"9145508f": "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126", "9145508f": "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126",
"450b9cb4": "fbba87e9d6852862252d31dd919f943c8edd7eb7", "450b9cb4": "fbba87e9d6852862252d31dd919f943c8edd7eb7",
"d1821125": "721621a353051fea6de484c26b534cdec4de616e", "d1821125": "721621a353051fea6de484c26b534cdec4de616e",
@@ -283562,6 +283644,8 @@
"cb176e45": "e5b2922ca137051059e4269b236d07a22c07bc84", "cb176e45": "e5b2922ca137051059e4269b236d07a22c07bc84",
"3ad83b6e": "f282b3aaf98f8423dab7d77f1aa0192be630f2fb", "3ad83b6e": "f282b3aaf98f8423dab7d77f1aa0192be630f2fb",
"4c866029": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de", "4c866029": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de",
"6d74253f": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"04387ea8": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"01008403": "9d4488178fe93d053d44f836e849d3d6b3e5974e", "01008403": "9d4488178fe93d053d44f836e849d3d6b3e5974e",
"a65c13ca": "c146b651696d25287241f4bab18226e488bc45b3", "a65c13ca": "c146b651696d25287241f4bab18226e488bc45b3",
"77cacffb": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de", "77cacffb": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
@@ -284186,6 +284270,9 @@
"bf24835a": "4d0f8dfc5980195ee905fa39e125cd418b8a9599", "bf24835a": "4d0f8dfc5980195ee905fa39e125cd418b8a9599",
"e1232465": "e18b9aabbda0cc178436855ef8d04bce96533049", "e1232465": "e18b9aabbda0cc178436855ef8d04bce96533049",
"ca72899c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c", "ca72899c": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"e8038ec9": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"8820b708": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"ea1fd68a": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"13eed5a5": "cb0d4276f9947762af0841a8af75efa6cbe9109c", "13eed5a5": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
"bb25c312": "35910b67070342cdd6ec4d68eb2e56b39d350543", "bb25c312": "35910b67070342cdd6ec4d68eb2e56b39d350543",
"342d0e13": "ca149058af89d8f612aaa529661e8267d9569875", "342d0e13": "ca149058af89d8f612aaa529661e8267d9569875",
@@ -284193,6 +284280,8 @@
"ae991348": "c8eb9c1926baad8bbba25f97be54abae233fe715", "ae991348": "c8eb9c1926baad8bbba25f97be54abae233fe715",
"67a7b03b": "4abe45e59dc9286321a115c76c31cc7fe85e58d5", "67a7b03b": "4abe45e59dc9286321a115c76c31cc7fe85e58d5",
"54c435f2": "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564", "54c435f2": "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564",
"9a1419e3": "f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"68bbb4a2": "cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"e00e7479": "005cecf1a5e20b05407d55d0aad22cf81ad66c31", "e00e7479": "005cecf1a5e20b05407d55d0aad22cf81ad66c31",
"fd02bb00": "705d20d39a910d58a070fb86780ac58c8007591c", "fd02bb00": "705d20d39a910d58a070fb86780ac58c8007591c",
"c9511683": "276d984150c2fd2fb5ba287f2e5ceb7262dc5a95", "c9511683": "276d984150c2fd2fb5ba287f2e5ceb7262dc5a95",
@@ -284200,6 +284289,7 @@
"51cef730": "808b7b46bfb033c7fe4d94d0dbf5a271de30dedc", "51cef730": "808b7b46bfb033c7fe4d94d0dbf5a271de30dedc",
"50fb46e4": "a848aad1840c7d85bfcdd552521a7181b00331cc", "50fb46e4": "a848aad1840c7d85bfcdd552521a7181b00331cc",
"69dc25d7": "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5", "69dc25d7": "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5",
"348f482e": "373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"9a76f6f2": "407f0fde89ac1a628fed0329072dfae1f8d304b1", "9a76f6f2": "407f0fde89ac1a628fed0329072dfae1f8d304b1",
"f3a06766": "f0168fbad73f8c0589c070f3e2071c1d62967a40", "f3a06766": "f0168fbad73f8c0589c070f3e2071c1d62967a40",
"cb72da07": "e17ecabba25dc797bf7a0e9979b18e5189e410e6", "cb72da07": "e17ecabba25dc797bf7a0e9979b18e5189e410e6",
@@ -286107,6 +286197,7 @@
"b2ea6948": "b41150f8cff409dde89939b80a980a20e853df0b", "b2ea6948": "b41150f8cff409dde89939b80a980a20e853df0b",
"2d012d14": "6c38cc6fba14863bfbdc730e53d3274fd79a4544", "2d012d14": "6c38cc6fba14863bfbdc730e53d3274fd79a4544",
"94468d79": "cb6be8206308470ecaa8b9cc882d82abe0ef3562", "94468d79": "cb6be8206308470ecaa8b9cc882d82abe0ef3562",
"a7b8bc61": "681e3362f0251d2e26e6bc2e06157c027f5f3116",
"2d08ea82": "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22", "2d08ea82": "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22",
"21a26259": "0aaf20633888d2d0a2da38e82773cb5d44e840eb", "21a26259": "0aaf20633888d2d0a2da38e82773cb5d44e840eb",
"e251326a": "d27da38e4d37727a44bf04d7690a09135fad7115", "e251326a": "d27da38e4d37727a44bf04d7690a09135fad7115",
@@ -290752,8 +290843,6 @@
"ebb770368645197d340c4c618e243425e64ab444f128b5a7ab84761564bf5d89": "60c6e393295188b8937bf975394b799c64d87e70", "ebb770368645197d340c4c618e243425e64ab444f128b5a7ab84761564bf5d89": "60c6e393295188b8937bf975394b799c64d87e70",
"58fb6bdd20229c3a1d442d416aa1d4c120eebf402bfb408a245ef406fbb84a81": "85acdc4646039d919c6125d715109685865376ab", "58fb6bdd20229c3a1d442d416aa1d4c120eebf402bfb408a245ef406fbb84a81": "85acdc4646039d919c6125d715109685865376ab",
"1cf244001bcc42008d93669cdf7bb37a2b343045cd81485527111c5dec2be092": "05b91dc8dd6fdbbf982388fd0673073363be9ce6", "1cf244001bcc42008d93669cdf7bb37a2b343045cd81485527111c5dec2be092": "05b91dc8dd6fdbbf982388fd0673073363be9ce6",
"76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"dc6e31aae8daa9067dafcf2bca6d9d3aa691a63474eb698ad8c54be3e360753b": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"3e1cfb8ce0475c8088d8393aeb75341edbed5f30d7c023aac673fe549c0545fe": "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126", "3e1cfb8ce0475c8088d8393aeb75341edbed5f30d7c023aac673fe549c0545fe": "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126",
"f3bb5f2101534172ec49f6e7b6eb66bb2a2116c953d64517fc098a51f042002a": "fbba87e9d6852862252d31dd919f943c8edd7eb7", "f3bb5f2101534172ec49f6e7b6eb66bb2a2116c953d64517fc098a51f042002a": "fbba87e9d6852862252d31dd919f943c8edd7eb7",
"5fc6a17d793b5b647c7590f650ee3f21c5210cca7e05e51734a3c3d20182698d": "721621a353051fea6de484c26b534cdec4de616e", "5fc6a17d793b5b647c7590f650ee3f21c5210cca7e05e51734a3c3d20182698d": "721621a353051fea6de484c26b534cdec4de616e",
@@ -302554,6 +302643,8 @@
"c172498a23d1176672931bab33b629c7d28f914a43dca9e540b8af1b37ccf2c6": "e5b2922ca137051059e4269b236d07a22c07bc84", "c172498a23d1176672931bab33b629c7d28f914a43dca9e540b8af1b37ccf2c6": "e5b2922ca137051059e4269b236d07a22c07bc84",
"8a65465a9da7ec657726a671da9a85963cf19d2c975e499aea6b97eb37e0b6ea": "f282b3aaf98f8423dab7d77f1aa0192be630f2fb", "8a65465a9da7ec657726a671da9a85963cf19d2c975e499aea6b97eb37e0b6ea": "f282b3aaf98f8423dab7d77f1aa0192be630f2fb",
"b60d493a7944ccf74c81f1e7b6bf38c2c7029e296648ea0e74cb22b10dd1fcb8": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de", "b60d493a7944ccf74c81f1e7b6bf38c2c7029e296648ea0e74cb22b10dd1fcb8": "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de",
"76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990": "d6cedce971a2a4ab2588e39285a9d2fb37b62924",
"dc6e31aae8daa9067dafcf2bca6d9d3aa691a63474eb698ad8c54be3e360753b": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10",
"f00797dd1ca5a077881fd47ac99febbd14930a5877d8aae99d85b2cd2ae7f57e": "9d4488178fe93d053d44f836e849d3d6b3e5974e", "f00797dd1ca5a077881fd47ac99febbd14930a5877d8aae99d85b2cd2ae7f57e": "9d4488178fe93d053d44f836e849d3d6b3e5974e",
"039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112": "c146b651696d25287241f4bab18226e488bc45b3", "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112": "c146b651696d25287241f4bab18226e488bc45b3",
"3ed66f7e82df79922739c6c2d34de90bc36a48d13acdff24f9d51013bd8c0677": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de", "3ed66f7e82df79922739c6c2d34de90bc36a48d13acdff24f9d51013bd8c0677": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
@@ -303178,6 +303269,9 @@
"bdff3e5659d67e67def05b33f749683b9376ae819d65d3dd62ac4640b3aaef48": "4d0f8dfc5980195ee905fa39e125cd418b8a9599", "bdff3e5659d67e67def05b33f749683b9376ae819d65d3dd62ac4640b3aaef48": "4d0f8dfc5980195ee905fa39e125cd418b8a9599",
"306b53ecfb182a504dd8a7446093c316387d2fd8dc350d0792ed1753fe0996cd": "e18b9aabbda0cc178436855ef8d04bce96533049", "306b53ecfb182a504dd8a7446093c316387d2fd8dc350d0792ed1753fe0996cd": "e18b9aabbda0cc178436855ef8d04bce96533049",
"cdefaf8efd47045f6820928eba84db5bed7557539328952b5f828315485e02ee": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c", "cdefaf8efd47045f6820928eba84db5bed7557539328952b5f828315485e02ee": "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c",
"d5558a0e0aa033482b098cc8a6449e78137f5069beabf421ab60d9eccbb62d8f": "2d9878fdd70979ecf6dd0da7652efd3797c26039",
"aa14a46908c1114e0c0f1c06accbfa7efc83c1768fb501699e043cbdb1af797f": "a3d939af27fe0929e61b8e20d49ef58316c0a66c",
"817eb2a258e44bb730b94ab77b00cac2480c2e27449078949e2897bad3cb3bc3": "81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"53e5ddc6825ecde7b502acb5fae3a173ba6696ec5b8bb7445f378f7b0ad1beb8": "cb0d4276f9947762af0841a8af75efa6cbe9109c", "53e5ddc6825ecde7b502acb5fae3a173ba6696ec5b8bb7445f378f7b0ad1beb8": "cb0d4276f9947762af0841a8af75efa6cbe9109c",
"fcb651c2ff39102a33ece3716579e3aee7d8b92d4700e52b56b6cf14baa8b8d1": "35910b67070342cdd6ec4d68eb2e56b39d350543", "fcb651c2ff39102a33ece3716579e3aee7d8b92d4700e52b56b6cf14baa8b8d1": "35910b67070342cdd6ec4d68eb2e56b39d350543",
"ea82ce9731de4a0b5af3e66e47e048af569e0e98c5739b32f268fb54caa7603a": "ca149058af89d8f612aaa529661e8267d9569875", "ea82ce9731de4a0b5af3e66e47e048af569e0e98c5739b32f268fb54caa7603a": "ca149058af89d8f612aaa529661e8267d9569875",
@@ -303185,6 +303279,8 @@
"654281fb002aa1a086ee979d694e9d6443864bdc8740d20188baf8e125e386fa": "c8eb9c1926baad8bbba25f97be54abae233fe715", "654281fb002aa1a086ee979d694e9d6443864bdc8740d20188baf8e125e386fa": "c8eb9c1926baad8bbba25f97be54abae233fe715",
"e31c519e6b22683cebefc5c2358b2b7ddad10964a1e478fa3eba01f3ab5adb17": "4abe45e59dc9286321a115c76c31cc7fe85e58d5", "e31c519e6b22683cebefc5c2358b2b7ddad10964a1e478fa3eba01f3ab5adb17": "4abe45e59dc9286321a115c76c31cc7fe85e58d5",
"15ff418a0fc877886015cc781c9c3b0810a870bee5803d76741bbcbcc540b9e5": "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564", "15ff418a0fc877886015cc781c9c3b0810a870bee5803d76741bbcbcc540b9e5": "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564",
"7194fda8019aeee6d74c70876bf39586996e594e5c85b53136d6cce3118e4a23": "f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"1f63be246613c1459891ea1cf9faca74161bf97a44ad75d305a035c319d478b3": "cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"ea3f65c6d1b18abe23b42a3d9ddf48d787df287feef6cfac52ca6109d8852d97": "005cecf1a5e20b05407d55d0aad22cf81ad66c31", "ea3f65c6d1b18abe23b42a3d9ddf48d787df287feef6cfac52ca6109d8852d97": "005cecf1a5e20b05407d55d0aad22cf81ad66c31",
"ac3d88c299e304faf76cda2b305510d5a6c19c06319e53ee54fdc18b028a860f": "705d20d39a910d58a070fb86780ac58c8007591c", "ac3d88c299e304faf76cda2b305510d5a6c19c06319e53ee54fdc18b028a860f": "705d20d39a910d58a070fb86780ac58c8007591c",
"2d765ffb2bef04082138e29e8ba7f7cc1c788c6415226d1aab1b7eb5bbd0431f": "276d984150c2fd2fb5ba287f2e5ceb7262dc5a95", "2d765ffb2bef04082138e29e8ba7f7cc1c788c6415226d1aab1b7eb5bbd0431f": "276d984150c2fd2fb5ba287f2e5ceb7262dc5a95",
@@ -303192,6 +303288,7 @@
"2e6883d4304c5de25c464a9fd97fcd2f642d8d2af2db9c2b13882e67f0f35b93": "808b7b46bfb033c7fe4d94d0dbf5a271de30dedc", "2e6883d4304c5de25c464a9fd97fcd2f642d8d2af2db9c2b13882e67f0f35b93": "808b7b46bfb033c7fe4d94d0dbf5a271de30dedc",
"2db47ffca9510468e37717266838576f96dccd9ed6b7f00c0a721c9868338b19": "a848aad1840c7d85bfcdd552521a7181b00331cc", "2db47ffca9510468e37717266838576f96dccd9ed6b7f00c0a721c9868338b19": "a848aad1840c7d85bfcdd552521a7181b00331cc",
"27ab08e76e08fa8a1023b076f3e6c23bc6841732658942f30cea634394f40729": "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5", "27ab08e76e08fa8a1023b076f3e6c23bc6841732658942f30cea634394f40729": "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5",
"db3c84c36729bd6a321566fae796f2ef3645f9889188f2af7e223d5f42d46755": "373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"deb7b69072b63f2916e74a44a8dc80d668f8010081009b667562f9f48203faae": "407f0fde89ac1a628fed0329072dfae1f8d304b1", "deb7b69072b63f2916e74a44a8dc80d668f8010081009b667562f9f48203faae": "407f0fde89ac1a628fed0329072dfae1f8d304b1",
"75093b914808931350c0ae0daa120ce1c12493cb4927a7e3698c8e492525a6ec": "f0168fbad73f8c0589c070f3e2071c1d62967a40", "75093b914808931350c0ae0daa120ce1c12493cb4927a7e3698c8e492525a6ec": "f0168fbad73f8c0589c070f3e2071c1d62967a40",
"10636657cd2e9b76da55e041007f9b34e08daa99affd932ef2d9a88ae0e7b1e9": "e17ecabba25dc797bf7a0e9979b18e5189e410e6", "10636657cd2e9b76da55e041007f9b34e08daa99affd932ef2d9a88ae0e7b1e9": "e17ecabba25dc797bf7a0e9979b18e5189e410e6",
@@ -305099,6 +305196,7 @@
"6d79dba0ad101cdaf3311dc70c94ecd163856236b6883cb242ed2cf4e07bfc80": "b41150f8cff409dde89939b80a980a20e853df0b", "6d79dba0ad101cdaf3311dc70c94ecd163856236b6883cb242ed2cf4e07bfc80": "b41150f8cff409dde89939b80a980a20e853df0b",
"400e334a2b49b14a9f585f3305d8e0f9a14c80e71680818abc9844efb91c92dc": "6c38cc6fba14863bfbdc730e53d3274fd79a4544", "400e334a2b49b14a9f585f3305d8e0f9a14c80e71680818abc9844efb91c92dc": "6c38cc6fba14863bfbdc730e53d3274fd79a4544",
"c128994d60990b4ecff6840c9f0117be52cec93fd3a1e3329e791ed25c1cb900": "cb6be8206308470ecaa8b9cc882d82abe0ef3562", "c128994d60990b4ecff6840c9f0117be52cec93fd3a1e3329e791ed25c1cb900": "cb6be8206308470ecaa8b9cc882d82abe0ef3562",
"a73b9f1c37d64532bf82d1d790a3e0cb81a9b2accb04d866901bb7ae1970473a": "681e3362f0251d2e26e6bc2e06157c027f5f3116",
"b4bbb22c62ce21e0447d74f3e6fd966b06c6ee16b629395021ea9f3d299f652a": "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22", "b4bbb22c62ce21e0447d74f3e6fd966b06c6ee16b629395021ea9f3d299f652a": "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22",
"59ca6249806f63805df4dddf997e7405b9d2e542ba71a9dad7d4fd6ee7d1bf2c": "0aaf20633888d2d0a2da38e82773cb5d44e840eb", "59ca6249806f63805df4dddf997e7405b9d2e542ba71a9dad7d4fd6ee7d1bf2c": "0aaf20633888d2d0a2da38e82773cb5d44e840eb",
"5d0a70211529b39745b6bfb7e9abf7cfa8fe7a61bf7882acdf712812aa9b2e70": "d27da38e4d37727a44bf04d7690a09135fad7115", "5d0a70211529b39745b6bfb7e9abf7cfa8fe7a61bf7882acdf712812aa9b2e70": "d27da38e4d37727a44bf04d7690a09135fad7115",
@@ -314459,24 +314557,6 @@
"switch/firmare installation.txt": [ "switch/firmare installation.txt": [
"05b91dc8dd6fdbbf982388fd0673073363be9ce6" "05b91dc8dd6fdbbf982388fd0673073363be9ce6"
], ],
"Arcade/Arcade/switch/prod.keys": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924"
],
"Arcade/switch/prod.keys": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924"
],
"switch/prod.keys": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924"
],
"Arcade/Arcade/switch/title.keys": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10"
],
"Arcade/switch/title.keys": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10"
],
"switch/title.keys": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10"
],
"Arcade/Arcade/sx1541.zip": [ "Arcade/Arcade/sx1541.zip": [
"1f424e6329bc1dfc254a60b752e0c2ad1d1f6126" "1f424e6329bc1dfc254a60b752e0c2ad1d1f6126"
], ],
@@ -466549,6 +466629,24 @@
"Super Game Boy/sgb_boot.bin": [ "Super Game Boy/sgb_boot.bin": [
"369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de" "369e6eb5e0c975eaa52a4a3f6ee07b2a3c3c16de"
], ],
"Nintendo/Switch/.variants/prod.keys.d6cedce9": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924"
],
"Switch/.variants/prod.keys.d6cedce9": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924"
],
".variants/prod.keys.d6cedce9": [
"d6cedce971a2a4ab2588e39285a9d2fb37b62924"
],
"Nintendo/Switch/.variants/title.keys.8e6c2873": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10"
],
"Switch/.variants/title.keys.8e6c2873": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10"
],
".variants/title.keys.8e6c2873": [
"8e6c2873d671f482d3fe0f7b96e18e0f84d02c10"
],
"Nintendo/Switch/Firmware_batocera43.zip": [ "Nintendo/Switch/Firmware_batocera43.zip": [
"9d4488178fe93d053d44f836e849d3d6b3e5974e" "9d4488178fe93d053d44f836e849d3d6b3e5974e"
], ],
@@ -472917,6 +473015,42 @@
"emucorex/pcsx2/resources/fonts/NotoSansHebrew-Regular.ttf": [ "emucorex/pcsx2/resources/fonts/NotoSansHebrew-Regular.ttf": [
"c549a2088bc9b678c8c79fa987ac5f4e6ff4772c" "c549a2088bc9b678c8c79fa987ac5f4e6ff4772c"
], ],
"Other/emucorex/resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039"
],
"emucorex/resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039"
],
"resources/GameIndex.yaml": [
"2d9878fdd70979ecf6dd0da7652efd3797c26039",
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"Other/emucorex/resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"emucorex/resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"resources/patches.zip": [
"a3d939af27fe0929e61b8e20d49ef58316c0a66c"
],
"Other/emucorex/resources/shaders/common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e"
],
"emucorex/resources/shaders/common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e"
],
"resources/shaders/common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e"
],
"shaders/common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a"
],
"common/fxaa.fx": [
"81c56a54bde692fb15fca4efe6db508b7d09bf6e",
"3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a"
],
"Other/emucorex/resources/shaders/opengl/cas.glsl": [ "Other/emucorex/resources/shaders/opengl/cas.glsl": [
"cb0d4276f9947762af0841a8af75efa6cbe9109c" "cb0d4276f9947762af0841a8af75efa6cbe9109c"
], ],
@@ -472959,6 +473093,46 @@
"emucorex/resources/shaders/opengl/shadeboost.glsl": [ "emucorex/resources/shaders/opengl/shadeboost.glsl": [
"03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564" "03c8b0d3ebeb0ecdc7f594ba6f7242a6f6e4d564"
], ],
"Other/emucorex/resources/shaders/opengl/tfx_fs.glsl": [
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528"
],
"emucorex/resources/shaders/opengl/tfx_fs.glsl": [
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528"
],
"resources/shaders/opengl/tfx_fs.glsl": [
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528"
],
"shaders/opengl/tfx_fs.glsl": [
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"00eb0418e7a25c89aaf7c526922fe45d83243ec5",
"919c3308c8850852d529d0127ce94e873375bfd2",
"554160d31bb97aca0e9f367f4de811dd8ad1fb21"
],
"opengl/tfx_fs.glsl": [
"f1c97200cb5b571a1ed6d33b5a2f854cebbfb528",
"00eb0418e7a25c89aaf7c526922fe45d83243ec5",
"919c3308c8850852d529d0127ce94e873375bfd2",
"554160d31bb97aca0e9f367f4de811dd8ad1fb21"
],
"Other/emucorex/resources/shaders/opengl/tfx_vgs.glsl": [
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943"
],
"emucorex/resources/shaders/opengl/tfx_vgs.glsl": [
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943"
],
"resources/shaders/opengl/tfx_vgs.glsl": [
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943"
],
"shaders/opengl/tfx_vgs.glsl": [
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"0e9e98f766c9250b465af776d0077f49f797fed2",
"fc40fc49de1cd8ed7d60df0394dd6367ae82b85f"
],
"opengl/tfx_vgs.glsl": [
"cb02fa6503183c313aa8d7c278bb9fbfe3d1a943",
"0e9e98f766c9250b465af776d0077f49f797fed2",
"fc40fc49de1cd8ed7d60df0394dd6367ae82b85f"
],
"Other/emucorex/resources/shaders/vulkan/cas.glsl": [ "Other/emucorex/resources/shaders/vulkan/cas.glsl": [
"005cecf1a5e20b05407d55d0aad22cf81ad66c31" "005cecf1a5e20b05407d55d0aad22cf81ad66c31"
], ],
@@ -473012,6 +473186,29 @@
"emucorex/resources/shaders/vulkan/shadeboost.glsl": [ "emucorex/resources/shaders/vulkan/shadeboost.glsl": [
"9b2704655a893ff1675d28df4bd9ffc9e15d3dd5" "9b2704655a893ff1675d28df4bd9ffc9e15d3dd5"
], ],
"Other/emucorex/resources/shaders/vulkan/tfx.glsl": [
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80"
],
"emucorex/resources/shaders/vulkan/tfx.glsl": [
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80"
],
"resources/shaders/vulkan/tfx.glsl": [
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80"
],
"shaders/vulkan/tfx.glsl": [
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"aa06cffa086126bb4972674562a5f5c8dc6082f3",
"1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329",
"b4ebf3f7f20fc88f91cda033552a7489a9ff179c",
"0a16f66d7816d73af3b28dea2bb292415695f6f7"
],
"vulkan/tfx.glsl": [
"373792e3bc3c89226bff1852dca3eaf5a8ae4a80",
"aa06cffa086126bb4972674562a5f5c8dc6082f3",
"1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329",
"b4ebf3f7f20fc88f91cda033552a7489a9ff179c",
"0a16f66d7816d73af3b28dea2bb292415695f6f7"
],
"Other/epsxe/.variants/libopenglplugin.so.0e506076": [ "Other/epsxe/.variants/libopenglplugin.so.0e506076": [
"407f0fde89ac1a628fed0329072dfae1f8d304b1" "407f0fde89ac1a628fed0329072dfae1f8d304b1"
], ],
@@ -477236,12 +477433,6 @@
"assets/shaders/common/fxaa.fx": [ "assets/shaders/common/fxaa.fx": [
"3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a" "3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a"
], ],
"shaders/common/fxaa.fx": [
"3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a"
],
"common/fxaa.fx": [
"3ac5f68049ed00d8ff3d2811bd09ef0c036efd0a"
],
"Other/nethersx2/assets/shaders/opengl/cas.glsl": [ "Other/nethersx2/assets/shaders/opengl/cas.glsl": [
"1a69a0fc522caff98fc3829ebf3f12a6a006a058" "1a69a0fc522caff98fc3829ebf3f12a6a006a058"
], ],
@@ -477325,16 +477516,6 @@
"00eb0418e7a25c89aaf7c526922fe45d83243ec5", "00eb0418e7a25c89aaf7c526922fe45d83243ec5",
"919c3308c8850852d529d0127ce94e873375bfd2" "919c3308c8850852d529d0127ce94e873375bfd2"
], ],
"shaders/opengl/tfx_fs.glsl": [
"00eb0418e7a25c89aaf7c526922fe45d83243ec5",
"919c3308c8850852d529d0127ce94e873375bfd2",
"554160d31bb97aca0e9f367f4de811dd8ad1fb21"
],
"opengl/tfx_fs.glsl": [
"00eb0418e7a25c89aaf7c526922fe45d83243ec5",
"919c3308c8850852d529d0127ce94e873375bfd2",
"554160d31bb97aca0e9f367f4de811dd8ad1fb21"
],
"Other/nethersx2/assets/shaders/opengl/tfx_vgs.glsl": [ "Other/nethersx2/assets/shaders/opengl/tfx_vgs.glsl": [
"0e9e98f766c9250b465af776d0077f49f797fed2" "0e9e98f766c9250b465af776d0077f49f797fed2"
], ],
@@ -477344,14 +477525,6 @@
"assets/shaders/opengl/tfx_vgs.glsl": [ "assets/shaders/opengl/tfx_vgs.glsl": [
"0e9e98f766c9250b465af776d0077f49f797fed2" "0e9e98f766c9250b465af776d0077f49f797fed2"
], ],
"shaders/opengl/tfx_vgs.glsl": [
"0e9e98f766c9250b465af776d0077f49f797fed2",
"fc40fc49de1cd8ed7d60df0394dd6367ae82b85f"
],
"opengl/tfx_vgs.glsl": [
"0e9e98f766c9250b465af776d0077f49f797fed2",
"fc40fc49de1cd8ed7d60df0394dd6367ae82b85f"
],
"Other/nethersx2/assets/shaders/vulkan/cas.glsl": [ "Other/nethersx2/assets/shaders/vulkan/cas.glsl": [
"709fd5393188f7cff0fe4230d0e3e8b69a82f5b9" "709fd5393188f7cff0fe4230d0e3e8b69a82f5b9"
], ],
@@ -477420,18 +477593,6 @@
"1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329", "1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329",
"b4ebf3f7f20fc88f91cda033552a7489a9ff179c" "b4ebf3f7f20fc88f91cda033552a7489a9ff179c"
], ],
"shaders/vulkan/tfx.glsl": [
"aa06cffa086126bb4972674562a5f5c8dc6082f3",
"1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329",
"b4ebf3f7f20fc88f91cda033552a7489a9ff179c",
"0a16f66d7816d73af3b28dea2bb292415695f6f7"
],
"vulkan/tfx.glsl": [
"aa06cffa086126bb4972674562a5f5c8dc6082f3",
"1c3a966a42cefcb9c26ce2fa77e4cd90f2d50329",
"b4ebf3f7f20fc88f91cda033552a7489a9ff179c",
"0a16f66d7816d73af3b28dea2bb292415695f6f7"
],
"Other/nethersx2/assets/sounds/achievements/unlock.wav": [ "Other/nethersx2/assets/sounds/achievements/unlock.wav": [
"eb488a76217629f620e40287567d7e2b30337680" "eb488a76217629f620e40287567d7e2b30337680"
], ],
@@ -494743,6 +494904,7 @@
], ],
"Config/Games.xml": [ "Config/Games.xml": [
"b41150f8cff409dde89939b80a980a20e853df0b", "b41150f8cff409dde89939b80a980a20e853df0b",
"681e3362f0251d2e26e6bc2e06157c027f5f3116",
"6871a47de90ca67f9ff8e4e280416851a8164563" "6871a47de90ca67f9ff8e4e280416851a8164563"
], ],
"Other/super3/Config/Supermodel.ini": [ "Other/super3/Config/Supermodel.ini": [
@@ -494763,6 +494925,12 @@
"Config/gamecontrollerdb.txt": [ "Config/gamecontrollerdb.txt": [
"cb6be8206308470ecaa8b9cc882d82abe0ef3562" "cb6be8206308470ecaa8b9cc882d82abe0ef3562"
], ],
"Other/supermodel-dojo/Config/Games.xml": [
"681e3362f0251d2e26e6bc2e06157c027f5f3116"
],
"supermodel-dojo/Config/Games.xml": [
"681e3362f0251d2e26e6bc2e06157c027f5f3116"
],
"Other/swf-player/assets/META-INF/AIR/application.xml": [ "Other/swf-player/assets/META-INF/AIR/application.xml": [
"bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22" "bc283d9ad8ad3bb99b632ca5b194a5bf27df1c22"
], ],
@@ -544215,9 +544383,6 @@
"PlayStation 2/resources/GameIndex.yaml": [ "PlayStation 2/resources/GameIndex.yaml": [
"40ecf6138c99a0aba775ef93240b295025a45500" "40ecf6138c99a0aba775ef93240b295025a45500"
], ],
"resources/GameIndex.yaml": [
"40ecf6138c99a0aba775ef93240b295025a45500"
],
"Sony/PlayStation 2/resources/sounds/achievements/message.wav": [ "Sony/PlayStation 2/resources/sounds/achievements/message.wav": [
"c54f04b63ac6f04e0a0944ef260b5497e4494dbd" "c54f04b63ac6f04e0a0944ef260b5497e4494dbd"
], ],
-39
View File
@@ -93,7 +93,6 @@ files:
- name: colecovision.rom - name: colecovision.rom
system: colecovision system: colecovision
required: true required: true
mode: standalone
sha256: "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad" sha256: "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad"
validation: [sha256] validation: [sha256]
description: "ColecoVision BIOS" description: "ColecoVision BIOS"
@@ -103,7 +102,6 @@ files:
- name: disksys.rom - name: disksys.rom
system: famicom-disk-system system: famicom-disk-system
required: true required: true
mode: standalone
sha256: "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72" sha256: "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72"
validation: [sha256] validation: [sha256]
description: "Famicom Disk System BIOS" description: "Famicom Disk System BIOS"
@@ -113,7 +111,6 @@ files:
- name: gba_bios.bin - name: gba_bios.bin
system: game-boy-advance system: game-boy-advance
required: true required: true
mode: standalone
sha256: "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570" sha256: "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570"
validation: [sha256] validation: [sha256]
description: "GBA BIOS" description: "GBA BIOS"
@@ -123,7 +120,6 @@ files:
- name: bios.gg - name: bios.gg
system: game-gear system: game-gear
required: false required: false
mode: standalone
sha256: "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2" sha256: "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2"
validation: [sha256] validation: [sha256]
description: "Game Gear BIOS" description: "Game Gear BIOS"
@@ -134,7 +130,6 @@ files:
system: master-system system: master-system
region: [north-america] region: [north-america]
required: false required: false
mode: standalone
sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50" sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50"
validation: [sha256] validation: [sha256]
description: "Master System BIOS (US)" description: "Master System BIOS (US)"
@@ -144,7 +139,6 @@ files:
system: master-system system: master-system
region: [japan] region: [japan]
required: false required: false
mode: standalone
sha256: "67846e26764bd862f19179294347f7353a4166b62ac4198a5ec32933b7da486e" sha256: "67846e26764bd862f19179294347f7353a4166b62ac4198a5ec32933b7da486e"
validation: [sha256] validation: [sha256]
description: "Master System BIOS (Japan)" description: "Master System BIOS (Japan)"
@@ -154,7 +148,6 @@ files:
system: master-system system: master-system
region: [europe] region: [europe]
required: false required: false
mode: standalone
sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50" sha256: "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50"
validation: [sha256] validation: [sha256]
description: "Master System BIOS (Europe), same binary as US" description: "Master System BIOS (Europe), same binary as US"
@@ -166,7 +159,6 @@ files:
system: mega-cd system: mega-cd
region: [north-america] region: [north-america]
required: true required: true
mode: standalone
sha256: "fb477cdbf94c84424c2feca4fe40656d85393fe7b7b401911b45ad2eb991258c" sha256: "fb477cdbf94c84424c2feca4fe40656d85393fe7b7b401911b45ad2eb991258c"
validation: [sha256] validation: [sha256]
description: "Mega CD / Sega CD BIOS (US)" description: "Mega CD / Sega CD BIOS (US)"
@@ -176,7 +168,6 @@ files:
system: mega-cd system: mega-cd
region: [japan] region: [japan]
required: true required: true
mode: standalone
sha256: "7133fc2dd2fe5b7d0acd53a5f10f3d00b5d31270239ad20d74ef32393e24af88" sha256: "7133fc2dd2fe5b7d0acd53a5f10f3d00b5d31270239ad20d74ef32393e24af88"
validation: [sha256] validation: [sha256]
description: "Mega CD BIOS (Japan)" description: "Mega CD BIOS (Japan)"
@@ -186,7 +177,6 @@ files:
system: mega-cd system: mega-cd
region: [europe] region: [europe]
required: true required: true
mode: standalone
sha256: "fe608a2a07676a23ab5fd5eee2f53c9e2526d69a28aa16ccd85c0ec42e6933cb" sha256: "fe608a2a07676a23ab5fd5eee2f53c9e2526d69a28aa16ccd85c0ec42e6933cb"
validation: [sha256] validation: [sha256]
description: "Mega CD BIOS (Europe)" description: "Mega CD BIOS (Europe)"
@@ -197,7 +187,6 @@ files:
system: mega-ld system: mega-ld
region: [north-america] region: [north-america]
required: true required: true
mode: standalone
sha256: "e89b5a319f66406611ec82fe5c4aa6827c175a05135bd7bd177366cba0465021" sha256: "e89b5a319f66406611ec82fe5c4aa6827c175a05135bd7bd177366cba0465021"
validation: [sha256] validation: [sha256]
description: "LaserActive SEGA PAC BIOS (US), v1.04" description: "LaserActive SEGA PAC BIOS (US), v1.04"
@@ -207,7 +196,6 @@ files:
system: mega-ld system: mega-ld
region: [japan] region: [japan]
required: true required: true
mode: standalone
sha256: "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94" sha256: "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94"
validation: [sha256] validation: [sha256]
description: "LaserActive SEGA PAC BIOS (Japan), v1.02" description: "LaserActive SEGA PAC BIOS (Japan), v1.02"
@@ -217,7 +205,6 @@ files:
- name: MSX.ROM - name: MSX.ROM
system: msx system: msx
required: true required: true
mode: standalone
sha256: "413a2b601a94b3792e054be2439cc77a1819cceadbfa9542f88d51c7480f2ef0" sha256: "413a2b601a94b3792e054be2439cc77a1819cceadbfa9542f88d51c7480f2ef0"
validation: [sha256] validation: [sha256]
description: "MSX BIOS ROM (Japan)" description: "MSX BIOS ROM (Japan)"
@@ -227,7 +214,6 @@ files:
- name: MSX2.ROM - name: MSX2.ROM
system: msx2 system: msx2
required: true required: true
mode: standalone
sha256: "0c672d86ead61a97f49a583b88b7c1905da120645cd44f0c9f2baf4f4631e0b1" sha256: "0c672d86ead61a97f49a583b88b7c1905da120645cd44f0c9f2baf4f4631e0b1"
validation: [sha256] validation: [sha256]
description: "MSX2 main BIOS ROM (Japan)" description: "MSX2 main BIOS ROM (Japan)"
@@ -236,7 +222,6 @@ files:
- name: MSX2EXT.ROM - name: MSX2EXT.ROM
system: msx2 system: msx2
required: true required: true
mode: standalone
sha256: "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300" sha256: "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300"
validation: [sha256] validation: [sha256]
description: "MSX2 sub ROM (Japan)" description: "MSX2 sub ROM (Japan)"
@@ -246,7 +231,6 @@ files:
- name: neo-epo.bin - name: neo-epo.bin
system: neo-geo system: neo-geo
required: true required: true
mode: standalone
description: "Neo Geo AES BIOS. Accepts neogeo.zip (extracts neo-epo.bin) or direct file. Byte-swapped on load." description: "Neo Geo AES BIOS. Accepts neogeo.zip (extracts neo-epo.bin) or direct file. Byte-swapped on load."
source_ref: "desktop-ui/emulator/neo-geo-aes.cpp:14, mia/system/neo-geo-aes.cpp:16" source_ref: "desktop-ui/emulator/neo-geo-aes.cpp:14, mia/system/neo-geo-aes.cpp:16"
@@ -254,7 +238,6 @@ files:
- name: sp-45.sp1 - name: sp-45.sp1
system: neo-geo system: neo-geo
required: true required: true
mode: standalone
description: "Neo Geo MVS BIOS. Accepts neogeo.zip (extracts sp-45.sp1) or direct file. Byte-swapped on load." description: "Neo Geo MVS BIOS. Accepts neogeo.zip (extracts sp-45.sp1) or direct file. Byte-swapped on load."
source_ref: "desktop-ui/emulator/neo-geo-mvs.cpp:15, mia/system/neo-geo-mvs.cpp:16" source_ref: "desktop-ui/emulator/neo-geo-mvs.cpp:15, mia/system/neo-geo-mvs.cpp:16"
@@ -262,7 +245,6 @@ files:
- name: ngp_bios.rom - name: ngp_bios.rom
system: neo-geo-pocket system: neo-geo-pocket
required: true required: true
mode: standalone
sha256: "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7" sha256: "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7"
validation: [sha256] validation: [sha256]
description: "Neo Geo Pocket BIOS" description: "Neo Geo Pocket BIOS"
@@ -272,7 +254,6 @@ files:
- name: ngpc_bios.rom - name: ngpc_bios.rom
system: neo-geo-pocket-color system: neo-geo-pocket-color
required: true required: true
mode: standalone
sha256: "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d" sha256: "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d"
validation: [sha256] validation: [sha256]
description: "Neo Geo Pocket Color BIOS" description: "Neo Geo Pocket Color BIOS"
@@ -283,7 +264,6 @@ files:
system: nintendo-64dd system: nintendo-64dd
region: [japan] region: [japan]
required: true required: true
mode: standalone
sha256: "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b" sha256: "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b"
validation: [sha256] validation: [sha256]
description: "Nintendo 64DD IPL ROM (Japan retail)" description: "Nintendo 64DD IPL ROM (Japan retail)"
@@ -293,7 +273,6 @@ files:
system: nintendo-64dd system: nintendo-64dd
region: [north-america] region: [north-america]
required: true required: true
mode: standalone
sha256: "e9fec87a45fba02399e88064b9e2f8cf0f2106e351c58279a87f05da5bc984ad" sha256: "e9fec87a45fba02399e88064b9e2f8cf0f2106e351c58279a87f05da5bc984ad"
validation: [sha256] validation: [sha256]
description: "Nintendo 64DD IPL ROM (US dev kit)" description: "Nintendo 64DD IPL ROM (US dev kit)"
@@ -302,7 +281,6 @@ files:
- name: 64dd_ipl_DEV.bin - name: 64dd_ipl_DEV.bin
system: nintendo-64dd system: nintendo-64dd
required: true required: true
mode: standalone
sha256: "9c2962a8b994a29e4cd04b3a6e4ed730a751414655ab6a9799ebf5fc08b79d44" sha256: "9c2962a8b994a29e4cd04b3a6e4ed730a751414655ab6a9799ebf5fc08b79d44"
validation: [sha256] validation: [sha256]
description: "Nintendo 64DD IPL ROM (development)" description: "Nintendo 64DD IPL ROM (development)"
@@ -312,7 +290,6 @@ files:
- name: syscard1.pce - name: syscard1.pce
system: pc-engine-cd system: pc-engine-cd
required: true required: true
mode: standalone
sha256: "afe9f27f91ac918348555b86298b4f984643eafa2773196f2c5441ea84f0c3bb" sha256: "afe9f27f91ac918348555b86298b4f984643eafa2773196f2c5441ea84f0c3bb"
validation: [sha256] validation: [sha256]
description: "PC Engine CD-ROM2 System Card v1.0 (Japan). Also used by PC Engine LD." description: "PC Engine CD-ROM2 System Card v1.0 (Japan). Also used by PC Engine LD."
@@ -321,7 +298,6 @@ files:
- name: syscard3.pce - name: syscard3.pce
system: pc-engine-cd system: pc-engine-cd
required: true required: true
mode: standalone
sha256: "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce" sha256: "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce"
validation: [sha256] validation: [sha256]
description: "Super CD-ROM2 System Card v3.0 (Japan). Also used by SuperGrafx CD as Arcade Card." description: "Super CD-ROM2 System Card v3.0 (Japan). Also used by SuperGrafx CD as Arcade Card."
@@ -330,7 +306,6 @@ files:
- name: syscard3u.pce - name: syscard3u.pce
system: pc-engine-cd system: pc-engine-cd
required: true required: true
mode: standalone
sha256: "cadac2725711b3c442bcf237b02f5a5210c96f17625c35fa58f009e0ed39e4db" sha256: "cadac2725711b3c442bcf237b02f5a5210c96f17625c35fa58f009e0ed39e4db"
validation: [sha256] validation: [sha256]
description: "TurboGrafx-CD Super System Card v3.0 (US)" description: "TurboGrafx-CD Super System Card v3.0 (US)"
@@ -339,7 +314,6 @@ files:
- name: games_express.pce - name: games_express.pce
system: pc-engine-cd system: pc-engine-cd
required: true required: true
mode: standalone
sha256: "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d" sha256: "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d"
validation: [sha256] validation: [sha256]
description: "Games Express CD Card (Japan). Also used by PC Engine LD." description: "Games Express CD Card (Japan). Also used by PC Engine LD."
@@ -349,7 +323,6 @@ files:
- name: pac-n10.pce - name: pac-n10.pce
system: pc-engine-ld system: pc-engine-ld
required: true required: true
mode: standalone
sha256: "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4" sha256: "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4"
validation: [sha256] validation: [sha256]
description: "NEC PAC-N10 LaserActive module (US)" description: "NEC PAC-N10 LaserActive module (US)"
@@ -358,7 +331,6 @@ files:
- name: pac-n1.pce - name: pac-n1.pce
system: pc-engine-ld system: pc-engine-ld
required: true required: true
mode: standalone
sha256: "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f" sha256: "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f"
validation: [sha256] validation: [sha256]
description: "NEC PAC-N1 LaserActive module (Japan)" description: "NEC PAC-N1 LaserActive module (Japan)"
@@ -367,7 +339,6 @@ files:
- name: pce-lp1.pce - name: pce-lp1.pce
system: pc-engine-ld system: pc-engine-ld
required: true required: true
mode: standalone
sha256: "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0" sha256: "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0"
validation: [sha256] validation: [sha256]
description: "NEC PCE-LP1 LaserActive module (Japan)" description: "NEC PCE-LP1 LaserActive module (Japan)"
@@ -377,7 +348,6 @@ files:
- name: scph5501.bin - name: scph5501.bin
system: sony-playstation system: sony-playstation
required: true required: true
mode: standalone
sha256: "11052b6499e466bbf0a709b1f9cb6834a9418e66680387912451e971cf8a1fef" sha256: "11052b6499e466bbf0a709b1f9cb6834a9418e66680387912451e971cf8a1fef"
validation: [sha256] validation: [sha256]
description: "PlayStation BIOS (US)" description: "PlayStation BIOS (US)"
@@ -386,7 +356,6 @@ files:
- name: scph5500.bin - name: scph5500.bin
system: sony-playstation system: sony-playstation
required: true required: true
mode: standalone
sha256: "9c0421858e217805f4abe18698afea8d5aa36ff0727eb8484944e00eb5e7eadb" sha256: "9c0421858e217805f4abe18698afea8d5aa36ff0727eb8484944e00eb5e7eadb"
validation: [sha256] validation: [sha256]
description: "PlayStation BIOS (Japan)" description: "PlayStation BIOS (Japan)"
@@ -395,7 +364,6 @@ files:
- name: scph5502.bin - name: scph5502.bin
system: sony-playstation system: sony-playstation
required: true required: true
mode: standalone
sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09" sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09"
validation: [sha256] validation: [sha256]
description: "PlayStation BIOS (Europe)" description: "PlayStation BIOS (Europe)"
@@ -406,7 +374,6 @@ files:
system: saturn system: saturn
region: [north-america] region: [north-america]
required: true required: true
mode: standalone
description: "Sega Saturn BIOS (US)" description: "Sega Saturn BIOS (US)"
source_ref: "desktop-ui/emulator/saturn.cpp:14" source_ref: "desktop-ui/emulator/saturn.cpp:14"
@@ -414,7 +381,6 @@ files:
system: saturn system: saturn
region: [japan] region: [japan]
required: true required: true
mode: standalone
description: "Sega Saturn BIOS (Japan)" description: "Sega Saturn BIOS (Japan)"
source_ref: "desktop-ui/emulator/saturn.cpp:15" source_ref: "desktop-ui/emulator/saturn.cpp:15"
@@ -422,7 +388,6 @@ files:
system: saturn system: saturn
region: [europe] region: [europe]
required: true required: true
mode: standalone
description: "Sega Saturn BIOS (Europe)" description: "Sega Saturn BIOS (Europe)"
source_ref: "desktop-ui/emulator/saturn.cpp:16" source_ref: "desktop-ui/emulator/saturn.cpp:16"
@@ -430,7 +395,6 @@ files:
- name: SGB1.sfc - name: SGB1.sfc
system: super-famicom system: super-famicom
required: false required: false
mode: standalone
category: game_data category: game_data
description: "Super Game Boy cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game." description: "Super Game Boy cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game."
source_ref: "EmuDeck emuDeckares.sh (launcher)" source_ref: "EmuDeck emuDeckares.sh (launcher)"
@@ -438,7 +402,6 @@ files:
- name: SGB2.sfc - name: SGB2.sfc
system: super-famicom system: super-famicom
required: false required: false
mode: standalone
category: game_data category: game_data
description: "Super Game Boy 2 cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game." description: "Super Game Boy 2 cartridge ROM. Loaded via CLI as SFC cartridge alongside GB game."
source_ref: "EmuDeck emuDeckares.sh (launcher)" source_ref: "EmuDeck emuDeckares.sh (launcher)"
@@ -446,7 +409,6 @@ files:
- name: STBIOS.bin - name: STBIOS.bin
system: super-famicom system: super-famicom
required: false required: false
mode: standalone
category: game_data category: game_data
description: "SuFami Turbo adapter BIOS. Loaded via CLI as SFC cartridge alongside SuFami games." description: "SuFami Turbo adapter BIOS. Loaded via CLI as SFC cartridge alongside SuFami games."
source_ref: "EmuDeck emuDeckares.sh (launcher)" source_ref: "EmuDeck emuDeckares.sh (launcher)"
@@ -454,7 +416,6 @@ files:
- name: BS-X.bin - name: BS-X.bin
system: super-famicom system: super-famicom
required: false required: false
mode: standalone
category: game_data category: game_data
description: "Satellaview BS-X Town cartridge ROM. Loaded via CLI as SFC cartridge for BS games." description: "Satellaview BS-X Town cartridge ROM. Loaded via CLI as SFC cartridge for BS games."
source_ref: "EmuDeck emuDeckares.sh (launcher)" source_ref: "EmuDeck emuDeckares.sh (launcher)"
-3
View File
@@ -101,7 +101,6 @@ files:
- name: "psxonpsp660.bin" - name: "psxonpsp660.bin"
description: "PSP embedded PS1 BIOS (region-free override)" description: "PSP embedded PS1 BIOS (region-free override)"
region: [world]
required: false required: false
size: 524288 size: 524288
sha1: "96880d1ca92a016ff054be5159bb06fe03cb4e14" sha1: "96880d1ca92a016ff054be5159bb06fe03cb4e14"
@@ -114,7 +113,6 @@ files:
- name: "ps1_rom.bin" - name: "ps1_rom.bin"
description: "PS3 embedded PS1 BIOS, first 512KB extracted (region-free override)" description: "PS3 embedded PS1 BIOS, first 512KB extracted (region-free override)"
region: [world]
required: false required: false
sha1: "c40146361eb8cf670b19fdc9759190257803cab7" sha1: "c40146361eb8cf670b19fdc9759190257803cab7"
md5: "81bbe60ba7a3d1cea1d48c14cbcc647b" md5: "81bbe60ba7a3d1cea1d48c14cbcc647b"
@@ -126,7 +124,6 @@ files:
- name: "openbios.bin" - name: "openbios.bin"
description: "OpenBIOS (open-source PS1 HLE BIOS, region-free override)" description: "OpenBIOS (open-source PS1 HLE BIOS, region-free override)"
region: [world]
required: false required: false
size: 524288 size: 524288
mode: libretro mode: libretro
+6 -5
View File
@@ -14,13 +14,14 @@ notes: >
Doom 3 source port based on dhewm3 (GPL Doom 3 engine). The core accepts Doom 3 source port based on dhewm3 (GPL Doom 3 engine). The core accepts
.pk4 extensions (retro_get_system_info sets valid_extensions = "pk4"). .pk4 extensions (retro_get_system_info sets valid_extensions = "pk4").
Content is loaded directly from the .pk4 file path - the engine extracts Content is loaded directly from the .pk4 file path - the engine extracts
the grandparent directory as fs_basepath (libretro.cpp:1097,1135-1136 the grandparent directory as fs_basepath (libretro.cpp:2381,2449-2450
via extract_directory called twice on info->path) and expects the standard via extract_directory called twice on info->path) and expects the standard
Doom 3 directory layout with base/ containing pak000.pk4 through pak008.pk4. Doom 3 directory layout with base/ containing pak000.pk4 through pak008.pk4.
BASE_GAMEDIR is hardcoded to "base" (Licensee.h:58). The boom3_xp variant BASE_GAMEDIR is hardcoded to "base" (Licensee.h:58). One binary serves both
compiles with _D3XP defined, which passes "+set fs_game d3xp" at startup games: content in a directory named d3xp, or the doom_game core option,
(libretro.cpp:538-544) to load the Resurrection of Evil expansion from a passes "+set fs_game d3xp" (libretro.cpp:1635-1643, 2384-2405) to load the
d3xp/ subdirectory alongside base/. The filesystem code (FileSystem.cpp:2125) Resurrection of Evil expansion from a d3xp/ subdirectory alongside base/.
The filesystem code (FileSystem.cpp:2207)
scans for .pk4 files in the game directory and loads them in order, with scans for .pk4 files in the game directory and loads them in order, with
higher-numbered paks overriding lower ones. The core requires OpenGL higher-numbered paks overriding lower ones. The core requires OpenGL
(RETRO_ENVIRONMENT_SET_HW_RENDER) and XRGB8888 pixel format. All required (RETRO_ENVIRONMENT_SET_HW_RENDER) and XRGB8888 pixel format. All required
-4
View File
@@ -25,21 +25,18 @@ files:
- name: "prod.keys" - name: "prod.keys"
required: true required: true
path: "switch/" path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)" note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:655-656" source_ref: "src/core/crypto/key_manager.cpp:655-656"
- name: "title.keys" - name: "title.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)" note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:659-660" source_ref: "src/core/crypto/key_manager.cpp:659-660"
- name: "console.keys" - name: "console.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)" note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:661-662" source_ref: "src/core/crypto/key_manager.cpp:661-662"
@@ -47,7 +44,6 @@ files:
required: false required: false
path: "switch/" path: "switch/"
size: 160 size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)" note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:274-292" source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:274-292"
-118
View File
@@ -62,7 +62,6 @@ files:
size: 2097152 size: 2097152
crc32: "54c0c963" crc32: "54c0c963"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:58-65" source_ref: "Machines/Utility/ROMCatalogue.cpp:58-65"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -75,7 +74,6 @@ files:
crc32: "79434781" crc32: "79434781"
required: true required: true
aliases: [basic2.rom] aliases: [basic2.rom]
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:78-85" source_ref: "Machines/Utility/ROMCatalogue.cpp:78-85"
- name: os12.rom - name: os12.rom
@@ -84,7 +82,6 @@ files:
size: 16384 size: 16384
crc32: "3c14fc70" crc32: "3c14fc70"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:436-443" source_ref: "Machines/Utility/ROMCatalogue.cpp:436-443"
- name: dfs09.rom - name: dfs09.rom
@@ -94,7 +91,6 @@ files:
crc32: "3ce609cf" crc32: "3ce609cf"
required: false required: false
note: "Conditional: only when using 8271 disk interface" note: "Conditional: only when using 8271 disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:444-451" source_ref: "Machines/Utility/ROMCatalogue.cpp:444-451"
- name: dfs-2.26.rom - name: dfs-2.26.rom
@@ -104,7 +100,6 @@ files:
crc32: "5ae33e94" crc32: "5ae33e94"
required: false required: false
note: "Conditional: needed for 1770 DFS or tube processor" note: "Conditional: needed for 1770 DFS or tube processor"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:452-459" source_ref: "Machines/Utility/ROMCatalogue.cpp:452-459"
- name: adfs-1.30.rom - name: adfs-1.30.rom
@@ -114,7 +109,6 @@ files:
crc32: "d3855588" crc32: "d3855588"
required: false required: false
note: "Conditional: needed for ADFS support" note: "Conditional: needed for ADFS support"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:460-467" source_ref: "Machines/Utility/ROMCatalogue.cpp:460-467"
- name: ADT-1.40.rom - name: ADT-1.40.rom
@@ -124,7 +118,6 @@ files:
crc32: "8314fed0" crc32: "8314fed0"
required: false required: false
note: "Optional: fetched separately, silently skipped if absent" note: "Optional: fetched separately, silently skipped if absent"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:468-475" source_ref: "Machines/Utility/ROMCatalogue.cpp:468-475"
- name: TUBE110.rom - name: TUBE110.rom
@@ -134,7 +127,6 @@ files:
crc32: "9ec2dbd0" crc32: "9ec2dbd0"
required: false required: false
note: "Conditional: needed for 6502 tube processor" note: "Conditional: needed for 6502 tube processor"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:476-483" source_ref: "Machines/Utility/ROMCatalogue.cpp:476-483"
- name: Z80_122.rom - name: Z80_122.rom
@@ -144,7 +136,6 @@ files:
crc32: "b5851efc" crc32: "b5851efc"
required: false required: false
note: "Conditional: needed for Z80 tube processor" note: "Conditional: needed for Z80 tube processor"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:484-491" source_ref: "Machines/Utility/ROMCatalogue.cpp:484-491"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -156,7 +147,6 @@ files:
size: 16384 size: 16384
crc32: "79434781" crc32: "79434781"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:90-97" source_ref: "Machines/Utility/ROMCatalogue.cpp:90-97"
- name: os.rom - name: os.rom
@@ -165,7 +155,6 @@ files:
size: 16384 size: 16384
crc32: "bf63fb1f" crc32: "bf63fb1f"
required: true required: true
mode: standalone
note: > note: >
The catalogue accepts five images: 406a42ce, 0886741b, bf63fb1f, 6fea0978 The catalogue accepts five images: 406a42ce, 0886741b, bf63fb1f, 6fea0978
and d61135d7. They differ only in the credits text, which lives on the IO and d61135d7. They differ only in the credits text, which lives on the IO
@@ -179,7 +168,6 @@ files:
crc32: "51523993" crc32: "51523993"
required: false required: false
note: "Conditional: needed for PRES ADFS" note: "Conditional: needed for PRES ADFS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:98-105" source_ref: "Machines/Utility/ROMCatalogue.cpp:98-105"
- name: ADFS-E00_2.rom - name: ADFS-E00_2.rom
@@ -189,7 +177,6 @@ files:
crc32: "8d17de0e" crc32: "8d17de0e"
required: false required: false
note: "Conditional: needed for PRES ADFS" note: "Conditional: needed for PRES ADFS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:106-113" source_ref: "Machines/Utility/ROMCatalogue.cpp:106-113"
- name: adfs.rom - name: adfs.rom
@@ -199,7 +186,6 @@ files:
crc32: "3289bdc6" crc32: "3289bdc6"
required: false required: false
note: "Conditional: needed for Acorn ADFS" note: "Conditional: needed for Acorn ADFS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:114-121" source_ref: "Machines/Utility/ROMCatalogue.cpp:114-121"
- name: DFS-1770-2.20.rom - name: DFS-1770-2.20.rom
@@ -209,7 +195,6 @@ files:
crc32: "f3dc9bc5" crc32: "f3dc9bc5"
required: false required: false
note: "Conditional: needed for DFS disk images" note: "Conditional: needed for DFS disk images"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:122-129" source_ref: "Machines/Utility/ROMCatalogue.cpp:122-129"
- name: ELK130E.rom - name: ELK130E.rom
@@ -219,7 +204,6 @@ files:
crc32: "a923368d" crc32: "a923368d"
required: false required: false
note: "Conditional: needed for IDE interface" note: "Conditional: needed for IDE interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:130-137" source_ref: "Machines/Utility/ROMCatalogue.cpp:130-137"
- name: AP6v133.rom - name: AP6v133.rom
@@ -229,7 +213,6 @@ files:
crc32: "e0013cfc" crc32: "e0013cfc"
required: false required: false
note: "Conditional: needed for AP6 commands. The catalogue records crc32 e0013cfc, which is the 7772-byte RetroHardware Plus 1 Support 1.33c build of 16 Jun 2020, and declares 8 KiB: Request::Node::validate() resizes the loaded image to 8192 bytes and the fetcher reads any length." note: "Conditional: needed for AP6 commands. The catalogue records crc32 e0013cfc, which is the 7772-byte RetroHardware Plus 1 Support 1.33c build of 16 Jun 2020, and declares 8 KiB: Request::Node::validate() resizes the loaded image to 8192 bytes and the fetcher reads any length."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:138-145, Machines/Utility/ROMCatalogue.cpp:1484-1499, OSBindings/SDL/main.cpp:756-783" source_ref: "Machines/Utility/ROMCatalogue.cpp:138-145, Machines/Utility/ROMCatalogue.cpp:1484-1499, OSBindings/SDL/main.cpp:756-783"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -241,7 +224,6 @@ files:
size: 262144 size: 262144
crc32: "c4f0f55f" crc32: "c4f0f55f"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:193-200" source_ref: "Machines/Utility/ROMCatalogue.cpp:193-200"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -255,7 +237,6 @@ files:
crc32: "815752df" crc32: "815752df"
required: false required: false
note: "Built into binary. Required for CPC 464 model." note: "Built into binary. Required for CPC 464 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:261-268, Machines/Utility/ROMLibrary.cpp:14535" source_ref: "Machines/Utility/ROMCatalogue.cpp:261-268, Machines/Utility/ROMLibrary.cpp:14535"
- name: basic464.rom - name: basic464.rom
@@ -265,7 +246,6 @@ files:
crc32: "7d9a3bac" crc32: "7d9a3bac"
required: false required: false
note: "Built into binary. Required for CPC 464 model." note: "Built into binary. Required for CPC 464 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:269-276, Machines/Utility/ROMLibrary.cpp:14532" source_ref: "Machines/Utility/ROMCatalogue.cpp:269-276, Machines/Utility/ROMLibrary.cpp:14532"
- name: os664.rom - name: os664.rom
@@ -275,7 +255,6 @@ files:
crc32: "3f5a6dc4" crc32: "3f5a6dc4"
required: false required: false
note: "Built into binary. Required for CPC 664 model." note: "Built into binary. Required for CPC 664 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:277-283, Machines/Utility/ROMLibrary.cpp:14537" source_ref: "Machines/Utility/ROMCatalogue.cpp:277-283, Machines/Utility/ROMLibrary.cpp:14537"
- name: basic664.rom - name: basic664.rom
@@ -285,7 +264,6 @@ files:
crc32: "32fee492" crc32: "32fee492"
required: false required: false
note: "Built into binary. Required for CPC 664 model." note: "Built into binary. Required for CPC 664 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:284-291, Machines/Utility/ROMLibrary.cpp:14534" source_ref: "Machines/Utility/ROMCatalogue.cpp:284-291, Machines/Utility/ROMLibrary.cpp:14534"
- name: os6128.rom - name: os6128.rom
@@ -295,7 +273,6 @@ files:
crc32: "0219bb74" crc32: "0219bb74"
required: false required: false
note: "Built into binary. Required for CPC 6128 model." note: "Built into binary. Required for CPC 6128 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:292-299, Machines/Utility/ROMLibrary.cpp:14536" source_ref: "Machines/Utility/ROMCatalogue.cpp:292-299, Machines/Utility/ROMLibrary.cpp:14536"
- name: basic6128.rom - name: basic6128.rom
@@ -305,7 +282,6 @@ files:
crc32: "ca6af63d" crc32: "ca6af63d"
required: false required: false
note: "Built into binary. Required for CPC 6128 model." note: "Built into binary. Required for CPC 6128 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:300-307, Machines/Utility/ROMLibrary.cpp:14533" source_ref: "Machines/Utility/ROMCatalogue.cpp:300-307, Machines/Utility/ROMLibrary.cpp:14533"
- name: amsdos.rom - name: amsdos.rom
@@ -315,7 +291,6 @@ files:
crc32: "1fe22ecd" crc32: "1fe22ecd"
required: false required: false
note: "Built into binary. Needed for CPC 664/6128 disk support." note: "Built into binary. Needed for CPC 664/6128 disk support."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:253-260, Machines/Utility/ROMLibrary.cpp:14531" source_ref: "Machines/Utility/ROMCatalogue.cpp:253-260, Machines/Utility/ROMLibrary.cpp:14531"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -328,7 +303,6 @@ files:
crc32: "ba210588" crc32: "ba210588"
required: true required: true
note: "Required for original Apple II model" note: "Required for original Apple II model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:336-343" source_ref: "Machines/Utility/ROMCatalogue.cpp:336-343"
- name: apple2.rom - name: apple2.rom
@@ -338,7 +312,6 @@ files:
crc32: "f66f9c26" crc32: "f66f9c26"
required: true required: true
note: "Required for Apple II+ model" note: "Required for Apple II+ model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:328-335" source_ref: "Machines/Utility/ROMCatalogue.cpp:328-335"
- name: apple2eu.rom - name: apple2eu.rom
@@ -348,7 +321,6 @@ files:
crc32: "e12be18d" crc32: "e12be18d"
required: true required: true
note: "Required for Apple IIe model" note: "Required for Apple IIe model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:320-327" source_ref: "Machines/Utility/ROMCatalogue.cpp:320-327"
- name: apple2e.rom - name: apple2e.rom
@@ -358,7 +330,6 @@ files:
crc32: "65989942" crc32: "65989942"
required: true required: true
note: "Required for Enhanced IIe model" note: "Required for Enhanced IIe model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:312-319" source_ref: "Machines/Utility/ROMCatalogue.cpp:312-319"
- name: apple2-character.rom - name: apple2-character.rom
@@ -368,7 +339,6 @@ files:
crc32: "64f415c6" crc32: "64f415c6"
required: true required: true
note: "Required for Apple II and II+ models" note: "Required for Apple II and II+ models"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:344-351" source_ref: "Machines/Utility/ROMCatalogue.cpp:344-351"
- name: apple2eu-character.rom - name: apple2eu-character.rom
@@ -378,7 +348,6 @@ files:
crc32: "816a86f1" crc32: "816a86f1"
required: true required: true
note: "Required for Apple IIe model" note: "Required for Apple IIe model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:352-359" source_ref: "Machines/Utility/ROMCatalogue.cpp:352-359"
- name: apple2e-character.rom - name: apple2e-character.rom
@@ -388,7 +357,6 @@ files:
crc32: "2651014d" crc32: "2651014d"
required: true required: true
note: "Required for Enhanced IIe model. Also used by Apple IIgs." note: "Required for Enhanced IIe model. Also used by Apple IIgs."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:360-367" source_ref: "Machines/Utility/ROMCatalogue.cpp:360-367"
- name: scsi.rom - name: scsi.rom
@@ -398,7 +366,6 @@ files:
crc32: "5aff85d3" crc32: "5aff85d3"
required: false required: false
note: "Conditional: needed for SCSI controller" note: "Conditional: needed for SCSI controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:368-375" source_ref: "Machines/Utility/ROMCatalogue.cpp:368-375"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -411,7 +378,6 @@ files:
crc32: "42f124b0" crc32: "42f124b0"
required: true required: true
note: "Required for ROM01 model" note: "Required for ROM01 model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:380-387" source_ref: "Machines/Utility/ROMCatalogue.cpp:380-387"
- name: apple2gs.rom2 - name: apple2gs.rom2
@@ -421,7 +387,6 @@ files:
crc32: "de7ddf29" crc32: "de7ddf29"
required: true required: true
note: "Required for ROM03 model (default)" note: "Required for ROM03 model (default)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:388-395" source_ref: "Machines/Utility/ROMCatalogue.cpp:388-395"
- name: apple2gs.chr - name: apple2gs.chr
@@ -430,7 +395,6 @@ files:
size: 4096 size: 4096
crc32: "91e53cd8" crc32: "91e53cd8"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:396-403" source_ref: "Machines/Utility/ROMCatalogue.cpp:396-403"
- name: "341s0632-2" - name: "341s0632-2"
@@ -439,7 +403,6 @@ files:
size: 4096 size: 4096
crc32: "e1c11fb0" crc32: "e1c11fb0"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:404-411" source_ref: "Machines/Utility/ROMCatalogue.cpp:404-411"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -451,7 +414,6 @@ files:
size: 196608 size: 196608
crc32: "1a586c64" crc32: "1a586c64"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:416-423" source_ref: "Machines/Utility/ROMCatalogue.cpp:416-423"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -463,7 +425,6 @@ files:
size: 8192 size: 8192
crc32: "3aa93ef3" crc32: "3aa93ef3"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:496-503" source_ref: "Machines/Utility/ROMCatalogue.cpp:496-503"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -477,7 +438,6 @@ files:
crc32: "718d42b1" crc32: "718d42b1"
required: false required: false
note: "Conditional: needed when VIC-20 has 1540 drive" note: "Conditional: needed when VIC-20 has 1540 drive"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:508-515" source_ref: "Machines/Utility/ROMCatalogue.cpp:508-515"
- name: 1541.bin - name: 1541.bin
@@ -487,7 +447,6 @@ files:
crc32: "fb760019" crc32: "fb760019"
required: false required: false
note: "Conditional: needed when Plus/4 has 1541 drive" note: "Conditional: needed when Plus/4 has 1541 drive"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:516-523" source_ref: "Machines/Utility/ROMCatalogue.cpp:516-523"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -501,7 +460,6 @@ files:
crc32: "ce7144f6" crc32: "ce7144f6"
required: false required: false
note: "Conditional: needed for 16-sector disk controller" note: "Conditional: needed for 16-sector disk controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:528-535" source_ref: "Machines/Utility/ROMCatalogue.cpp:528-535"
- name: state-machine-16.rom - name: state-machine-16.rom
@@ -518,7 +476,6 @@ files:
other image is read as the unadulterated P6 PROM (9796a238), its address other image is read as the unadulterated P6 PROM (9796a238), its address
lines and the top nibble of each value remapped into that order. Both give lines and the top nibble of each value remapped into that order. Both give
the same internal table. the same internal table.
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:536-543, Components/DiskII/DiskII.cpp:187-239" source_ref: "Machines/Utility/ROMCatalogue.cpp:536-543, Components/DiskII/DiskII.cpp:187-239"
- name: boot-13.rom - name: boot-13.rom
@@ -528,7 +485,6 @@ files:
crc32: "d34eb2ff" crc32: "d34eb2ff"
required: false required: false
note: "Conditional: needed for 13-sector disk controller" note: "Conditional: needed for 13-sector disk controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:544-551" source_ref: "Machines/Utility/ROMCatalogue.cpp:544-551"
- name: state-machine-13.rom - name: state-machine-13.rom
@@ -538,7 +494,6 @@ files:
crc32: "62e22620" crc32: "62e22620"
required: false required: false
note: "Conditional: needed for 13-sector disk controller" note: "Conditional: needed for 13-sector disk controller"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:552-559" source_ref: "Machines/Utility/ROMCatalogue.cpp:552-559"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -552,7 +507,6 @@ files:
required: true required: true
aliases: ["Exos (198x)(Enterprise).bin"] aliases: ["Exos (198x)(Enterprise).bin"]
note: "One EXOS version required. Selected by target or any available." note: "One EXOS version required. Selected by target or any available."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:600-607" source_ref: "Machines/Utility/ROMCatalogue.cpp:600-607"
- name: exos20.bin - name: exos20.bin
@@ -563,7 +517,6 @@ files:
required: true required: true
aliases: ["Expandible OS v2.0 (1984)(Intelligent Software).bin"] aliases: ["Expandible OS v2.0 (1984)(Intelligent Software).bin"]
note: "Alternative EXOS version" note: "Alternative EXOS version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:611-618" source_ref: "Machines/Utility/ROMCatalogue.cpp:611-618"
- name: exos21.bin - name: exos21.bin
@@ -574,7 +527,6 @@ files:
required: true required: true
aliases: ["Expandible OS v2.1 (1985)(Intelligent Software).bin"] aliases: ["Expandible OS v2.1 (1985)(Intelligent Software).bin"]
note: "Alternative EXOS version" note: "Alternative EXOS version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:622-629" source_ref: "Machines/Utility/ROMCatalogue.cpp:622-629"
- name: exos23.bin - name: exos23.bin
@@ -585,7 +537,6 @@ files:
required: true required: true
aliases: ["Expandible OS v2.3 (1987)(Intelligent Software).bin"] aliases: ["Expandible OS v2.3 (1987)(Intelligent Software).bin"]
note: "Alternative EXOS version" note: "Alternative EXOS version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:633-640" source_ref: "Machines/Utility/ROMCatalogue.cpp:633-640"
- name: basic10.bin - name: basic10.bin
@@ -595,7 +546,6 @@ files:
crc32: "d62e4fb7" crc32: "d62e4fb7"
required: true required: true
note: "One BASIC version required. v1.0 single-file version." note: "One BASIC version required. v1.0 single-file version."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:645-652" source_ref: "Machines/Utility/ROMCatalogue.cpp:645-652"
- name: "BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin" - name: "BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin"
@@ -605,7 +555,6 @@ files:
crc32: "37bf48e1" crc32: "37bf48e1"
required: false required: false
note: "Alternative: both parts needed together as substitute for basic10.bin" note: "Alternative: both parts needed together as substitute for basic10.bin"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:653-660" source_ref: "Machines/Utility/ROMCatalogue.cpp:653-660"
- name: "BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin" - name: "BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin"
@@ -615,7 +564,6 @@ files:
crc32: "c5298c79" crc32: "c5298c79"
required: false required: false
note: "Alternative: both parts needed together as substitute for basic10.bin" note: "Alternative: both parts needed together as substitute for basic10.bin"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:661-668" source_ref: "Machines/Utility/ROMCatalogue.cpp:661-668"
- name: basic11.bin - name: basic11.bin
@@ -625,7 +573,6 @@ files:
crc32: "683cf455" crc32: "683cf455"
required: true required: true
note: "Alternative BASIC version" note: "Alternative BASIC version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:669-676" source_ref: "Machines/Utility/ROMCatalogue.cpp:669-676"
- name: "BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin" - name: "BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin"
@@ -635,7 +582,6 @@ files:
crc32: "c96b7602" crc32: "c96b7602"
required: false required: false
note: "Alternative format of BASIC v1.1 with trailing byte" note: "Alternative format of BASIC v1.1 with trailing byte"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:677-684" source_ref: "Machines/Utility/ROMCatalogue.cpp:677-684"
- name: basic21.bin - name: basic21.bin
@@ -648,7 +594,6 @@ files:
- "BASIC Interpreter v2.1 (1985)(Intelligent Software).bin" - "BASIC Interpreter v2.1 (1985)(Intelligent Software).bin"
- "BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin" - "BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin"
note: "Alternative BASIC version" note: "Alternative BASIC version"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:685-693" source_ref: "Machines/Utility/ROMCatalogue.cpp:685-693"
- name: epdos.bin - name: epdos.bin
@@ -659,7 +604,6 @@ files:
required: false required: false
aliases: ["EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"] aliases: ["EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"]
note: "Conditional: needed for EPDOS disk interface" note: "Conditional: needed for EPDOS disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:698-705" source_ref: "Machines/Utility/ROMCatalogue.cpp:698-705"
- name: exdos.bin - name: exdos.bin
@@ -670,7 +614,6 @@ files:
required: false required: false
aliases: ["EX-DOS EPROM (198x)(Enterprise).bin"] aliases: ["EX-DOS EPROM (198x)(Enterprise).bin"]
note: "Conditional: needed when DOS is set to EXDOS" note: "Conditional: needed when DOS is set to EXDOS"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:709-716" source_ref: "Machines/Utility/ROMCatalogue.cpp:709-716"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -683,7 +626,6 @@ files:
crc32: "6d0c8a28" crc32: "6d0c8a28"
required: true required: true
note: "Required for Macintosh 128k model" note: "Required for Macintosh 128k model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:724-731" source_ref: "Machines/Utility/ROMCatalogue.cpp:724-731"
- name: mac512k.rom - name: mac512k.rom
@@ -693,7 +635,6 @@ files:
crc32: "cf759e0d" crc32: "cf759e0d"
required: true required: true
note: "Required for Macintosh 512k model" note: "Required for Macintosh 512k model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:732-739" source_ref: "Machines/Utility/ROMCatalogue.cpp:732-739"
- name: macplus.rom - name: macplus.rom
@@ -708,7 +649,6 @@ files:
checksum 4D1F8172 of the third Mac Plus revision. The checksum 4D1F8172 of the third Mac Plus revision. The
machine copies whichever is supplied into its 128 KiB ROM without machine copies whichever is supplied into its 128 KiB ROM without
reading the revision. reading the revision.
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:740-751, Machines/Apple/Macintosh/Macintosh.cpp:127-144" source_ref: "Machines/Utility/ROMCatalogue.cpp:740-751, Machines/Apple/Macintosh/Macintosh.cpp:127-144"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -722,7 +662,6 @@ files:
crc32: "0072ed54" crc32: "0072ed54"
required: false required: false
note: "Optional. Boots cartridge directly if absent." note: "Optional. Boots cartridge directly if absent."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:764-771" source_ref: "Machines/Utility/ROMCatalogue.cpp:764-771"
- name: japanese-bios.sms - name: japanese-bios.sms
@@ -733,7 +672,6 @@ files:
crc32: "48d44a13" crc32: "48d44a13"
required: false required: false
note: "Optional. Used for Japan region. Boots without if absent." note: "Optional. Used for Japan region. Boots without if absent."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:756-763" source_ref: "Machines/Utility/ROMCatalogue.cpp:756-763"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -747,7 +685,6 @@ files:
crc32: "94ee12f3" crc32: "94ee12f3"
required: true required: true
note: "Required for MSX1. Region bytes patched in memory if regional BIOS absent." note: "Required for MSX1. Region bytes patched in memory if regional BIOS absent."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:777-784" source_ref: "Machines/Utility/ROMCatalogue.cpp:777-784"
- name: msx-japanese.rom - name: msx-japanese.rom
@@ -758,7 +695,6 @@ files:
crc32: "ee229390" crc32: "ee229390"
required: false required: false
note: "Alternative to msx.rom for Japanese region" note: "Alternative to msx.rom for Japanese region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:785-792" source_ref: "Machines/Utility/ROMCatalogue.cpp:785-792"
- name: msx-american.rom - name: msx-american.rom
@@ -768,7 +704,6 @@ files:
size: 32768 size: 32768
required: false required: false
note: "Alternative to msx.rom for American region" note: "Alternative to msx.rom for American region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:793-800" source_ref: "Machines/Utility/ROMCatalogue.cpp:793-800"
- name: msx-european.rom - name: msx-european.rom
@@ -778,7 +713,6 @@ files:
size: 32768 size: 32768
required: false required: false
note: "Alternative to msx.rom for European region" note: "Alternative to msx.rom for European region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:801-808" source_ref: "Machines/Utility/ROMCatalogue.cpp:801-808"
- name: disk.rom - name: disk.rom
@@ -788,7 +722,6 @@ files:
crc32: "721f61df" crc32: "721f61df"
required: false required: false
note: "Conditional: needed for disk drive support" note: "Conditional: needed for disk drive support"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:809-816" source_ref: "Machines/Utility/ROMCatalogue.cpp:809-816"
- name: msx2.rom - name: msx2.rom
@@ -799,7 +732,6 @@ files:
crc32: "6cdaf3a5" crc32: "6cdaf3a5"
required: true required: true
note: "Required for MSX2" note: "Required for MSX2"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:818-825" source_ref: "Machines/Utility/ROMCatalogue.cpp:818-825"
- name: msx2ext.rom - name: msx2ext.rom
@@ -810,7 +742,6 @@ files:
crc32: "66237ecf" crc32: "66237ecf"
required: true required: true
note: "Required for MSX2" note: "Required for MSX2"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:826-833" source_ref: "Machines/Utility/ROMCatalogue.cpp:826-833"
- name: fmpac.rom - name: fmpac.rom
@@ -820,7 +751,6 @@ files:
crc32: "0e84505d" crc32: "0e84505d"
required: false required: false
note: "Conditional: needed for MSX-Music support" note: "Conditional: needed for MSX-Music support"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:834-841" source_ref: "Machines/Utility/ROMCatalogue.cpp:834-841"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -833,7 +763,6 @@ files:
crc32: "f18710b4" crc32: "f18710b4"
required: true required: true
note: "Required for Oric-1 model" note: "Required for Oric-1 model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:870-877" source_ref: "Machines/Utility/ROMCatalogue.cpp:870-877"
- name: basic11.rom - name: basic11.rom
@@ -843,7 +772,6 @@ files:
crc32: "c3a92bef" crc32: "c3a92bef"
required: true required: true
note: "Required for Oric Atmos model" note: "Required for Oric Atmos model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:878-885" source_ref: "Machines/Utility/ROMCatalogue.cpp:878-885"
- name: pravetz.rom - name: pravetz.rom
@@ -853,7 +781,6 @@ files:
crc32: "58079502" crc32: "58079502"
required: true required: true
note: "Required for Pravetz 8D model" note: "Required for Pravetz 8D model"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:886-893" source_ref: "Machines/Utility/ROMCatalogue.cpp:886-893"
- name: colour.rom - name: colour.rom
@@ -863,7 +790,6 @@ files:
crc32: "d50fca65" crc32: "d50fca65"
required: false required: false
note: "Optional. Enables colour video output." note: "Optional. Enables colour video output."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:846-853" source_ref: "Machines/Utility/ROMCatalogue.cpp:846-853"
- name: ORIC.PAL-PROM-TBP24S10-1ab9b572.bin - name: ORIC.PAL-PROM-TBP24S10-1ab9b572.bin
@@ -873,7 +799,6 @@ files:
crc32: "1ab9b572" crc32: "1ab9b572"
required: false required: false
note: "Optional. Alternative colour ROM." note: "Optional. Alternative colour ROM."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:854-861" source_ref: "Machines/Utility/ROMCatalogue.cpp:854-861"
- name: PRAVETZ-8D.PAL-PROM-82S129-1576a69b.bin - name: PRAVETZ-8D.PAL-PROM-82S129-1576a69b.bin
@@ -883,7 +808,6 @@ files:
crc32: "1576a69b" crc32: "1576a69b"
required: false required: false
note: "Optional. Colour ROM for Pravetz model." note: "Optional. Colour ROM for Pravetz model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:862-869" source_ref: "Machines/Utility/ROMCatalogue.cpp:862-869"
- name: bd500.rom - name: bd500.rom
@@ -893,7 +817,6 @@ files:
crc32: "61952e34" crc32: "61952e34"
required: false required: false
note: "Conditional: needed for Byte Drive 500 disk interface" note: "Conditional: needed for Byte Drive 500 disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:894-901" source_ref: "Machines/Utility/ROMCatalogue.cpp:894-901"
- name: jasmin.rom - name: jasmin.rom
@@ -903,7 +826,6 @@ files:
crc32: "37220e89" crc32: "37220e89"
required: false required: false
note: "Conditional: needed for Jasmin disk interface" note: "Conditional: needed for Jasmin disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:902-909" source_ref: "Machines/Utility/ROMCatalogue.cpp:902-909"
- name: microdisc.rom - name: microdisc.rom
@@ -913,7 +835,6 @@ files:
crc32: "a9664a9c" crc32: "a9664a9c"
required: false required: false
note: "Conditional: needed for Microdisc disk interface" note: "Conditional: needed for Microdisc disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:910-917" source_ref: "Machines/Utility/ROMCatalogue.cpp:910-917"
- name: 8dos.rom - name: 8dos.rom
@@ -923,7 +844,6 @@ files:
crc32: "49a74c06" crc32: "49a74c06"
required: false required: false
note: "Conditional: needed for Pravetz 8-DOS disk interface" note: "Conditional: needed for Pravetz 8-DOS disk interface"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:918-925" source_ref: "Machines/Utility/ROMCatalogue.cpp:918-925"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -936,7 +856,6 @@ files:
crc32: "9576944c" crc32: "9576944c"
required: true required: true
note: "Required for XT-class emulation" note: "Required for XT-class emulation"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:930-937" source_ref: "Machines/Utility/ROMCatalogue.cpp:930-937"
- name: GLaTICK_0.8.5_AT.ROM - name: GLaTICK_0.8.5_AT.ROM
@@ -946,7 +865,6 @@ files:
crc32: "371ea3f1" crc32: "371ea3f1"
required: false required: false
note: "XT-class RTC option ROM" note: "XT-class RTC option ROM"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:938-945" source_ref: "Machines/Utility/ROMCatalogue.cpp:938-945"
- name: "Phoenix 80286 ROM BIOS Version 3.05.bin" - name: "Phoenix 80286 ROM BIOS Version 3.05.bin"
@@ -956,7 +874,6 @@ files:
crc32: "8d0d318a" crc32: "8d0d318a"
required: true required: true
note: "Required for AT-class emulation (alternative to IBM AT BIOS)" note: "Required for AT-class emulation (alternative to IBM AT BIOS)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:946-953" source_ref: "Machines/Utility/ROMCatalogue.cpp:946-953"
- name: at-bios.bin - name: at-bios.bin
@@ -966,7 +883,6 @@ files:
crc32: "674426be" crc32: "674426be"
required: true required: true
note: "AT-class: single-file IBM AT BIOS (alternative to split U27/U47)" note: "AT-class: single-file IBM AT BIOS (alternative to split U27/U47)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:954-961" source_ref: "Machines/Utility/ROMCatalogue.cpp:954-961"
- name: BIOS_5170_15NOV85_U27_61X9266_27256.BIN - name: BIOS_5170_15NOV85_U27_61X9266_27256.BIN
@@ -976,7 +892,6 @@ files:
crc32: "4995be7a" crc32: "4995be7a"
required: false required: false
note: "AT-class: split BIOS, used with U47 chip" note: "AT-class: split BIOS, used with U47 chip"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:962-969" source_ref: "Machines/Utility/ROMCatalogue.cpp:962-969"
- name: BIOS_5170_15NOV85_U47_61X9265_27256.BIN - name: BIOS_5170_15NOV85_U47_61X9265_27256.BIN
@@ -986,7 +901,6 @@ files:
crc32: "c32713e4" crc32: "c32713e4"
required: false required: false
note: "AT-class: split BIOS, used with U27 chip" note: "AT-class: split BIOS, used with U27 chip"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:970-977" source_ref: "Machines/Utility/ROMCatalogue.cpp:970-977"
- name: CGA.F08 - name: CGA.F08
@@ -996,7 +910,6 @@ files:
crc32: "a362ffe6" crc32: "a362ffe6"
required: true required: true
note: "Required for CGA video adapter" note: "Required for CGA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:978-985" source_ref: "Machines/Utility/ROMCatalogue.cpp:978-985"
- name: EUMDA9.F14 - name: EUMDA9.F14
@@ -1006,7 +919,6 @@ files:
crc32: "7754882a" crc32: "7754882a"
required: true required: true
note: "Required for MDA video adapter" note: "Required for MDA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:986-993" source_ref: "Machines/Utility/ROMCatalogue.cpp:986-993"
- name: ibm_6277356_ega_card_u44_27128.bin - name: ibm_6277356_ega_card_u44_27128.bin
@@ -1016,7 +928,6 @@ files:
crc32: "2f2fbc40" crc32: "2f2fbc40"
required: false required: false
note: "Needed for EGA video adapter" note: "Needed for EGA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:994-1001" source_ref: "Machines/Utility/ROMCatalogue.cpp:994-1001"
- name: ibm_vga.bin - name: ibm_vga.bin
@@ -1026,7 +937,6 @@ files:
crc32: "03b3f90d" crc32: "03b3f90d"
required: false required: false
note: "Needed for VGA video adapter" note: "Needed for VGA video adapter"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1002-1009" source_ref: "Machines/Utility/ROMCatalogue.cpp:1002-1009"
- name: ibm-basic-1.10.rom - name: ibm-basic-1.10.rom
@@ -1036,7 +946,6 @@ files:
crc32: "ebacb791" crc32: "ebacb791"
required: false required: false
note: "Required for AT-class with IBM BIOS (not needed with Phoenix)" note: "Required for AT-class with IBM BIOS (not needed with Phoenix)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1010-1017" source_ref: "Machines/Utility/ROMCatalogue.cpp:1010-1017"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -1048,7 +957,6 @@ files:
size: 16384 size: 16384
crc32: "74eaae87" crc32: "74eaae87"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1046-1053" source_ref: "Machines/Utility/ROMCatalogue.cpp:1046-1053"
- name: kernal.318004-05.bin - name: kernal.318004-05.bin
@@ -1058,7 +966,6 @@ files:
crc32: "71c07bd4" crc32: "71c07bd4"
required: true required: true
note: "Default kernel version requested by machine code" note: "Default kernel version requested by machine code"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1038-1045" source_ref: "Machines/Utility/ROMCatalogue.cpp:1038-1045"
- name: kernal.318004-03.bin - name: kernal.318004-03.bin
@@ -1068,7 +975,6 @@ files:
crc32: "77bab934" crc32: "77bab934"
required: false required: false
note: "Alternative kernel version (in catalogue but not default)" note: "Alternative kernel version (in catalogue but not default)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1022-1029" source_ref: "Machines/Utility/ROMCatalogue.cpp:1022-1029"
- name: kernal.318004-04.bin - name: kernal.318004-04.bin
@@ -1078,7 +984,6 @@ files:
crc32: "be54ed79" crc32: "be54ed79"
required: false required: false
note: "Alternative kernel version (in catalogue but not default)" note: "Alternative kernel version (in catalogue but not default)"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1030-1037" source_ref: "Machines/Utility/ROMCatalogue.cpp:1030-1037"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -1090,7 +995,6 @@ files:
size: 16384 size: 16384
crc32: "f62d2f3a" crc32: "f62d2f3a"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1157-1164" source_ref: "Machines/Utility/ROMCatalogue.cpp:1157-1164"
- name: mo5-v1.1.rom - name: mo5-v1.1.rom
@@ -1099,7 +1003,6 @@ files:
size: 16384 size: 16384
crc32: "237c60bf" crc32: "237c60bf"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1165-1172" source_ref: "Machines/Utility/ROMCatalogue.cpp:1165-1172"
- name: mo6-v1.rom - name: mo6-v1.rom
@@ -1108,7 +1011,6 @@ files:
size: 65536 size: 65536
crc32: "45e859c6" crc32: "45e859c6"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1173-1180" source_ref: "Machines/Utility/ROMCatalogue.cpp:1173-1180"
- name: mo6-v2.rom - name: mo6-v2.rom
@@ -1117,7 +1019,6 @@ files:
size: 65536 size: 65536
crc32: "b4430a3b" crc32: "b4430a3b"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1181-1188" source_ref: "Machines/Utility/ROMCatalogue.cpp:1181-1188"
- name: mo6-v3.rom - name: mo6-v3.rom
@@ -1126,7 +1027,6 @@ files:
size: 65536 size: 65536
crc32: "7a012322" crc32: "7a012322"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1189-1196" source_ref: "Machines/Utility/ROMCatalogue.cpp:1189-1196"
- name: pc128.rom - name: pc128.rom
@@ -1135,7 +1035,6 @@ files:
size: 65536 size: 65536
crc32: "fc173504" crc32: "fc173504"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1197-1204" source_ref: "Machines/Utility/ROMCatalogue.cpp:1197-1204"
- name: cd90-640.rom - name: cd90-640.rom
@@ -1145,7 +1044,6 @@ files:
crc32: "5114c0a5" crc32: "5114c0a5"
required: false required: false
note: "Requested on top of the machine ROM when a disk is attached." note: "Requested on top of the machine ROM when a disk is attached."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1206-1213" source_ref: "Machines/Utility/ROMCatalogue.cpp:1206-1213"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -1157,7 +1055,6 @@ files:
size: 8192 size: 8192
crc32: "db4c43c1" crc32: "db4c43c1"
required: true required: true
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1218-1225" source_ref: "Machines/Utility/ROMCatalogue.cpp:1218-1225"
- name: characters-english.bin - name: characters-english.bin
@@ -1168,7 +1065,6 @@ files:
crc32: "83e032a6" crc32: "83e032a6"
required: true required: true
note: "Required for English region" note: "Required for English region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1226-1233" source_ref: "Machines/Utility/ROMCatalogue.cpp:1226-1233"
- name: kernel-pal.bin - name: kernel-pal.bin
@@ -1179,7 +1075,6 @@ files:
crc32: "4be07cb4" crc32: "4be07cb4"
required: true required: true
note: "Required for English PAL region" note: "Required for English PAL region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1234-1241" source_ref: "Machines/Utility/ROMCatalogue.cpp:1234-1241"
- name: kernel-ntsc.bin - name: kernel-ntsc.bin
@@ -1190,7 +1085,6 @@ files:
crc32: "e5e7c174" crc32: "e5e7c174"
required: true required: true
note: "Required for English NTSC region" note: "Required for English NTSC region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1242-1249" source_ref: "Machines/Utility/ROMCatalogue.cpp:1242-1249"
- name: characters-danish.bin - name: characters-danish.bin
@@ -1201,7 +1095,6 @@ files:
crc32: "7fc11454" crc32: "7fc11454"
required: false required: false
note: "Required for Danish region" note: "Required for Danish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1250-1257" source_ref: "Machines/Utility/ROMCatalogue.cpp:1250-1257"
- name: kernel-danish.bin - name: kernel-danish.bin
@@ -1212,7 +1105,6 @@ files:
crc32: "02adaf16" crc32: "02adaf16"
required: false required: false
note: "Required for Danish region" note: "Required for Danish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1258-1265" source_ref: "Machines/Utility/ROMCatalogue.cpp:1258-1265"
- name: characters-japanese.bin - name: characters-japanese.bin
@@ -1223,7 +1115,6 @@ files:
crc32: "fcfd8a4b" crc32: "fcfd8a4b"
required: false required: false
note: "Required for Japanese region" note: "Required for Japanese region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1266-1273" source_ref: "Machines/Utility/ROMCatalogue.cpp:1266-1273"
- name: kernel-japanese.bin - name: kernel-japanese.bin
@@ -1234,7 +1125,6 @@ files:
crc32: "336900d7" crc32: "336900d7"
required: false required: false
note: "Required for Japanese region" note: "Required for Japanese region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1274-1281" source_ref: "Machines/Utility/ROMCatalogue.cpp:1274-1281"
- name: characters-swedish.bin - name: characters-swedish.bin
@@ -1245,7 +1135,6 @@ files:
crc32: "d808551d" crc32: "d808551d"
required: false required: false
note: "Required for Swedish region" note: "Required for Swedish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1282-1289" source_ref: "Machines/Utility/ROMCatalogue.cpp:1282-1289"
- name: kernel-swedish.bin - name: kernel-swedish.bin
@@ -1256,7 +1145,6 @@ files:
crc32: "b2a60662" crc32: "b2a60662"
required: false required: false
note: "Required for Swedish region" note: "Required for Swedish region"
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1290-1297" source_ref: "Machines/Utility/ROMCatalogue.cpp:1290-1297"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -1270,7 +1158,6 @@ files:
crc32: "ddee531f" crc32: "ddee531f"
required: false required: false
note: "Built into binary. Required for 16K/48K model." note: "Built into binary. Required for 16K/48K model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1302-1309, Machines/Utility/ROMLibrary.cpp:14539" source_ref: "Machines/Utility/ROMCatalogue.cpp:1302-1309, Machines/Utility/ROMLibrary.cpp:14539"
- name: 128.rom - name: 128.rom
@@ -1280,7 +1167,6 @@ files:
crc32: "2cbe8995" crc32: "2cbe8995"
required: false required: false
note: "Built into binary. Required for 128K model." note: "Built into binary. Required for 128K model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1310-1317, Machines/Utility/ROMLibrary.cpp:14540" source_ref: "Machines/Utility/ROMCatalogue.cpp:1310-1317, Machines/Utility/ROMLibrary.cpp:14540"
- name: plus2.rom - name: plus2.rom
@@ -1290,7 +1176,6 @@ files:
crc32: "e7a517dc" crc32: "e7a517dc"
required: false required: false
note: "Built into binary. Required for +2 model." note: "Built into binary. Required for +2 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1318-1325, Machines/Utility/ROMLibrary.cpp:14541" source_ref: "Machines/Utility/ROMCatalogue.cpp:1318-1325, Machines/Utility/ROMLibrary.cpp:14541"
- name: plus3.rom - name: plus3.rom
@@ -1300,7 +1185,6 @@ files:
crc32: "96e3c17a" crc32: "96e3c17a"
required: false required: false
note: "Built into binary. Required for +2a/+3 model." note: "Built into binary. Required for +2a/+3 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1326-1336, Machines/Utility/ROMLibrary.cpp:14542" source_ref: "Machines/Utility/ROMCatalogue.cpp:1326-1336, Machines/Utility/ROMLibrary.cpp:14542"
# ------------------------------------------------------- # -------------------------------------------------------
@@ -1314,7 +1198,6 @@ files:
crc32: "4c7fc597" crc32: "4c7fc597"
required: false required: false
note: "Built into binary. Required for ZX80 model." note: "Built into binary. Required for ZX80 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1341-1348, Machines/Utility/ROMLibrary.cpp:14544" source_ref: "Machines/Utility/ROMCatalogue.cpp:1341-1348, Machines/Utility/ROMLibrary.cpp:14544"
- name: zx81.rom - name: zx81.rom
@@ -1324,5 +1207,4 @@ files:
crc32: "4b1dd6eb" crc32: "4b1dd6eb"
required: false required: false
note: "Built into binary. Required for ZX81 model." note: "Built into binary. Required for ZX81 model."
mode: standalone
source_ref: "Machines/Utility/ROMCatalogue.cpp:1349-1356, Machines/Utility/ROMLibrary.cpp:14545" source_ref: "Machines/Utility/ROMCatalogue.cpp:1349-1356, Machines/Utility/ROMLibrary.cpp:14545"
+3 -3
View File
@@ -28,11 +28,11 @@ notes: |
- ROMEXT[0] (upper ROM slot 0, 16KB): second half of cpc6128_bin (BASIC) - ROMEXT[0] (upper ROM slot 0, 16KB): second half of cpc6128_bin (BASIC)
- ROMEXT[7] (upper ROM slot 7, 16KB): romdisc_bin (AMSDOS) - ROMEXT[7] (upper ROM slot 7, 16KB): romdisc_bin (AMSDOS)
Called from HardResetCPC() (crocods-core/platform.c:1009-1020) using Called from HardResetCPC() (crocods-core/platform.c:1024-1035) using
the extern byte arrays directly. No filesystem ROM loading exists in the extern byte arrays directly. No filesystem ROM loading exists in
the libretro path. the libretro path.
A Multiface ROM loader exists (crocods-core/multiface.c:51-61) that A Multiface ROM loader exists (crocods-core/multiface.c:53-64) that
reads LocRomMulti via fopen(), but InitMultiface() is never called reads LocRomMulti via fopen(), but InitMultiface() is never called
from the libretro build or platform.c. Dead code in libretro context. from the libretro build or platform.c. Dead code in libretro context.
@@ -45,7 +45,7 @@ files: []
platform_details: platform_details:
bios_mapping: bios_mapping:
source_ref: "crocods-core/vga.c:185-200, crocods-core/platform.c:1009-1020" source_ref: "crocods-core/vga.c:185-200, crocods-core/platform.c:1024-1035"
note: | note: |
All ROMs are embedded. InitMemCPC() copies cpc6128_bin and romdisc_bin All ROMs are embedded. InitMemCPC() copies cpc6128_bin and romdisc_bin
into ROMINF and ROMEXT arrays via memcpy. No filesystem lookup occurs. into ROMINF and ROMEXT arrays via memcpy. No filesystem lookup occurs.
+5 -5
View File
@@ -148,7 +148,7 @@ files:
required: false required: false
hle_fallback: true hle_fallback: true
note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu" note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:26-39,42-89" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:27-45,47-92"
- name: "keys.bin" - name: "keys.bin"
path: null path: null
@@ -156,7 +156,7 @@ files:
required: false required: false
hle_fallback: true hle_fallback: true
note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import" note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:20, Source/Core/DiscIO/NANDImporter.cpp:82-94, Source/Core/DiscIO/NANDImporter.h:46" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:20, Source/Core/DiscIO/NANDImporter.cpp:77-91, Source/Core/DiscIO/NANDImporter.h:35-38"
# -- Wii SD Card Image -- # -- Wii SD Card Image --
- name: "WiiSD.raw" - name: "WiiSD.raw"
@@ -189,21 +189,21 @@ files:
required: false required: false
hle_fallback: true hle_fallback: true
note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import" note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:239-323" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:220-304"
- name: "clientcakey.pem" - name: "clientcakey.pem"
path: "Wii/clientcakey.pem" path: "Wii/clientcakey.pem"
required: false required: false
hle_fallback: true hle_fallback: true
note: "SSL client private key. Auto-extracted from IOS13 content during NAND import" note: "SSL client private key. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:275" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:256"
- name: "rootca.pem" - name: "rootca.pem"
path: "Wii/rootca.pem" path: "Wii/rootca.pem"
required: false required: false
hle_fallback: true hle_fallback: true
note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import" note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:276" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:257"
# -- Realtek Bluetooth firmware (Wiimote passthrough) -- # -- Realtek Bluetooth firmware (Wiimote passthrough) --
# Required for real Wiimote connectivity via USB Bluetooth adapters with Realtek chipsets. # Required for real Wiimote connectivity via USB Bluetooth adapters with Realtek chipsets.
-5
View File
@@ -28,28 +28,24 @@ files:
- name: "prod.keys" - name: "prod.keys"
required: true required: true
path: "switch/" path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)" note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:574-575, 853" source_ref: "src/core/crypto/key_manager.cpp:574-575, 853"
- name: "dev.keys" - name: "dev.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Development-unit keys, read instead of prod.keys when use_dev_keys is set" note: "Development-unit keys, read instead of prod.keys when use_dev_keys is set"
source_ref: "src/core/crypto/key_manager.cpp:570-571, 852" source_ref: "src/core/crypto/key_manager.cpp:570-571, 852"
- name: "title.keys" - name: "title.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)" note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:577-578, 850" source_ref: "src/core/crypto/key_manager.cpp:577-578, 850"
- name: "console.keys" - name: "console.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)" note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:579-580" source_ref: "src/core/crypto/key_manager.cpp:579-580"
@@ -57,7 +53,6 @@ files:
required: false required: false
path: "switch/" path: "switch/"
size: 160 size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)" note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:281-302" source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:281-302"
-16
View File
@@ -79,7 +79,6 @@ files:
description: "Sega CD BIOS (US)" description: "Sega CD BIOS (US)"
region: [north-america] region: [north-america]
required: true required: true
mode: standalone
size: 131072 size: 131072
validation: [size] validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:39-49 (read), frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (bios_path_for_region), backend/segacd-core/src/api.rs:77-79 (BIOS_LEN check)" source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:39-49 (read), frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (bios_path_for_region), backend/segacd-core/src/api.rs:77-79 (BIOS_LEN check)"
@@ -90,7 +89,6 @@ files:
description: "Mega CD BIOS (Europe)" description: "Mega CD BIOS (Europe)"
region: [europe] region: [europe]
required: true required: true
mode: standalone
size: 131072 size: 131072
validation: [size] validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (eu_bios_path), backend/segacd-core/src/api.rs:77-79" source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (eu_bios_path), backend/segacd-core/src/api.rs:77-79"
@@ -100,7 +98,6 @@ files:
description: "Mega CD BIOS (Japan)" description: "Mega CD BIOS (Japan)"
region: [japan] region: [japan]
required: true required: true
mode: standalone
size: 131072 size: 131072
validation: [size] validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (jp_bios_path), backend/segacd-core/src/api.rs:77-79" source_ref: "frontend/jgenesis-native-driver/src/mainloop/genesis.rs:238-244 (jp_bios_path), backend/segacd-core/src/api.rs:77-79"
@@ -112,7 +109,6 @@ files:
system: nintendo-gba system: nintendo-gba
description: "Game Boy Advance BIOS" description: "Game Boy Advance BIOS"
required: true required: true
mode: standalone
size: 16384 size: 16384
validation: [size] validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/gba.rs:29-33 (GbaNoBios), backend/gba-core/src/memory.rs:10,102-107 (BIOS_ROM_LEN check)" source_ref: "frontend/jgenesis-native-driver/src/mainloop/gba.rs:29-33 (GbaNoBios), backend/gba-core/src/memory.rs:10,102-107 (BIOS_ROM_LEN check)"
@@ -126,7 +122,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-1" description: "NEC uPD77C25 firmware for DSP-1"
required: true required: true
mode: standalone
size: 8192 size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:336-353 (MissingDsp1Rom), backend/snes-coprocessors/src/upd77c25.rs:28-31,35-38,382-390" source_ref: "backend/snes-core/src/memory/cartridge.rs:336-353 (MissingDsp1Rom), backend/snes-coprocessors/src/upd77c25.rs:28-31,35-38,382-390"
note: "0x1800 program plus 0x800 data. Also used by DSP-1B titles, which have no separate slot. Layout is sliced at a fixed offset, size is not checked." note: "0x1800 program plus 0x800 data. Also used by DSP-1B titles, which have no separate slot. Layout is sliced at a fixed offset, size is not checked."
@@ -136,7 +131,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-2" description: "NEC uPD77C25 firmware for DSP-2"
required: true required: true
mode: standalone
size: 8192 size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:340-342 (MissingDsp2Rom), :929 (Dungeon Master CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390" source_ref: "backend/snes-core/src/memory/cartridge.rs:340-342 (MissingDsp2Rom), :929 (Dungeon Master CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390"
@@ -145,7 +139,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-3" description: "NEC uPD77C25 firmware for DSP-3"
required: true required: true
mode: standalone
size: 8192 size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:343-345 (MissingDsp3Rom), :931 (SD Gundam GX CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390" source_ref: "backend/snes-core/src/memory/cartridge.rs:343-345 (MissingDsp3Rom), :931 (SD Gundam GX CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390"
@@ -154,7 +147,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "NEC uPD77C25 firmware for DSP-4" description: "NEC uPD77C25 firmware for DSP-4"
required: true required: true
mode: standalone
size: 8192 size: 8192
source_ref: "backend/snes-core/src/memory/cartridge.rs:346-348 (MissingDsp4Rom), :933 (Top Gear 3000 CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390" source_ref: "backend/snes-core/src/memory/cartridge.rs:346-348 (MissingDsp4Rom), :933 (Top Gear 3000 CRC32 match), backend/snes-coprocessors/src/upd77c25.rs:382-390"
@@ -163,7 +155,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "NEC uPD96050 firmware for ST010" description: "NEC uPD96050 firmware for ST010"
required: true required: true
mode: standalone
size: 53248 size: 53248
source_ref: "backend/snes-core/src/memory/cartridge.rs:294-305 (MissingSt010Rom), :938-943 (variant detection), backend/snes-coprocessors/src/upd77c25.rs:28-31,35-38" source_ref: "backend/snes-core/src/memory/cartridge.rs:294-305 (MissingSt010Rom), :938-943 (variant detection), backend/snes-coprocessors/src/upd77c25.rs:28-31,35-38"
note: "0xC000 program plus 0x1000 data. Used by F1 ROC II." note: "0xC000 program plus 0x1000 data. Used by F1 ROC II."
@@ -173,7 +164,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "NEC uPD96050 firmware for ST011" description: "NEC uPD96050 firmware for ST011"
required: true required: true
mode: standalone
size: 53248 size: 53248
source_ref: "backend/snes-core/src/memory/cartridge.rs:298-300 (MissingSt011Rom), :938-943 (Hayazashi Nidan Morita Shougi CRC32 match)" source_ref: "backend/snes-core/src/memory/cartridge.rs:298-300 (MissingSt011Rom), :938-943 (Hayazashi Nidan Morita Shougi CRC32 match)"
@@ -182,7 +172,6 @@ files:
system: nintendo-snes system: nintendo-snes
description: "ARM firmware for ST018" description: "ARM firmware for ST018"
required: true required: true
mode: standalone
min_size: 163840 min_size: 163840
validation: [size] validation: [size]
source_ref: "backend/snes-core/src/memory/cartridge.rs:310-320 (MissingSt018Rom), backend/snes-coprocessors/src/st018.rs:18-22,346-352 (TOTAL_ROM_LEN check)" source_ref: "backend/snes-core/src/memory/cartridge.rs:310-320 (MissingSt018Rom), backend/snes-coprocessors/src/st018.rs:18-22,346-352 (TOTAL_ROM_LEN check)"
@@ -195,7 +184,6 @@ files:
system: sega-mastersystem system: sega-mastersystem
description: "Master System BIOS" description: "Master System BIOS"
required: false required: false
mode: standalone
source_ref: "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs:27-33,50-56,74-80,112-122, backend/smsgg-core/src/memory.rs:101-106,148-168" source_ref: "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs:27-33,50-56,74-80,112-122, backend/smsgg-core/src/memory.rs:101-106,148-168"
note: "Read only when sms_boot_from_bios is enabled or the emulator runs without a cartridge, both off by default. One slot accepts any regional dump, no size or content check." note: "Read only when sms_boot_from_bios is enabled or the emulator runs without a cartridge, both off by default. One slot accepts any regional dump, no size or content check."
@@ -203,7 +191,6 @@ files:
system: sega-gamegear system: sega-gamegear
description: "Game Gear BIOS" description: "Game Gear BIOS"
required: false required: false
mode: standalone
source_ref: "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs:27-33,50-56,74-80,112-122" source_ref: "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs:27-33,50-56,74-80,112-122"
note: "Read only when gg_boot_from_bios is enabled or the emulator runs without a cartridge, both off by default." note: "Read only when gg_boot_from_bios is enabled or the emulator runs without a cartridge, both off by default."
@@ -214,7 +201,6 @@ files:
system: nintendo-gb system: nintendo-gb
description: "Game Boy boot ROM" description: "Game Boy boot ROM"
required: false required: false
mode: standalone
size: 256 size: 256
validation: [size] validation: [size]
source_ref: "frontend/jgenesis-native-driver/src/mainloop/gb.rs:29-33,77-89, backend/gb-core/src/memory.rs:9,45-63 (DMG_BOOT_ROM_LEN check)" source_ref: "frontend/jgenesis-native-driver/src/mainloop/gb.rs:29-33,77-89, backend/gb-core/src/memory.rs:9,45-63 (DMG_BOOT_ROM_LEN check)"
@@ -224,7 +210,6 @@ files:
system: nintendo-gbc system: nintendo-gbc
description: "Game Boy Color boot ROM" description: "Game Boy Color boot ROM"
required: false required: false
mode: standalone
min_size: 2048 min_size: 2048
max_size: 2304 max_size: 2304
validation: [size] validation: [size]
@@ -239,7 +224,6 @@ files:
system: nintendo-nes system: nintendo-nes
description: "External NES color palette" description: "External NES color palette"
required: false required: false
mode: standalone
min_size: 192 min_size: 192
max_size: 1536 max_size: 1536
validation: [size] validation: [size]
+1 -1
View File
@@ -61,7 +61,7 @@ notes: |
the JVM default soundbank in place. VlcDir points at a VLC installation that the JVM default soundbank in place. VlcDir points at a VLC installation that
vlcj scans for libvlc. vlcj scans for libvlc.
ref: src/media/emulator/media/EmulatorMIDI.java:26-36, ref: src/media/emulator/media/EmulatorMIDI.java:26-36,
src/main/emulator/ui/swt/Property.java:1860-1901 src/main/emulator/ui/swt/Property.java:1863-1904
files: files:
- name: M7_EmuSmw7.dll - name: M7_EmuSmw7.dll
+4 -3
View File
@@ -2,9 +2,10 @@ emulator: Kronos
type: libretro type: libretro
core_classification: official_port core_classification: official_port
source: "https://github.com/libretro/yabause" source: "https://github.com/libretro/yabause"
source_branch: kronos
upstream: "https://github.com/FCare/Kronos" upstream: "https://github.com/FCare/Kronos"
profiled_date: "2026-03-24" profiled_date: "2026-03-24"
source_commit: "8926b0c6c347f8c5c755911ddb0ac695420ffbf8" source_commit: "3791ffb2221fda61d70aec5cca376418caf05669"
core_version: "v2.6.0" core_version: "v2.6.0"
display_name: "Sega - Saturn/ST-V (Kronos)" display_name: "Sega - Saturn/ST-V (Kronos)"
cores: cores:
@@ -244,7 +245,7 @@ files:
size: 2097152 size: 2097152
validation: [size] validation: [size]
note: "ROM cartridge for The King of Fighters '95. Auto-detected via game DB." note: "ROM cartridge for The King of Fighters '95. Auto-detected via game DB."
source_ref: "yabause/src/utils/src/db.c:43-44, yabause/src/cs0.c:1207-1216" source_ref: "yabause/src/utils/src/db.c:43-44, yabause/src/sys/memory/src/cs0.c:1526-1535"
- name: "mpr-19367-mx.ic1" - name: "mpr-19367-mx.ic1"
system: sega-saturn system: sega-saturn
@@ -252,4 +253,4 @@ files:
size: 2097152 size: 2097152
validation: [size] validation: [size]
note: "ROM cartridge for Ultraman: Hikari no Kyojin Densetsu. Auto-detected via game DB." note: "ROM cartridge for Ultraman: Hikari no Kyojin Densetsu. Auto-detected via game DB."
source_ref: "yabause/src/utils/src/db.c:45, yabause/src/cs0.c:1207-1216" source_ref: "yabause/src/utils/src/db.c:45, yabause/src/sys/memory/src/cs0.c:1526-1535"
+5 -5
View File
@@ -36,7 +36,7 @@ files:
min_size: 4194304 min_size: 4194304
max_size: 8388608 max_size: 8388608
validation: [size] validation: [size]
source_ref: "pcsx2/ps2/BiosTools.cpp:231-254,267-322" source_ref: "pcsx2/ps2/BiosTools.cpp:33-34,63-180,249-274,286-348"
note: > note: >
Scans pcsx2/bios/ for any file between 4 MB and 8 MB. Validates via romdir Scans pcsx2/bios/ for any file between 4 MB and 8 MB. Validates via romdir
structure parsing (RESET + ROMVER entries). User selects BIOS via core option structure parsing (RESET + ROMVER entries). User selects BIOS via core option
@@ -47,7 +47,7 @@ files:
required: false required: false
max_size: 4194304 max_size: 4194304
description: "DVD player ROM" description: "DVD player ROM"
source_ref: "pcsx2/ps2/BiosTools.cpp:190-211,314" source_ref: "pcsx2/ps2/BiosTools.cpp:189-229,339"
note: > note: >
DVD player ROM. Tries <biospath>.rom1 (appended) then <biosbase>.rom1 DVD player ROM. Tries <biospath>.rom1 (appended) then <biosbase>.rom1
(extension replaced). Silently skipped if not found. (extension replaced). Silently skipped if not found.
@@ -57,7 +57,7 @@ files:
required: false required: false
max_size: 524288 max_size: 524288
description: "Chinese ROM extension" description: "Chinese ROM extension"
source_ref: "pcsx2/ps2/BiosTools.cpp:190-211,315" source_ref: "pcsx2/ps2/BiosTools.cpp:189-229,340"
note: > note: >
Chinese region ROM extension. Same naming convention as rom1. Chinese region ROM extension. Same naming convention as rom1.
Only present on Chinese region consoles. Only present on Chinese region consoles.
@@ -68,7 +68,7 @@ files:
hle_fallback: true hle_fallback: true
size: 1024 size: 1024
description: "NVRAM / EEPROM data" description: "NVRAM / EEPROM data"
source_ref: "pcsx2/CDVD/CDVD.cpp:224-267" source_ref: "pcsx2/CDVD/CDVD.cpp:196-227"
note: > note: >
Console EEPROM data (language, timezone, region, iLink ID, OSD settings). Console EEPROM data (language, timezone, region, iLink ID, OSD settings).
Path derived from BIOS path with extension replaced to .nvm. Path derived from BIOS path with extension replaced to .nvm.
@@ -80,7 +80,7 @@ files:
hle_fallback: true hle_fallback: true
size: 4 size: 4
description: "Mechacon version" description: "Mechacon version"
source_ref: "pcsx2/CDVD/CDVD.cpp:255-266" source_ref: "pcsx2/CDVD/CDVD.cpp:229-245"
note: > note: >
Mechacon (disc drive controller) version as u32. Mechacon (disc drive controller) version as u32.
Auto-created with default 0x00020603 if missing. Auto-created with default 0x00020603 if missing.
+1 -1
View File
@@ -3183,7 +3183,7 @@ files:
required: true required: true
category: bios_zip category: bios_zip
system: casio-loopy system: casio-loopy
source_ref: "src/mame/casio/casloopy.cpp:2105-2462" source_ref: "src/mame/casio/casloopy.cpp:2097-2105"
contents: contents:
- name: hd6437021.lsi302 - name: hd6437021.lsi302
description: SH-1 CPU internal mask ROM description: SH-1 CPU internal mask ROM
+1 -1
View File
@@ -680,7 +680,7 @@ files:
required: true required: true
category: bios_zip category: bios_zip
system: casio-loopy system: casio-loopy
source_ref: "src/mame/casio/casloopy.cpp:2105-2462" source_ref: "src/mame/casio/casloopy.cpp:2097-2105"
contents: contents:
- name: hd6437021.lsi302 - name: hd6437021.lsi302
description: SH-1 CPU internal mask ROM description: SH-1 CPU internal mask ROM
+2 -2
View File
@@ -292,7 +292,7 @@ files:
- name: "scph5502.bin" - name: "scph5502.bin"
system: sony-playstation system: sony-playstation
description: "SCPH-5502 / v3.0E BIOS" description: "SCPH-5502 / v3.0E BIOS"
region: [europe] region: [europe, oceania]
required: true required: true
size: 524288 size: 524288
sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09" sha256: "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09"
@@ -376,7 +376,7 @@ files:
- name: "epr-17952a.ic8" - name: "epr-17952a.ic8"
system: sega-stv system: sega-stv
description: "ST-V BIOS (North America)" description: "ST-V BIOS (North America)"
region: [north-america] region: [north-america, south-korea, asia-pal, brazil, latin-america]
required: true required: true
size: 524288 size: 524288
validation: [size] validation: [size]
-6
View File
@@ -23,28 +23,24 @@ notes: >
files: files:
- name: "syscard3.pce" - name: "syscard3.pce"
description: "CD-ROM System Card 3.0 (Japan)" description: "CD-ROM System Card 3.0 (Japan)"
region: [japan]
required: true required: true
source_ref: "libretro.c:65, libretro.c:1896-1901" source_ref: "libretro.c:65, libretro.c:1896-1901"
note: "Default value for pce_fast_cdbios option." note: "Default value for pce_fast_cdbios option."
- name: "syscard2.pce" - name: "syscard2.pce"
description: "CD-ROM System Card 2.0 (Japan)" description: "CD-ROM System Card 2.0 (Japan)"
region: [japan]
required: false required: false
source_ref: "libretro.c:1903" source_ref: "libretro.c:1903"
note: "pce_fast_cdbios = 'System Card 2'." note: "pce_fast_cdbios = 'System Card 2'."
- name: "syscard1.pce" - name: "syscard1.pce"
description: "CD-ROM System Card 1.0 (Japan)" description: "CD-ROM System Card 1.0 (Japan)"
region: [japan]
required: false required: false
source_ref: "libretro.c:1905" source_ref: "libretro.c:1905"
note: "pce_fast_cdbios = 'System Card 1'." note: "pce_fast_cdbios = 'System Card 1'."
- name: "gexpress.pce" - name: "gexpress.pce"
description: "Games Express CD Card" description: "Games Express CD Card"
region: [japan]
required: false required: false
aliases: ["gecard.pce"] aliases: ["gecard.pce"]
source_ref: "libretro.c:1906-1907" source_ref: "libretro.c:1906-1907"
@@ -52,14 +48,12 @@ files:
- name: "syscard3u.pce" - name: "syscard3u.pce"
description: "CD-ROM System Card 3.0 (US / TurboGrafx-CD)" description: "CD-ROM System Card 3.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false required: false
source_ref: "libretro.c:1908-1909" source_ref: "libretro.c:1908-1909"
note: "pce_fast_cdbios = 'System Card 3 US'. Not in .info." note: "pce_fast_cdbios = 'System Card 3 US'. Not in .info."
- name: "syscard2u.pce" - name: "syscard2u.pce"
description: "CD-ROM System Card 2.0 (US / TurboGrafx-CD)" description: "CD-ROM System Card 2.0 (US / TurboGrafx-CD)"
region: [north-america]
required: false required: false
source_ref: "libretro.c:1910-1911" source_ref: "libretro.c:1910-1911"
note: "pce_fast_cdbios = 'System Card 2 US'. Not in .info." note: "pce_fast_cdbios = 'System Card 2 US'. Not in .info."
+9 -9
View File
@@ -30,7 +30,7 @@ notes: |
mgba_skip_bios (default OFF) - skip the boot animation even if loaded mgba_skip_bios (default OFF) - skip the boot animation even if loaded
The libretro port resolves BIOS filenames in retro_load_game() The libretro port resolves BIOS filenames in retro_load_game()
(src/platform/libretro/libretro.c:2091-2148). It picks the filename (src/platform/libretro/libretro.c:2060-2118). It picks the filename
based on detected model: based on detected model:
GBA -> gba_bios.bin GBA -> gba_bios.bin
GB (DMG/MGB) -> gb_bios.bin GB (DMG/MGB) -> gb_bios.bin
@@ -38,7 +38,7 @@ notes: |
SGB -> sgb_bios.bin SGB -> sgb_bios.bin
SGB2 -> gb_bios.bin (falls to default, missing case) SGB2 -> gb_bios.bin (falls to default, missing case)
The standalone core (src/gb/core.c:637-684) correctly maps SGB2 to The standalone core (src/gb/core.c:649-696) correctly maps SGB2 to
sgb_bios.bin (marked TODO for a dedicated file). The libretro port sgb_bios.bin (marked TODO for a dedicated file). The libretro port
omits GB_MODEL_SGB2 from its switch, so SGB2 falls to default. omits GB_MODEL_SGB2 from its switch, so SGB2 falls to default.
@@ -57,7 +57,7 @@ files:
size: 16384 # 16 KB (0x4000, GBA_SIZE_BIOS) size: 16384 # 16 KB (0x4000, GBA_SIZE_BIOS)
validation: [size] validation: [size]
note: "GBA BIOS. HLE replacement built in. Real BIOS adds boot logo and full SWI accuracy." note: "GBA BIOS. HLE replacement built in. Real BIOS adds boot logo and full SWI accuracy."
source_ref: "src/platform/libretro/libretro.c:2099" source_ref: "src/platform/libretro/libretro.c:2068"
# ------------------------------------------------------- # -------------------------------------------------------
# Game Boy - boot ROM (optional, skip fallback) # Game Boy - boot ROM (optional, skip fallback)
@@ -68,7 +68,7 @@ files:
size: 256 # 256 B (0x100) size: 256 # 256 B (0x100)
validation: [crc32] validation: [crc32]
note: "DMG/MGB boot ROM. Scrolling Nintendo logo. Skipped if absent." note: "DMG/MGB boot ROM. Scrolling Nintendo logo. Skipped if absent."
source_ref: "src/platform/libretro/libretro.c:971" source_ref: "src/platform/libretro/libretro.c:2104"
# ------------------------------------------------------- # -------------------------------------------------------
# Game Boy Color - boot ROM (optional, skip fallback) # Game Boy Color - boot ROM (optional, skip fallback)
@@ -79,7 +79,7 @@ files:
size: 2304 # 2304 B (0x900) size: 2304 # 2304 B (0x900)
validation: [crc32] validation: [crc32]
note: "CGB boot ROM. Used for CGB, AGB (GBA in GB mode), and SCGB models." note: "CGB boot ROM. Used for CGB, AGB (GBA in GB mode), and SCGB models."
source_ref: "src/platform/libretro/libretro.c:2127" source_ref: "src/platform/libretro/libretro.c:2097"
# ------------------------------------------------------- # -------------------------------------------------------
# Super Game Boy - boot ROM (optional, skip fallback) # Super Game Boy - boot ROM (optional, skip fallback)
@@ -89,14 +89,14 @@ files:
required: false required: false
size: 256 # 256 B (0x100) size: 256 # 256 B (0x100)
validation: [crc32] validation: [crc32]
note: "SGB boot ROM. SGB2 maps here in standalone (src/gb/core.c:643) but falls to gb_bios.bin in libretro." note: "SGB boot ROM. SGB2 maps here in standalone (src/gb/core.c:655) but falls to gb_bios.bin in libretro."
source_ref: "src/platform/libretro/libretro.c:2130" source_ref: "src/platform/libretro/libretro.c:2100"
platform_details: platform_details:
gba: gba:
bios_size: 16384 # 16 KB bios_size: 16384 # 16 KB
hle_bios: true hle_bios: true
source_ref: "src/gba/hle-bios.c, src/gba/gba.c:520-551" source_ref: "src/gba/hle-bios.c, src/gba/gba.c:513-552"
note: | note: |
GBALoadBIOS() validates size (must be exactly 0x4000) then computes GBALoadBIOS() validates size (must be exactly 0x4000) then computes
an internal checksum. Two official checksums are recognized: an internal checksum. Two official checksums are recognized:
@@ -125,7 +125,7 @@ platform_details:
sgb: sgb:
bios_size: 256 # 256 B bios_size: 256 # 256 B
hle_bios: false hle_bios: false
source_ref: "src/gb/gb.c:570-605, src/gb/core.c:642-644" source_ref: "src/gb/gb.c:570-605, src/gb/core.c:654-656"
note: | note: |
Accepted SGB CRC32 checksums: Accepted SGB CRC32 checksums:
SGB = 0xEC8A83B9, SGB2 = 0x53D0DD63 SGB = 0xEC8A83B9, SGB2 = 0x53D0DD63
+3 -3
View File
@@ -98,7 +98,7 @@ notes: |
MSX.emu/src/blueMSX/Memory/romMapperDRAM.c:88-99), and the Philips FDC, Panasonic MSX.emu/src/blueMSX/Memory/romMapperDRAM.c:88-99), and the Philips FDC, Panasonic
FDC and MoonSound mappers test nothing at all FDC and MoonSound mappers test nothing at all
(MSX.emu/src/blueMSX/Memory/romMapperPhilipsFdc.c:139-146, (MSX.emu/src/blueMSX/Memory/romMapperPhilipsFdc.c:139-146,
MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:236-262, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:228-255,
MSX.emu/src/blueMSX/Memory/romMapperMoonsound.c:129-158). MSX.emu/src/blueMSX/Memory/romMapperMoonsound.c:129-158).
The ROM database is compiled in. mediaDbGuessRom matches a cartridge's SHA-1 against The ROM database is compiled in. mediaDbGuessRom matches a cartridge's SHA-1 against
@@ -396,7 +396,7 @@ files:
required: true required: true
size: 16384 size: 16384
description: "Panasonic floppy disk ROM of the MSX2+ machine" description: "Panasonic floppy disk ROM of the MSX2+ machine"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:236-262" source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:228-255"
- name: "MSXTR.rom" - name: "MSXTR.rom"
path: "Machines/Shared Roms/MSXTR.rom" path: "Machines/Shared Roms/MSXTR.rom"
@@ -436,7 +436,7 @@ files:
required: true required: true
size: 65536 size: 65536
description: "MSX-DOS 2.3 disk ROM of the turbo R machine" description: "MSX-DOS 2.3 disk ROM of the turbo R machine"
source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:236-262" source_ref: "MSX.emu/src/blueMSX/Board/Machine.c:300-339,450-468,1168-1181, MSX.emu/src/main/RomLoader.cc:57-92,94-133, MSX.emu/src/blueMSX/Memory/romMapperTC8566AF.c:228-255"
- name: "coleco.rom" - name: "coleco.rom"
path: "Machines/COL - ColecoVision/coleco.rom" path: "Machines/COL - ColecoVision/coleco.rom"
+6 -4
View File
@@ -151,16 +151,18 @@ files:
source_ref: "cbus/ideio.c:1928-1930, cbus/ideio.c:1944-1981" source_ref: "cbus/ideio.c:1928-1930, cbus/ideio.c:1944-1981"
# -- SCSI BIOS ROM -- # -- SCSI BIOS ROM --
# Loaded by scsiio.c:219. 16 KB (0x4000). Falls back to scsibios[] stub. # Read into a 0x4000 buffer; any non-empty read is kept. Falls back to the
# scsibios[] stub.
- name: "scsi.rom" - name: "scsi.rom"
path: "np2kai/scsi.rom" path: "np2kai/scsi.rom"
size: 16384 max_size: 16384
required: false required: false
hle_fallback: true hle_fallback: true
note: > note: >
SCSI controller BIOS ROM (16 KB). The core includes a built-in SCSI controller BIOS ROM, up to 16 KB: the full-size test is commented
out and a shorter file is used as read. The core includes a built-in
SCSI BIOS stub as fallback. SCSI BIOS stub as fallback.
source_ref: "cbus/scsiio.c:214-231" source_ref: "cbus/scsiio.c:720-732"
# -- SASI BIOS ROM -- # -- SASI BIOS ROM --
# Loaded by sasiio.c:455. 4 KB (0x1000). Falls back to sasibios[] stub. # Loaded by sasiio.c:455. 4 KB (0x1000). Falls back to sasibios[] stub.
+1 -1
View File
@@ -84,7 +84,7 @@ platform_details:
size: 4096 size: 4096
load_priority: load_priority:
source_ref: "source/PokeMini.c:515-523" source_ref: "source/PokeMini.c:500-509"
note: | note: |
During PokeMini_Reset(), the core first reloads FreeBIOS as a During PokeMini_Reset(), the core first reloads FreeBIOS as a
safe baseline, then attempts to load bios.min from disk. If safe baseline, then attempts to load bios.min from disk. If
-33
View File
@@ -37,7 +37,6 @@ files:
region: [north-america] region: [north-america]
size: 2097152 size: 2097152
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "GameCube NTSC-U boot ROM. HLE available but real IPL needed for GC menu boot and accurate fonts" note: "GameCube NTSC-U boot ROM. HLE available but real IPL needed for GC menu boot and accurate fonts"
source_ref: "Source/Core/Common/CommonPaths.h:138, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:109-110" source_ref: "Source/Core/Common/CommonPaths.h:138, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:109-110"
@@ -47,7 +46,6 @@ files:
region: [europe] region: [europe]
size: 2097152 size: 2097152
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "GameCube PAL boot ROM" note: "GameCube PAL boot ROM"
source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:184" source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:184"
@@ -57,7 +55,6 @@ files:
region: [japan] region: [japan]
size: 2097152 size: 2097152
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "GameCube NTSC-J boot ROM. JAP is the legacy directory name" note: "GameCube NTSC-J boot ROM. JAP is the legacy directory name"
source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:186" source_ref: "Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:186"
@@ -70,7 +67,6 @@ files:
path: "GC/dsp_rom.bin" path: "GC/dsp_rom.bin"
size: 8192 size: 8192
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
validation: [size] validation: [size]
known_hash_adler32: "0x66f334fe" known_hash_adler32: "0x66f334fe"
@@ -82,7 +78,6 @@ files:
path: "GC/dsp_coef.bin" path: "GC/dsp_coef.bin"
size: 4096 size: 4096
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
validation: [size] validation: [size]
known_hash_adler32: "0xf3b93527" known_hash_adler32: "0xf3b93527"
@@ -98,7 +93,6 @@ files:
path: "GC/font_western.bin" path: "GC/font_western.bin"
size: 9589 size: 9589
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Windows-1252 font for GC/Wii text rendering. Free alternative bundled, real one from IPL dump preferred" note: "Windows-1252 font for GC/Wii text rendering. Free alternative bundled, real one from IPL dump preferred"
source_ref: "Source/Core/Common/CommonPaths.h:132, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:131,203-218" source_ref: "Source/Core/Common/CommonPaths.h:132, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:131,203-218"
@@ -107,7 +101,6 @@ files:
path: "GC/font_japanese.bin" path: "GC/font_japanese.bin"
size: 303693 size: 303693
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Shift-JIS font for Japanese text. Free alternative bundled, real one from IPL dump preferred" note: "Shift-JIS font for Japanese text. Free alternative bundled, real one from IPL dump preferred"
source_ref: "Source/Core/Common/CommonPaths.h:135, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:130" source_ref: "Source/Core/Common/CommonPaths.h:135, Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:130"
@@ -118,7 +111,6 @@ files:
- name: "gba_bios.bin" - name: "gba_bios.bin"
path: "GBA/gba_bios.bin" path: "GBA/gba_bios.bin"
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "GBA BIOS for GC-GBA link feature (uses integrated mGBA). Path configurable in settings" note: "GBA BIOS for GC-GBA link feature (uses integrated mGBA). Path configurable in settings"
source_ref: "Source/Core/Common/CommonPaths.h:144, Source/Core/Core/HW/GBACore.cpp:344-361" source_ref: "Source/Core/Common/CommonPaths.h:144, Source/Core/Core/HW/GBACore.cpp:344-361"
@@ -127,7 +119,6 @@ files:
- name: "SYSCONF" - name: "SYSCONF"
path: "Wii/shared2/sys/SYSCONF" path: "Wii/shared2/sys/SYSCONF"
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Wii system configuration. Auto-generated by PrimeHack, can be imported from NAND backup" note: "Wii system configuration. Auto-generated by PrimeHack, can be imported from NAND backup"
source_ref: "Source/Core/Common/CommonPaths.h:118, Source/Core/Core/WiiRoot.cpp:266" source_ref: "Source/Core/Common/CommonPaths.h:118, Source/Core/Core/WiiRoot.cpp:266"
@@ -136,7 +127,6 @@ files:
path: "Wii/title/00000001/00000002/data/setting.txt" path: "Wii/title/00000001/00000002/data/setting.txt"
size: 256 size: 256
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Wii region/language settings. Auto-generated during Wii boot emulation" note: "Wii region/language settings. Auto-generated during Wii boot emulation"
source_ref: "Source/Core/Common/CommonPaths.h:152, Source/Core/Core/Boot/Boot_BS2Emu.cpp:377-454" source_ref: "Source/Core/Common/CommonPaths.h:152, Source/Core/Core/Boot/Boot_BS2Emu.cpp:377-454"
@@ -145,7 +135,6 @@ files:
- name: "nand.bin" - name: "nand.bin"
path: null path: null
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu" note: "BootMii NAND backup. Can be imported to populate Wii NAND with channels, saves, system menu"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:26-89" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:26-89"
@@ -154,7 +143,6 @@ files:
path: null path: null
size: 1024 size: 1024
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import" note: "OTP/SEEPROM dump (Wii encryption keys). Needed if not appended to nand.bin for NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:19,76-88" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:19,76-88"
@@ -163,7 +151,6 @@ files:
- name: "WiiSD.raw" - name: "WiiSD.raw"
path: "Load/WiiSD.raw" path: "Load/WiiSD.raw"
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Virtual SD card image for Wii homebrew. Auto-created, supports SD/SDHC up to 4GB" note: "Virtual SD card image for Wii homebrew. Auto-created, supports SD/SDHC up to 4GB"
source_ref: "Source/Core/Common/CommonPaths.h:149" source_ref: "Source/Core/Common/CommonPaths.h:149"
@@ -172,7 +159,6 @@ files:
- name: "codehandler.bin" - name: "codehandler.bin"
path: null path: null
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Gecko/Ocarina cheat code handler. Shipped with PrimeHack in Sys/" note: "Gecko/Ocarina cheat code handler. Shipped with PrimeHack in Sys/"
source_ref: "Source/Core/Common/CommonPaths.h:154, Source/Core/Core/GeckoCode.cpp:121" source_ref: "Source/Core/Common/CommonPaths.h:154, Source/Core/Core/GeckoCode.cpp:121"
@@ -181,7 +167,6 @@ files:
- name: "Wii System Menu" - name: "Wii System Menu"
path: null path: null
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "Wii System Menu WAD. Installed to NAND via Tools > Install WAD, needed for Wii Menu boot" note: "Wii System Menu WAD. Installed to NAND via Tools > Install WAD, needed for Wii Menu boot"
source_ref: "Source/Core/DolphinQt/MenuBar.cpp:335, Source/Core/DolphinQt/MenuBar.cpp:1582-1590" source_ref: "Source/Core/DolphinQt/MenuBar.cpp:335, Source/Core/DolphinQt/MenuBar.cpp:1582-1590"
@@ -190,7 +175,6 @@ files:
- name: "clientca.pem" - name: "clientca.pem"
path: "Wii/clientca.pem" path: "Wii/clientca.pem"
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import" note: "SSL client certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:201-285" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:201-285"
@@ -198,7 +182,6 @@ files:
- name: "clientcakey.pem" - name: "clientcakey.pem"
path: "Wii/clientcakey.pem" path: "Wii/clientcakey.pem"
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "SSL client private key. Auto-extracted from IOS13 content during NAND import" note: "SSL client private key. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:237" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:237"
@@ -206,7 +189,6 @@ files:
- name: "rootca.pem" - name: "rootca.pem"
path: "Wii/rootca.pem" path: "Wii/rootca.pem"
required: false required: false
mode: standalone
hle_fallback: true hle_fallback: true
note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import" note: "SSL root CA certificate. Auto-extracted from IOS13 content during NAND import"
source_ref: "Source/Core/DiscIO/NANDImporter.cpp:238" source_ref: "Source/Core/DiscIO/NANDImporter.cpp:238"
@@ -220,105 +202,90 @@ files:
- name: "rtl8723a_fw.bin" - name: "rtl8723a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723a_fw.bin" path: "Load/Firmware/rtl_bt/rtl8723a_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8723A BT firmware for Wiimote passthrough" note: "Realtek 8723A BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:379" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:379"
- name: "rtl8723b_fw.bin" - name: "rtl8723b_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723b_fw.bin" path: "Load/Firmware/rtl_bt/rtl8723b_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8723B BT firmware for Wiimote passthrough" note: "Realtek 8723B BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:516-519" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:516-519"
- name: "rtl8723d_fw.bin" - name: "rtl8723d_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8723d_fw.bin" path: "Load/Firmware/rtl_bt/rtl8723d_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8723D BT firmware for Wiimote passthrough" note: "Realtek 8723D BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:383" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:383"
- name: "rtl8761a_fw.bin" - name: "rtl8761a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8761a_fw.bin" path: "Load/Firmware/rtl_bt/rtl8761a_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8761A BT firmware for Wiimote passthrough" note: "Realtek 8761A BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:385" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:385"
- name: "rtl8761bu_fw.bin" - name: "rtl8761bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8761bu_fw.bin" path: "Load/Firmware/rtl_bt/rtl8761bu_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8761BU BT firmware for Wiimote passthrough" note: "Realtek 8761BU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:387" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:387"
- name: "rtl8821a_fw.bin" - name: "rtl8821a_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8821a_fw.bin" path: "Load/Firmware/rtl_bt/rtl8821a_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8821A BT firmware for Wiimote passthrough" note: "Realtek 8821A BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:389" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:389"
- name: "rtl8821c_fw.bin" - name: "rtl8821c_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8821c_fw.bin" path: "Load/Firmware/rtl_bt/rtl8821c_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8821C BT firmware for Wiimote passthrough" note: "Realtek 8821C BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:391" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:391"
- name: "rtl8822b_fw.bin" - name: "rtl8822b_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8822b_fw.bin" path: "Load/Firmware/rtl_bt/rtl8822b_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8822B BT firmware for Wiimote passthrough" note: "Realtek 8822B BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:395" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:395"
- name: "rtl8822cu_fw.bin" - name: "rtl8822cu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8822cu_fw.bin" path: "Load/Firmware/rtl_bt/rtl8822cu_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8822CU BT firmware for Wiimote passthrough" note: "Realtek 8822CU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:393" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:393"
- name: "rtl8851bu_fw.bin" - name: "rtl8851bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8851bu_fw.bin" path: "Load/Firmware/rtl_bt/rtl8851bu_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8851BU BT firmware for Wiimote passthrough" note: "Realtek 8851BU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:397" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:397"
- name: "rtl8852au_fw.bin" - name: "rtl8852au_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852au_fw.bin" path: "Load/Firmware/rtl_bt/rtl8852au_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8852AU BT firmware for Wiimote passthrough" note: "Realtek 8852AU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:399" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:399"
- name: "rtl8852bu_fw.bin" - name: "rtl8852bu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852bu_fw.bin" path: "Load/Firmware/rtl_bt/rtl8852bu_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8852BU BT firmware for Wiimote passthrough" note: "Realtek 8852BU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:401" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:401"
- name: "rtl8852cu_fw.bin" - name: "rtl8852cu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852cu_fw.bin" path: "Load/Firmware/rtl_bt/rtl8852cu_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8852CU BT firmware for Wiimote passthrough" note: "Realtek 8852CU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:403" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:403"
- name: "rtl8852btu_fw.bin" - name: "rtl8852btu_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8852btu_fw.bin" path: "Load/Firmware/rtl_bt/rtl8852btu_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8852BT/8852BE-VT BT firmware for Wiimote passthrough" note: "Realtek 8852BT/8852BE-VT BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:405" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:405"
- name: "rtl8922au_fw.bin" - name: "rtl8922au_fw.bin"
path: "Load/Firmware/rtl_bt/rtl8922au_fw.bin" path: "Load/Firmware/rtl_bt/rtl8922au_fw.bin"
required: false required: false
mode: standalone
note: "Realtek 8922AU BT firmware for Wiimote passthrough" note: "Realtek 8922AU BT firmware for Wiimote passthrough"
source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:407" source_ref: "Source/Core/Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp:407"
-3
View File
@@ -21,21 +21,18 @@ files:
- name: "prod.keys" - name: "prod.keys"
required: true required: true
path: "switch/" path: "switch/"
mode: standalone
note: "Switch production keys for NCA decryption" note: "Switch production keys for NCA decryption"
source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:239" source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:239"
- name: "title.keys" - name: "title.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Per-title decryption keys, needed for specific game content" note: "Per-title decryption keys, needed for specific game content"
source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:240" source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:240"
- name: "console.keys" - name: "console.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Console-specific keys for encrypted content" note: "Console-specific keys for encrypted content"
source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:241" source_ref: "src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs:241"
-4
View File
@@ -26,21 +26,18 @@ files:
- name: "prod.keys" - name: "prod.keys"
required: true required: true
path: "switch/" path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)" note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:654" source_ref: "src/core/crypto/key_manager.cpp:654"
- name: "title.keys" - name: "title.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)" note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:657" source_ref: "src/core/crypto/key_manager.cpp:657"
- name: "console.keys" - name: "console.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)" note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:658" source_ref: "src/core/crypto/key_manager.cpp:658"
@@ -48,7 +45,6 @@ files:
required: false required: false
path: "switch/" path: "switch/"
size: 160 size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)" note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294" source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294"
-4
View File
@@ -27,21 +27,18 @@ files:
- name: "prod.keys" - name: "prod.keys"
required: true required: true
path: "switch/" path: "switch/"
mode: standalone
note: "Production keys for NCA decryption (master, key area, header, titlekek)" note: "Production keys for NCA decryption (master, key area, header, titlekek)"
source_ref: "src/core/crypto/key_manager.cpp:655-656" source_ref: "src/core/crypto/key_manager.cpp:655-656"
- name: "title.keys" - name: "title.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Per-title encryption keys (rights_id to titlekey mappings)" note: "Per-title encryption keys (rights_id to titlekey mappings)"
source_ref: "src/core/crypto/key_manager.cpp:659-660" source_ref: "src/core/crypto/key_manager.cpp:659-660"
- name: "console.keys" - name: "console.keys"
required: false required: false
path: "switch/" path: "switch/"
mode: standalone
note: "Console-specific keys (BIS, SD seed)" note: "Console-specific keys (BIS, SD seed)"
source_ref: "src/core/crypto/key_manager.cpp:661-662" source_ref: "src/core/crypto/key_manager.cpp:661-662"
@@ -49,7 +46,6 @@ files:
required: false required: false
path: "switch/" path: "switch/"
size: 160 size: 160
mode: standalone
note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)" note: "Amiibo decryption keys (two InternalKey structs, 0x50 bytes each)"
source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294" source_ref: "src/core/hle/service/nfc/common/amiibo_crypto.cpp:273-294"
+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60" $defaultInstallSha256 = "60e18da18b5a9a43b18d37020b676ad47babd6d12c5314e8fcc1370e4864c4e5"
$maximumInstallerBytes = 2MB $maximumInstallerBytes = 2MB
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
$temporary = $null $temporary = $null
+31 -2
View File
@@ -103,8 +103,35 @@ DEFAULT_DESTS = {
"rocknix": Path("/storage/roms/bios"), "rocknix": Path("/storage/roms/bios"),
"misterfpga": Path("/media/fat/games"), "misterfpga": Path("/media/fat/games"),
"retropie": Path.home() / "RetroPie" / "BIOS", "retropie": Path.home() / "RetroPie" / "BIOS",
"romm": Path("/romm/library/bios"),
} }
def _os_default_dests(os_type: str) -> dict[str, Path]:
"""Where a platform keeps its BIOS on this OS, when it is not detected.
The same folders the platform's detection looks for: a platform named on
the command line but not found must not land in ~/bios, which none of
them reads.
"""
home = Path.home()
if os_type == "windows":
profile = Path(os.environ.get("USERPROFILE", str(home)))
appdata = Path(os.environ.get("APPDATA", str(home / "AppData" / "Roaming")))
return {
"retroarch": appdata / "RetroArch" / "system",
"retrobat": profile / "RetroBat" / "bios",
"bizhawk": profile / "BizHawk" / "Firmware",
}
if os_type == "darwin":
return {
"retroarch": home / "Library" / "Application Support" / "RetroArch" / "system",
}
return {
"retroarch": home / ".config" / "retroarch" / "system",
"bizhawk": home / ".config" / "BizHawk" / "Firmware",
}
# Set when the run is interrupted, so queued downloads end instead of draining # Set when the run is interrupted, so queued downloads end instead of draining
# the pool: every future is submitted before the first one completes. # the pool: every future is submitted before the first one completes.
_stop = threading.Event() _stop = threading.Event()
@@ -1385,7 +1412,7 @@ def _prompt_manual_platform(os_type: str) -> list[tuple[str, Path]]:
plat = AVAILABLE_PLATFORMS[int(choice) - 1] plat = AVAILABLE_PLATFORMS[int(choice) - 1]
elif choice in AVAILABLE_PLATFORMS: elif choice in AVAILABLE_PLATFORMS:
plat = choice plat = choice
default_dest = DEFAULT_DESTS.get(plat, Path.home() / "bios") default_dest = _default_dest(os_type, plat)
try: try:
raw = input(f"BIOS directory [{default_dest}]: ").strip() raw = input(f"BIOS directory [{default_dest}]: ").strip()
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
@@ -1405,7 +1432,9 @@ def _default_dest(os_type: str, platform_name: str) -> Path:
""" """
if os_type == "android" and platform_name == "retroarch": if os_type == "android" and platform_name == "retroarch":
return _android_storage_root() / "RetroArch" / "system" return _android_storage_root() / "RetroArch" / "system"
return DEFAULT_DESTS.get(platform_name, Path.home() / "bios") return _os_default_dests(os_type).get(
platform_name, DEFAULT_DESTS.get(platform_name, Path.home() / "bios")
)
def _manual_usage_hint(os_type: str) -> list[str]: def _manual_usage_hint(os_type: str) -> list[str]:
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER="" TEMP_INSTALLER=""
TEMP_DIRECTORY="" TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="42b1747ae867d41da39b11daf783c0106daa6ed18861a2214c196f3a5c9a3c60" DEFAULT_INSTALL_SHA256="60e18da18b5a9a43b18d37020b676ad47babd6d12c5314e8fcc1370e4864c4e5"
MAX_INSTALLER_BYTES=2097152 MAX_INSTALLER_BYTES=2097152
cleanup() { cleanup() {
+15 -15
View File
@@ -5,7 +5,7 @@
"platform": "batocera", "platform": "batocera",
"display_name": "Batocera", "display_name": "Batocera",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T06:50:25Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -238,9 +238,9 @@
} }
], ],
"total_files": 12826, "total_files": 12826,
"total_size": 8065364335, "total_size": 8065338769,
"pack_files": 12828, "pack_files": 12828,
"pack_size": 8065364335, "pack_size": 8065338769,
"total_omitted": 30, "total_omitted": 30,
"omitted_files": [ "omitted_files": [
{ {
@@ -40969,20 +40969,20 @@
}, },
{ {
"dest": "switch/prod.keys", "dest": "switch/prod.keys",
"sha1": "d6cedce971a2a4ab2588e39285a9d2fb37b62924", "sha1": "c146b651696d25287241f4bab18226e488bc45b3",
"sha256": "76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990", "sha256": "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112",
"size": 15039, "size": 16025,
"repo_path": "bios/Arcade/Arcade/switch/prod.keys", "repo_path": "bios/Nintendo/Switch/prod.keys",
"cores": [ "cores": [
"citron" "citron"
] ]
}, },
{ {
"dest": "switch/title.keys", "dest": "switch/title.keys",
"sha1": "8e6c2873d671f482d3fe0f7b96e18e0f84d02c10", "sha1": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
"sha256": "dc6e31aae8daa9067dafcf2bca6d9d3aa691a63474eb698ad8c54be3e360753b", "sha256": "3ed66f7e82df79922739c6c2d34de90bc36a48d13acdff24f9d51013bd8c0677",
"size": 19584, "size": 1224,
"repo_path": "bios/Arcade/Arcade/switch/title.keys", "repo_path": "bios/Nintendo/Switch/title.keys",
"cores": [ "cores": [
"citron" "citron"
] ]
@@ -85625,10 +85625,10 @@
}, },
{ {
"dest": "np2kai/scsi.rom", "dest": "np2kai/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d", "sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5", "sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 16384, "size": 8192,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom", "repo_path": "bios/NEC/PC-98/scsi.rom",
"cores": [ "cores": [
"np2kai" "np2kai"
] ]
+1555 -185
View File
File diff suppressed because it is too large. Load diff
+531 -31
View File
@@ -5,7 +5,7 @@
"platform": "emudeck", "platform": "emudeck",
"display_name": "EmuDeck", "display_name": "EmuDeck",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T10:11:17Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -250,10 +250,10 @@
} }
} }
], ],
"total_files": 569, "total_files": 619,
"total_size": 3452229095, "total_size": 3473031414,
"pack_files": 571, "pack_files": 621,
"pack_size": 3452229095, "pack_size": 3473031414,
"total_omitted": 0, "total_omitted": 0,
"omitted_files": [], "omitted_files": [],
"files": [ "files": [
@@ -331,18 +331,18 @@
}, },
{ {
"dest": "disksys.rom", "dest": "disksys.rom",
"sha1": "57fe1bdee955bb48d357e463ccbf129496930b62", "sha1": "af5af53f66982e749643fdf8b2acbb7d4d3ed229",
"sha256": "99c18490ed9002d9c6d999b9d8d15be5c051bdfa7cc7e73318053c9a994b0178", "sha256": "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72",
"size": 8192, "size": 8192,
"repo_path": "bios/Arcade/FBNeo/fdsbios.nes", "repo_path": "bios/Nintendo/Famicom Disk System/.variants/disksys.rom.af5af53f",
"cores": null "cores": null
}, },
{ {
"dest": "prod.keys", "dest": "prod.keys",
"sha1": "d6cedce971a2a4ab2588e39285a9d2fb37b62924", "sha1": "c146b651696d25287241f4bab18226e488bc45b3",
"sha256": "76df55f85c6628cd924f227a72a301bbb731833c0a0f00c440e01e6b34480990", "sha256": "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112",
"size": 15039, "size": 16025,
"repo_path": "bios/Arcade/Arcade/switch/prod.keys", "repo_path": "bios/Nintendo/Switch/prod.keys",
"cores": null "cores": null
}, },
{ {
@@ -387,26 +387,26 @@
}, },
{ {
"dest": "bios_CD_E.bin", "dest": "bios_CD_E.bin",
"sha1": "f891e0ea651e2232af0c5c4cb46a0cae2ee8f356", "sha1": "7063192ae9f6b696c5b81bc8f0a9fe6f0c400e58",
"sha256": "16735df92fcc1d0fc6a1eced244867e95281c5006340926082b74e363e073dc4", "sha256": "fe608a2a07676a23ab5fd5eee2f53c9e2526d69a28aa16ccd85c0ec42e6933cb",
"size": 131072, "size": 131072,
"repo_path": "bios/Sega/Mega CD/MCD_eu_100.bin", "repo_path": "bios/Sega/Mega CD/eu_mcd2_9303.bin",
"cores": null "cores": null
}, },
{ {
"dest": "bios_CD_U.bin", "dest": "bios_CD_U.bin",
"sha1": "f4f315adcef9b8feb0364c21ab7f0eaf5457f3ed", "sha1": "5a8c4b91d3034c1448aac4b5dc9a6484fce51636",
"sha256": "807384678ef259494d943eddb6954dfc7069693d0dde0c424e2028488d88e81a", "sha256": "fb477cdbf94c84424c2feca4fe40656d85393fe7b7b401911b45ad2eb991258c",
"size": 131072, "size": 131072,
"repo_path": "bios/Sega/Mega CD/bios_CD_U.bin", "repo_path": "bios/Sega/Mega CD/SegaCDBIOS9303.bin",
"cores": null "cores": null
}, },
{ {
"dest": "bios_CD_J.bin", "dest": "bios_CD_J.bin",
"sha1": "4846f448160059a7da0215a5df12ca160f26dd69", "sha1": "d203cfe22c03ae479dd8ca33840cf8d9776eb3ff",
"sha256": "923eb9da740f98ab1572085e3d351e2c0982df0906b0b99efb68a9997c94cf8f", "sha256": "7133fc2dd2fe5b7d0acd53a5f10f3d00b5d31270239ad20d74ef32393e24af88",
"size": 131072, "size": 131072,
"repo_path": "bios/Sega/Mega CD/MCD_jp_100p.bin", "repo_path": "bios/Sega/Mega CD/jp_mcd2_921222.bin",
"cores": null "cores": null
}, },
{ {
@@ -529,6 +529,316 @@
"repo_path": "bios/Microsoft/Xbox/Complex_4627.bin", "repo_path": "bios/Microsoft/Xbox/Complex_4627.bin",
"cores": null "cores": null
}, },
{
"dest": "gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
"sha256": "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570",
"size": 16384,
"repo_path": "bios/Nintendo/Game Boy Advance/GBA_bios.rom",
"cores": [
"ares"
]
},
{
"dest": "bios.gg",
"sha1": "914aa165e3d879f060be77870d345b60cfeb4ede",
"sha256": "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2",
"size": 1024,
"repo_path": "bios/Sega/Game Gear/GG_majesco.gg",
"cores": [
"ares"
]
},
{
"dest": "bios_U.sms",
"sha1": "c315672807d8ddb8d91443729405c766dd95cae7",
"sha256": "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50",
"size": 8192,
"repo_path": "bios/Other/zesarux/sms.rom",
"cores": [
"ares"
]
},
{
"dest": "bios_J.sms",
"sha1": "a8c1b39a2e41137835eda6a5de6d46dd9fadbaf2",
"sha256": "67846e26764bd862f19179294347f7353a4166b62ac4198a5ec32933b7da486e",
"size": 8192,
"repo_path": "bios/Sega/Master System/MasterSystem/japanese-bios.sms",
"cores": [
"ares"
]
},
{
"dest": "bios_E.sms",
"sha1": "c315672807d8ddb8d91443729405c766dd95cae7",
"sha256": "477617917a12a30f9f43844909dc2de6e6a617430f5c9a36306c86414a670d50",
"size": 8192,
"repo_path": "bios/Other/zesarux/sms.rom",
"cores": [
"ares"
]
},
{
"dest": "mega_ld_bios_U.bin",
"sha1": "aa811861f8874775075bd3f53008c8aaf59b07db",
"sha256": "e89b5a319f66406611ec82fe5c4aa6827c175a05135bd7bd177366cba0465021",
"size": 131072,
"repo_path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.04 (1993)(Pioneer - Sega)(US).bin",
"cores": [
"ares"
]
},
{
"dest": "mega_ld_bios_J.bin",
"sha1": "26237b333db4a4c6770297fa5e655ea95840d5d9",
"sha256": "dca942d977217f703d8d1c6eb1aeb6b32c78ecc421486bbb46c459d385161c94",
"size": 131072,
"repo_path": "bios/Pioneer/LaserActive/Pioneer LaserActive Sega PAC Boot ROM v1.02 (1993)(Pioneer - Sega)(JP)(en-ja).bin",
"cores": [
"ares"
]
},
{
"dest": "MSX.ROM",
"sha1": "302afb5d8be26c758309ca3df611ae69cced2821",
"sha256": "413a2b601a94b3792e054be2439cc77a1819cceadbfa9542f88d51c7480f2ef0",
"size": 32768,
"repo_path": "bios/Microsoft/MSX/CLK/msx-japanese.rom",
"cores": [
"ares"
]
},
{
"dest": "MSX2.ROM",
"sha1": "0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd",
"sha256": "0c672d86ead61a97f49a583b88b7c1905da120645cd44f0c9f2baf4f4631e0b1",
"size": 32768,
"repo_path": "bios/Other/msx-emu/Machines/Shared Roms/MSX2J.rom",
"cores": [
"ares"
]
},
{
"dest": "MSX2EXT.ROM",
"sha1": "5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02",
"sha256": "6c6f421a10c428d960b7ecc990f99af1c638147f747bddca7b0bf0e2ab738300",
"size": 16384,
"repo_path": "bios/Microsoft/MSX/MSX2EXT.ROM",
"cores": [
"ares"
]
},
{
"dest": "neo-epo.bin",
"sha1": "1b3b22092f30c4d1b2c15f04d1670eb1e9fbea07",
"sha256": "70f7906d68acbc3630b6e7792ebb485288b05ca72c6d4c243e07e230aae8d1e4",
"size": 131072,
"repo_path": "bios/SNK/Neo Geo/neo-epo.bin",
"cores": [
"ares"
]
},
{
"dest": "sp-45.sp1",
"sha1": "cdf1f49e3ff2bac528c21ed28449cf35b7957dc1",
"sha256": "0fbeee82b463187d6360e92811f1fad58649feb8ab2584c524222d0b5bf53a17",
"size": 524288,
"repo_path": "bios/SNK/Neo Geo/sp-45.sp1",
"cores": [
"ares"
]
},
{
"dest": "ngp_bios.rom",
"sha1": "2f6429b68446536d8b03f35d02f1e98beb6460a0",
"sha256": "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7",
"size": 65536,
"repo_path": "bios/SNK/Neo Geo Pocket/SNK Neo-Geo Pocket BIOS (1998)(SNK)(en-ja).bin",
"cores": [
"ares"
]
},
{
"dest": "ngpc_bios.rom",
"sha1": "edc13192054a59be49c6d55f83b70e2510968e86",
"sha256": "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d",
"size": 65536,
"repo_path": "bios/SNK/Neo Geo Pocket/SNK Neo-Geo Pocket Color BIOS (1999)(SNK)(en-ja).bin",
"cores": [
"ares"
]
},
{
"dest": "64dd_ipl_J.bin",
"sha1": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
"sha256": "806400ec0df94b0755de6c5b8249d6b6a9866124c5ddbdac198bde22499bfb8b",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_v12_JPN.bin",
"cores": [
"ares"
]
},
{
"dest": "64dd_ipl_U.bin",
"sha1": "3c5b93ca231550c68693a14f03cea8d5dbd1be9e",
"sha256": "e9fec87a45fba02399e88064b9e2f8cf0f2106e351c58279a87f05da5bc984ad",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_USA.bin",
"cores": [
"ares"
]
},
{
"dest": "64dd_ipl_DEV.bin",
"sha1": "10c4173c2a7eb09c6579818f72ef18fa0b6d32de",
"sha256": "9c2962a8b994a29e4cd04b3a6e4ed730a751414655ab6a9799ebf5fc08b79d44",
"size": 4194304,
"repo_path": "bios/Nintendo/64DD/64DD_IPL_DEV.bin",
"cores": [
"ares"
]
},
{
"dest": "syscard1.pce",
"sha1": "a39a66da7de6ba94ab84d04eef7afeec7d4ee66a",
"sha256": "afe9f27f91ac918348555b86298b4f984643eafa2773196f2c5441ea84f0c3bb",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard1.pce",
"cores": [
"ares"
]
},
{
"dest": "syscard3.pce",
"sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": [
"ares"
]
},
{
"dest": "syscard3u.pce",
"sha1": "d02611d99921986147c753df14c7349b31d71950",
"sha256": "cadac2725711b3c442bcf237b02f5a5210c96f17625c35fa58f009e0ed39e4db",
"size": 262144,
"repo_path": "bios/NEC/PC Engine/syscard3u.pce",
"cores": [
"ares"
]
},
{
"dest": "games_express.pce",
"sha1": "014881a959e045e00f4db8f52955200865d40280",
"sha256": "4b86bb96a48a4ca8375fc0109631d0b1d64f255a03b01de70594d40788ba6c3d",
"size": 32768,
"repo_path": "bios/NEC/PC Engine/gecard.pce",
"cores": [
"ares"
]
},
{
"dest": "pac-n10.pce",
"sha1": "f7412aa822d70a55b2ff3d7095137263dc54f6b6",
"sha256": "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4",
"size": 524288,
"repo_path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-N10 (US) (v1.02).bin",
"cores": [
"ares"
]
},
{
"dest": "pac-n1.pce",
"sha1": "6973e2593e66fd21627fedccec98d4a364afaaff",
"sha256": "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f",
"size": 524288,
"repo_path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin",
"cores": [
"ares"
]
},
{
"dest": "pce-lp1.pce",
"sha1": "c1b9202cbe072db12114b223a9ba5374b30718fb",
"sha256": "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0",
"size": 524288,
"repo_path": "bios/Pioneer/LaserActive/[BIOS] LaserActive PCE-LP1 (Japan) (v1.02).bin",
"cores": [
"ares"
]
},
{
"dest": "saturn_bios_U.bin",
"sha1": "faa8ea183a6d7bbe5d4e03bb1332519800d3fbc3",
"sha256": "96e106f740ab448cf89f0dd49dfbac7fe5391cb6bd6e14ad5e3061c13330266f",
"size": 524288,
"repo_path": "bios/Sega/Saturn/SAT_1.00-(U+E).bin",
"cores": [
"ares"
]
},
{
"dest": "saturn_bios_J.bin",
"sha1": "df94c5b4d47eb3cc404d88b33a8fda237eaf4720",
"sha256": "dcfef4b99605f872b6c3b6d05c045385cdea3d1b702906a0ed930df7bcb7deac",
"size": 524288,
"repo_path": "bios/Sega/Saturn/SAT_1.01-(J).bin",
"cores": [
"ares"
]
},
{
"dest": "saturn_bios_E.bin",
"sha1": "faa8ea183a6d7bbe5d4e03bb1332519800d3fbc3",
"sha256": "96e106f740ab448cf89f0dd49dfbac7fe5391cb6bd6e14ad5e3061c13330266f",
"size": 524288,
"repo_path": "bios/Sega/Saturn/SAT_1.00-(U+E).bin",
"cores": [
"ares"
]
},
{
"dest": "SGB1.sfc",
"sha1": "973e10840db683cf3faf61bd443090786b3a9f04",
"sha256": "a75160f7b89b1f0e20fd2f6441bb86285c7378db5035ef6885485eaff6059376",
"size": 262144,
"repo_path": "bios/Nintendo/Super Game Boy/SGB1.sfc/program.rom",
"cores": [
"ares"
]
},
{
"dest": "SGB2.sfc",
"sha1": "e5b2922ca137051059e4269b236d07a22c07bc84",
"sha256": "c172498a23d1176672931bab33b629c7d28f914a43dca9e540b8af1b37ccf2c6",
"size": 524288,
"repo_path": "bios/Nintendo/Super Game Boy/SGB2.sfc/program.rom",
"cores": [
"ares"
]
},
{
"dest": "STBIOS.bin",
"sha1": "ef86ea192eed03d5c413fdbbfd46043be1d7a127",
"sha256": "edacb453da14f825f05d1134d6035f4bf034e55f7cfb97c70c4ee107eabc7342",
"size": 262144,
"repo_path": "bios/Nintendo/SuFami Turbo/STBIOS.bin",
"cores": [
"ares"
]
},
{
"dest": "BS-X.bin",
"sha1": "4891d739a8a8b67923681bad4fb67edab2e90e50",
"sha256": "a49827b45ff9ac9cf5b4658190e1428e59251bc82d8a63d8e9e0f71e439f008f",
"size": 1048576,
"repo_path": "bios/Nintendo/SNES/BS-X.bin",
"cores": [
"ares"
]
},
{ {
"dest": "sysdata/shared_font.bin", "dest": "sysdata/shared_font.bin",
"sha1": "60022d6d026c8df950722478aab25702a0dfa067", "sha1": "60022d6d026c8df950722478aab25702a0dfa067",
@@ -699,6 +1009,36 @@
"cemu" "cemu"
] ]
}, },
{
"dest": "switch/title.keys",
"sha1": "bfee8e708dc4c8f27bfe51a12d57caaa0b7f74de",
"sha256": "3ed66f7e82df79922739c6c2d34de90bc36a48d13acdff24f9d51013bd8c0677",
"size": 1224,
"repo_path": "bios/Nintendo/Switch/title.keys",
"cores": [
"citron"
]
},
{
"dest": "switch/console.keys",
"sha1": "721ca59a261d04af13fb84497ad79b6d8c6b7609",
"sha256": "b98c31cc764c49b8259142de3ada5bb7440ce0fd11315ae64294ddb0d56a523d",
"size": 451,
"repo_path": "bios/Other/citron/switch/console.keys",
"cores": [
"citron"
]
},
{
"dest": "switch/key_retail.bin",
"sha1": "bbdbb49a917d14f7a997d327ba40d40c39e606ce",
"sha256": "7f9283654ec1097fbdff31de946651ae60c2854afb544abe8963d389639c710e",
"size": 160,
"repo_path": "bios/Other/citron/switch/key_retail.bin",
"cores": [
"citron"
]
},
{ {
"dest": "GC/dsp_rom.bin", "dest": "GC/dsp_rom.bin",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992", "sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
@@ -1841,6 +2181,16 @@
"duckstation" "duckstation"
] ]
}, },
{
"dest": "switch/dev.keys",
"sha1": "fc916ff462b842983d4061d09230163ac4b38f16",
"sha256": "72685b5ce6c7ea3e18e3e0d98be1a99de8197299ee15429687b27df614ec92b9",
"size": 781,
"repo_path": "bios/Other/eden/switch/dev.keys",
"cores": [
"eden"
]
},
{ {
"dest": "dc/dc_nvmem.bin", "dest": "dc/dc_nvmem.bin",
"sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a", "sha1": "72c65f2a11ee7b72b77b9945187b7a332632e14a",
@@ -3551,16 +3901,6 @@
"melonds" "melonds"
] ]
}, },
{
"dest": "gba_bios.bin",
"sha1": "300c20df6731a33952ded8c436f7f186d25d3492",
"sha256": "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570",
"size": 16384,
"repo_path": "bios/Nintendo/Game Boy Advance/GBA_bios.rom",
"cores": [
"mgba"
]
},
{ {
"dest": "gb_bios.bin", "dest": "gb_bios.bin",
"sha1": "4ed31ec6b0b175bb109c0eb5fd3d193da823339f", "sha1": "4ed31ec6b0b175bb109c0eb5fd3d193da823339f",
@@ -3631,6 +3971,156 @@
"ppsspp" "ppsspp"
] ]
}, },
{
"dest": "Load/Firmware/rtl_bt/rtl8723a_fw.bin",
"sha1": "2937529b25c46e29429811829988f56f28142a41",
"sha256": "756951ee36e417c0714b00547a6175d5d5decbe1e4cf7585f211019150c7cd10",
"size": 24548,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8723a_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8723b_fw.bin",
"sha1": "5c43fb17ef04a2ee13f9e697f685f68f0d0d1096",
"sha256": "c9c8a9f6ebd90b7449a7e4d38ee39952dc7cc47d6c1d054e42cfbb5e4d818853",
"size": 45048,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8723b_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8723d_fw.bin",
"sha1": "a9182178ae400dd68b3e82f5f4c650001e9d1b85",
"sha256": "6b70723b2db9b658cd9535fe36f1494b4494699e1ad07920c9356f8bd90d9263",
"size": 52080,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8723d_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8761a_fw.bin",
"sha1": "7eca505aad586772d66c3d6336d450c591e10670",
"sha256": "581e5e81f5b51a29f475f8a276be95ad3210b3e3065987541c508e7f7a83a462",
"size": 74488,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8761a_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8761bu_fw.bin",
"sha1": "f5bf3a7fc9605ee31b3904d64b89ce0fa3d9eaf5",
"sha256": "1d7a9597349ad89344fa16c1913d3e39e9a12e966e417ca16871bc79bbe59edb",
"size": 44484,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8761bu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8821a_fw.bin",
"sha1": "0183b48cda05bd4aed70b9f0b762eea5d2fc3b1d",
"sha256": "3ed5077f8f02ff2b06be257691dc72061efac976cd2dc174057833624c820b9d",
"size": 37420,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8821a_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8821c_fw.bin",
"sha1": "9a192c6a003390a5eab017fe79d42514ec6712e7",
"sha256": "dfe7ef772d078ec09490c02190a32fcd40b3eaf3b040049d8930eb80e6d5743c",
"size": 54636,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8821c_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8822b_fw.bin",
"sha1": "d5ec968f1609b1c436ad25db7d34f5e393373396",
"sha256": "eecef2c3b2ff03c3a41292a93a25eca6f4ec38eb4a7491a3651b530be89a04fc",
"size": 51176,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8822b_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8822cu_fw.bin",
"sha1": "08751f476d0d0aa165af020ed19d2ed1fbaeeef6",
"sha256": "09d81457457a7760b867c715517d12dd3919f9ecec7b080f2985e75f1563209f",
"size": 38207,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8822cu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8851bu_fw.bin",
"sha1": "0e877d65077a42db4971c7f930dddc7b63047617",
"sha256": "7fcf88c00b2548a5ff157102a35722db5e4be655dab1ce8d9bddb6df85f88f24",
"size": 49760,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8851bu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852au_fw.bin",
"sha1": "24118f0e3b1ac5be44abacd4fa0bc07e2364e223",
"sha256": "56be2eb99d36109bb5e01eee171573b6f3dd041238c01cd97ce4f01df8dbf09e",
"size": 67188,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852au_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852bu_fw.bin",
"sha1": "22cacf5d1b9da25801cd5d6e8b7f06dff0965439",
"sha256": "c2b0d34a4ff8a8ea136993f0dc4ba657522d84231b4094c2a53cda39afc91c57",
"size": 129733,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852bu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852cu_fw.bin",
"sha1": "acd067bdc7933d1d9b8f2d72a750de5d125a5529",
"sha256": "9b8b27741a914c1cecc1ab9837194236890522101a57ba1898eb55f8d538dae0",
"size": 113336,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852cu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8852btu_fw.bin",
"sha1": "eb1139c1b27429eef639b6002b16212d702ad378",
"sha256": "bd162fd3b70e805cf7a8098f22f4ddf5d89da58fafff97d87da1a1e2e0cbeb80",
"size": 128140,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8852btu_fw.bin",
"cores": [
"primehack"
]
},
{
"dest": "Load/Firmware/rtl_bt/rtl8922au_fw.bin",
"sha1": "088a3cf44d2372cdb557241bbbf519bcbbac156f",
"sha256": "112290ca9dc3985034f572594812f0deb8530dd031686461b55cac43a1c35a79",
"size": 71360,
"repo_path": "bios/Nintendo/GameCube/Load/Firmware/rtl_bt/rtl8922au_fw.bin",
"cores": [
"primehack"
]
},
{ {
"dest": "PS3UPDAT.PUP", "dest": "PS3UPDAT.PUP",
"sha1": "093f8698b54b78dcb701de2043f82639de51d63b", "sha1": "093f8698b54b78dcb701de2043f82639de51d63b",
@@ -4567,6 +5057,16 @@
"cemu" "cemu"
] ]
}, },
{
"dest": "switch/prod.keys",
"sha1": "c146b651696d25287241f4bab18226e488bc45b3",
"sha256": "039895c6e93a0c6208c3af27da8c3fc408083873a96f2320dc334a6fa252c112",
"size": 16025,
"repo_path": "bios/Nintendo/Switch/prod.keys",
"cores": [
"citron"
]
},
{ {
"dest": "GC/USA/IPL.bin", "dest": "GC/USA/IPL.bin",
"sha1": "ef9194ab4804aa0aa8540d846caf291b28331165", "sha1": "ef9194ab4804aa0aa8540d846caf291b28331165",
+14 -14
View File
@@ -5,7 +5,7 @@
"platform": "lakka", "platform": "lakka",
"display_name": "Lakka", "display_name": "Lakka",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T06:50:25Z",
"base_destination": "system", "base_destination": "system",
"detect": [ "detect": [
{ {
@@ -17,9 +17,9 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 5867, "total_files": 5867,
"total_size": 6399851574, "total_size": 6399839286,
"pack_files": 8510, "pack_files": 8510,
"pack_size": 6440384738, "pack_size": 6440372450,
"total_omitted": 0, "total_omitted": 0,
"omitted_files": [], "omitted_files": [],
"files": [ "files": [
@@ -961,10 +961,10 @@
}, },
{ {
"dest": "galaksija/ROM2.BIN", "dest": "galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de", "sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723", "sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 8192, "size": 4096,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN", "repo_path": "bios/Galaksija/ROM2.BIN",
"cores": null "cores": null
}, },
{ {
@@ -1771,10 +1771,10 @@
}, },
{ {
"dest": "dolphin-emu/Sys/GC/dsp_rom.bin", "dest": "dolphin-emu/Sys/GC/dsp_rom.bin",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992", "sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe", "sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
"size": 8192, "size": 8192,
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin", "repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
"cores": null "cores": null
}, },
{ {
@@ -47491,10 +47491,10 @@
}, },
{ {
"dest": "np2kai/scsi.rom", "dest": "np2kai/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d", "sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5", "sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 16384, "size": 8192,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom", "repo_path": "bios/NEC/PC-98/scsi.rom",
"cores": [ "cores": [
"np2kai" "np2kai"
] ]
+11 -11
View File
@@ -5,7 +5,7 @@
"platform": "recalbox", "platform": "recalbox",
"display_name": "Recalbox", "display_name": "Recalbox",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T06:50:25Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -238,9 +238,9 @@
} }
], ],
"total_files": 3229, "total_files": 3229,
"total_size": 4193670403, "total_size": 4193661699,
"pack_files": 3231, "pack_files": 3231,
"pack_size": 4193670403, "pack_size": 4193661699,
"total_omitted": 1, "total_omitted": 1,
"omitted_files": [ "omitted_files": [
{ {
@@ -1815,10 +1815,10 @@
}, },
{ {
"dest": "pcenginecd/syscard3.pce", "dest": "pcenginecd/syscard3.pce",
"sha1": "1b4c260326d905bc718812dad0f68089977f427b", "sha1": "79f5ff55dd10187c7fd7b8daab0b3ffbd1f56a2c",
"sha256": "df4f75feebb95e53dfef72dea0787df743e3474ba046ff5a4cb88e34dda93ff1", "sha256": "e11527b3b96ce112a037138988ca72fd117a6b0779c2480d9e03eaebece3d9ce",
"size": 262656, "size": 262144,
"repo_path": "bios/NEC/PC Engine/.variants/syscard3.pce.1b4c2603", "repo_path": "bios/NEC/PC Engine/syscard3.pce",
"cores": null "cores": null
}, },
{ {
@@ -25025,10 +25025,10 @@
}, },
{ {
"dest": "np2kai/scsi.rom", "dest": "np2kai/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d", "sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5", "sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 16384, "size": 8192,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom", "repo_path": "bios/NEC/PC-98/scsi.rom",
"cores": [ "cores": [
"np2kai" "np2kai"
] ]
+14 -14
View File
@@ -5,7 +5,7 @@
"platform": "retroarch", "platform": "retroarch",
"display_name": "RetroArch", "display_name": "RetroArch",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T06:50:25Z",
"base_destination": "system", "base_destination": "system",
"detect": [ "detect": [
{ {
@@ -256,9 +256,9 @@
} }
], ],
"total_files": 5867, "total_files": 5867,
"total_size": 6399851574, "total_size": 6399839286,
"pack_files": 8510, "pack_files": 8510,
"pack_size": 6440384738, "pack_size": 6440372450,
"total_omitted": 0, "total_omitted": 0,
"omitted_files": [], "omitted_files": [],
"files": [ "files": [
@@ -1200,10 +1200,10 @@
}, },
{ {
"dest": "galaksija/ROM2.BIN", "dest": "galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de", "sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723", "sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 8192, "size": 4096,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN", "repo_path": "bios/Galaksija/ROM2.BIN",
"cores": null "cores": null
}, },
{ {
@@ -2010,10 +2010,10 @@
}, },
{ {
"dest": "dolphin-emu/Sys/GC/dsp_rom.bin", "dest": "dolphin-emu/Sys/GC/dsp_rom.bin",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992", "sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe", "sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
"size": 8192, "size": 8192,
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin", "repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
"cores": null "cores": null
}, },
{ {
@@ -47730,10 +47730,10 @@
}, },
{ {
"dest": "np2kai/scsi.rom", "dest": "np2kai/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d", "sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5", "sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 16384, "size": 8192,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom", "repo_path": "bios/NEC/PC-98/scsi.rom",
"cores": [ "cores": [
"np2kai" "np2kai"
] ]
+1479 -99
View File
File diff suppressed because it is too large. Load diff
+1192 -12
View File
File diff suppressed because it is too large. Load diff
+14 -14
View File
@@ -5,7 +5,7 @@
"platform": "retropie", "platform": "retropie",
"display_name": "RetroPie", "display_name": "RetroPie",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T06:50:25Z",
"base_destination": "BIOS", "base_destination": "BIOS",
"detect": [ "detect": [
{ {
@@ -16,9 +16,9 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 9899, "total_files": 9899,
"total_size": 8731282254, "total_size": 8731269966,
"pack_files": 12542, "pack_files": 12542,
"pack_size": 8771815418, "pack_size": 8771803130,
"total_omitted": 8, "total_omitted": 8,
"omitted_files": [ "omitted_files": [
{ {
@@ -1041,10 +1041,10 @@
}, },
{ {
"dest": "galaksija/ROM2.BIN", "dest": "galaksija/ROM2.BIN",
"sha1": "1bae895fbdd658cfb56c53cc2139282cc1e778de", "sha1": "5d5ab4313a2d0effe7572bb129193b64cab002c1",
"sha256": "9fc01e6beab84e61e0267fa39e23c649cdf2ccd25ad1cbfc2de0facfd160e723", "sha256": "7d93fe474fcc98c06a6d96e533f8935e4e707edf0792d3e9d679ea68404375de",
"size": 8192, "size": 4096,
"repo_path": "bios/Sony/PlayStation 2/ROM2.BIN", "repo_path": "bios/Galaksija/ROM2.BIN",
"cores": null "cores": null
}, },
{ {
@@ -1851,10 +1851,10 @@
}, },
{ {
"dest": "dolphin-emu/Sys/GC/dsp_rom.bin", "dest": "dolphin-emu/Sys/GC/dsp_rom.bin",
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992", "sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe", "sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
"size": 8192, "size": 8192,
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin", "repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
"cores": null "cores": null
}, },
{ {
@@ -84803,10 +84803,10 @@
}, },
{ {
"dest": "np2kai/scsi.rom", "dest": "np2kai/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d", "sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5", "sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 16384, "size": 8192,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom", "repo_path": "bios/NEC/PC-98/scsi.rom",
"cores": [ "cores": [
"np2kai" "np2kai"
] ]
+7 -7
View File
@@ -5,7 +5,7 @@
"platform": "rocknix", "platform": "rocknix",
"display_name": "ROCKNIX", "display_name": "ROCKNIX",
"version": "1.0", "version": "1.0",
"generated": "2026-10-06T03:26:07Z", "generated": "2026-10-06T06:50:25Z",
"base_destination": "bios", "base_destination": "bios",
"detect": [ "detect": [
{ {
@@ -18,9 +18,9 @@
], ],
"standalone_copies": [], "standalone_copies": [],
"total_files": 5804, "total_files": 5804,
"total_size": 6570732492, "total_size": 6570724300,
"pack_files": 5806, "pack_files": 5806,
"pack_size": 6570732492, "pack_size": 6570724300,
"total_omitted": 0, "total_omitted": 0,
"omitted_files": [], "omitted_files": [],
"files": [ "files": [
@@ -45704,10 +45704,10 @@
}, },
{ {
"dest": "np2kai/scsi.rom", "dest": "np2kai/scsi.rom",
"sha1": "451c85c46b92e6ad2ad930f055ccf0fe3049936d", "sha1": "3d7166f05daad1b022fa04c2569e788580158095",
"sha256": "5e1efe51b2c8be901ab4283c671bd84364a574845b7269f92bdddc4dae9ae5d5", "sha256": "9809ed07796454d5524cce240c4f815496b82fae3644aba80778612c618d5cf7",
"size": 16384, "size": 8192,
"repo_path": "bios/Other/Clock Signal/AppleII/scsi.rom", "repo_path": "bios/NEC/PC-98/scsi.rom",
"cores": [ "cores": [
"np2kai" "np2kai"
] ]
+1319 -9
View File
File diff suppressed because it is too large. Load diff
+12
View File
@@ -148,6 +148,7 @@ platforms:
retroarch: retroarch:
config: retroarch.yml config: retroarch.yml
status: active status: active
extract_to: system/
logo: https://raw.githubusercontent.com/libretro/RetroArch/master/media/retroarch-vector_invader-only.svg logo: https://raw.githubusercontent.com/libretro/RetroArch/master/media/retroarch-vector_invader-only.svg
scraper: libretro scraper: libretro
source_url: https://raw.githubusercontent.com/libretro/libretro-database/master/dat/System.dat source_url: https://raw.githubusercontent.com/libretro/libretro-database/master/dat/System.dat
@@ -182,6 +183,7 @@ platforms:
batocera: batocera:
config: batocera.yml config: batocera.yml
status: active status: active
extract_to: /userdata/bios/
logo: https://raw.githubusercontent.com/batocera-linux/batocera-emulationstation/master/resources/splash_batocera.svg logo: https://raw.githubusercontent.com/batocera-linux/batocera-emulationstation/master/resources/splash_batocera.svg
scraper: batocera scraper: batocera
source_url: https://raw.githubusercontent.com/batocera-linux/batocera.linux/master/package/batocera/core/batocera-scripts/scripts/batocera-systems source_url: https://raw.githubusercontent.com/batocera-linux/batocera.linux/master/package/batocera/core/batocera-scripts/scripts/batocera-systems
@@ -407,6 +409,7 @@ platforms:
recalbox: recalbox:
config: recalbox.yml config: recalbox.yml
status: active status: active
extract_to: /recalbox/share/bios/
logo: https://raw.githubusercontent.com/homarr-labs/dashboard-icons/main/svg/recalbox.svg logo: https://raw.githubusercontent.com/homarr-labs/dashboard-icons/main/svg/recalbox.svg
scraper: recalbox scraper: recalbox
source_url: https://gitlab.com/recalbox/recalbox/-/raw/master/board/recalbox/fsoverlay/recalbox/share_init/system/.emulationstation/es_bios.xml source_url: https://gitlab.com/recalbox/recalbox/-/raw/master/board/recalbox/fsoverlay/recalbox/share_init/system/.emulationstation/es_bios.xml
@@ -620,6 +623,7 @@ platforms:
retrobat: retrobat:
config: retrobat.yml config: retrobat.yml
status: active status: active
extract_to: bios/
logo: https://raw.githubusercontent.com/RetroBat-Official/retrobat/main/system/resources/retrobat_logo_notext.png logo: https://raw.githubusercontent.com/RetroBat-Official/retrobat/main/system/resources/retrobat_logo_notext.png
scraper: retrobat scraper: retrobat
source_url: https://raw.githubusercontent.com/RetroBat-Official/emulatorlauncher/master/batocera-systems/Resources/batocera-systems.json source_url: https://raw.githubusercontent.com/RetroBat-Official/emulatorlauncher/master/batocera-systems/Resources/batocera-systems.json
@@ -808,6 +812,7 @@ platforms:
emudeck: emudeck:
config: emudeck.yml config: emudeck.yml
status: active status: active
extract_to: ~/Emulation/bios/
logo: https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/icons/EmuDeck.png logo: https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/icons/EmuDeck.png
scraper: emudeck scraper: emudeck
source_url: https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/functions/checkBIOS.sh source_url: https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/functions/checkBIOS.sh
@@ -838,6 +843,7 @@ platforms:
lakka: lakka:
config: lakka.yml config: lakka.yml
status: active status: active
extract_to: /storage/system/
logo: https://raw.githubusercontent.com/libretro/retroarch-assets/master/src/xmb/flatui/lakka.svg logo: https://raw.githubusercontent.com/libretro/retroarch-assets/master/src/xmb/flatui/lakka.svg
scraper: libretro scraper: libretro
inherits_from: retroarch inherits_from: retroarch
@@ -854,6 +860,7 @@ platforms:
retrodeck: retrodeck:
config: retrodeck.yml config: retrodeck.yml
status: active status: active
extract_to: ~/retrodeck/
logo: https://raw.githubusercontent.com/RetroDECK/RetroDECK/main/res/icon.svg logo: https://raw.githubusercontent.com/RetroDECK/RetroDECK/main/res/icon.svg
scraper: retrodeck scraper: retrodeck
source_url: https://github.com/RetroDECK/components source_url: https://github.com/RetroDECK/components
@@ -898,6 +905,7 @@ platforms:
romm: romm:
config: romm.yml config: romm.yml
status: active status: active
extract_to: library/bios/
logo: https://avatars.githubusercontent.com/u/168586850 logo: https://avatars.githubusercontent.com/u/168586850
scraper: romm scraper: romm
source_url: https://raw.githubusercontent.com/rommapp/romm/master/backend/models/fixtures/known_bios_files.json source_url: https://raw.githubusercontent.com/rommapp/romm/master/backend/models/fixtures/known_bios_files.json
@@ -945,6 +953,7 @@ platforms:
rocknix: rocknix:
config: rocknix.yml config: rocknix.yml
status: active status: active
extract_to: /storage/roms/bios/
logo: https://avatars.githubusercontent.com/u/152618023 logo: https://avatars.githubusercontent.com/u/152618023
scraper: rocknix scraper: rocknix
cores: all_libretro cores: all_libretro
@@ -959,6 +968,7 @@ platforms:
retropie: retropie:
config: retropie.yml config: retropie.yml
status: archived status: archived
extract_to: ~/RetroPie/BIOS/
logo: https://avatars.githubusercontent.com/u/11378204 logo: https://avatars.githubusercontent.com/u/11378204
scraper: retropie scraper: retropie
source: https://github.com/RetroPie/RetroPie-Setup source: https://github.com/RetroPie/RetroPie-Setup
@@ -975,6 +985,7 @@ platforms:
bizhawk: bizhawk:
config: bizhawk.yml config: bizhawk.yml
status: active status: active
extract_to: Firmware/
logo: https://avatars.githubusercontent.com/u/11743303 logo: https://avatars.githubusercontent.com/u/11743303
scraper: bizhawk scraper: bizhawk
source_url: https://raw.githubusercontent.com/TASEmulators/BizHawk/master/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs source_url: https://raw.githubusercontent.com/TASEmulators/BizHawk/master/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs
@@ -1039,6 +1050,7 @@ platforms:
misterfpga: misterfpga:
config: misterfpga.yml config: misterfpga.yml
status: active status: active
extract_to: /media/fat/games/
logo: https://avatars.githubusercontent.com/u/47264183 logo: https://avatars.githubusercontent.com/u/47264183
scraper: misterfpga scraper: misterfpga
source_url: https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip source_url: https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip
+1 -1
View File
@@ -16,7 +16,7 @@ dependencies = [
# and the CI contract check are the only callers, and no user path imports it. # and the CI contract check are the only callers, and no user path imports it.
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"jsonschema>=4.23", "jsonschema[format-nongpl]>=4.23",
] ]
# The site build the repo generates and documents. Both were hard # The site build the repo generates and documents. Both were hard
# requirements of deploy-site.yml and of wiki/release-process.md while being # requirements of deploy-site.yml and of wiki/release-process.md while being
+31
View File
@@ -31,6 +31,37 @@
"files" "files"
] ]
} }
},
{
"comment": "Per-file mode separates the two builds of a dual profile. A profile with only a standalone build has no libretro side to separate it from, and a file marked standalone there drops out of the gap analysis and of the packs of every platform that lists the emulator without standalone_cores.",
"if": {
"properties": {
"type": {
"const": "standalone"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"files": {
"items": {
"not": {
"properties": {
"mode": {
"const": "standalone"
}
},
"required": [
"mode"
]
}
}
}
}
}
} }
], ],
"properties": { "properties": {
+28
View File
@@ -72,6 +72,11 @@ def _strip_timestamps(text: str) -> str:
class ArtifactLockBusy(RuntimeError): class ArtifactLockBusy(RuntimeError):
"""Raised when another process already holds the artifact directory.""" """Raised when another process already holds the artifact directory."""
# A run that holds an artifact directory for its whole duration names it
# here for the steps it spawns: they would otherwise be refused by it.
HELD_LOCK_ENV = "RETROBIOS_HELD_ARTIFACT_DIR"
@contextlib.contextmanager @contextlib.contextmanager
def artifact_lock(directory: str, exclusive: bool = True): def artifact_lock(directory: str, exclusive: bool = True):
"""Serialize access to a shared artifact directory across processes. """Serialize access to a shared artifact directory across processes.
@@ -88,6 +93,9 @@ def artifact_lock(directory: str, exclusive: bool = True):
return return
os.makedirs(directory, exist_ok=True) os.makedirs(directory, exist_ok=True)
if os.environ.get(HELD_LOCK_ENV) == os.path.realpath(directory):
yield
return
lock_path = os.path.join(directory, ".lock") lock_path = os.path.join(directory, ".lock")
mode = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH mode = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH
with open(lock_path, "w") as handle: with open(lock_path, "w") as handle:
@@ -103,6 +111,26 @@ def artifact_lock(directory: str, exclusive: bool = True):
finally: finally:
fcntl.flock(handle, fcntl.LOCK_UN) fcntl.flock(handle, fcntl.LOCK_UN)
@contextlib.contextmanager
def hold_artifact_lock(directory: str):
"""Hold a directory exclusively across several child steps.
A lock taken step by step frees the directory between them: another
run can purge the packs one step built before the next step checks
them. The children this process spawns inherit the hold.
"""
with artifact_lock(directory):
previous = os.environ.get(HELD_LOCK_ENV)
os.environ[HELD_LOCK_ENV] = os.path.realpath(directory)
try:
yield
finally:
if previous is None:
os.environ.pop(HELD_LOCK_ENV, None)
else:
os.environ[HELD_LOCK_ENV] = previous
def _build_timestamp(db: dict | None = None) -> str: def _build_timestamp(db: dict | None = None) -> str:
"""Timestamp for generated artifacts. """Timestamp for generated artifacts.
+21 -7
View File
@@ -157,6 +157,22 @@ def _core_names(config: dict) -> set[str]:
return names return names
def _keyed_files(files: list[dict]) -> dict[str, dict]:
"""Each declaration under its own key.
A platform declares one destination several times (an archive once per
inner ROM, MD5 alternatives): keyed by destination alone, the dict kept
the last and a change to the others read as identical.
"""
keyed: dict[str, dict] = {}
seen: dict[str, int] = {}
for f in files:
key = _file_key(f)
seen[key] = seen.get(key, 0) + 1
keyed[key if seen[key] == 1 else f"{key}#{seen[key]}"] = f
return keyed
def diff_platform(old: dict, new: dict) -> PlatformDiff: def diff_platform(old: dict, new: dict) -> PlatformDiff:
"""Compare two platform files the way a reviewer reads the diff. """Compare two platform files the way a reviewer reads the diff.
@@ -172,12 +188,8 @@ def diff_platform(old: dict, new: dict) -> PlatformDiff:
diff.systems_added = sorted(set(new_systems) - set(old_systems)) diff.systems_added = sorted(set(new_systems) - set(old_systems))
diff.systems_removed = sorted(set(old_systems) - set(new_systems)) diff.systems_removed = sorted(set(old_systems) - set(new_systems))
for system in sorted(set(old_systems) & set(new_systems)): for system in sorted(set(old_systems) & set(new_systems)):
old_files = { old_files = _keyed_files((old_systems[system] or {}).get("files") or [])
_file_key(f): f for f in (old_systems[system] or {}).get("files") or [] new_files = _keyed_files((new_systems[system] or {}).get("files") or [])
}
new_files = {
_file_key(f): f for f in (new_systems[system] or {}).get("files") or []
}
diff.files_added.extend( diff.files_added.extend(
f"{system}/{k}" for k in sorted(set(new_files) - set(old_files)) f"{system}/{k}" for k in sorted(set(new_files) - set(old_files))
) )
@@ -792,7 +804,9 @@ def _check_fbneo(cache_dir: str, offline: bool) -> Finding:
# --- CI toolchain ----------------------------------------------------------- # --- CI toolchain -----------------------------------------------------------
_PIN_RE = re.compile(r'"?([A-Za-z0-9_.\-]+)((?:[<>=!~]=?[^,"\s]+)(?:,[<>=!~]=?[^,"\s]+)*)?"?') _PIN_RE = re.compile(
r'"?([A-Za-z0-9_.\-]+)(?:\[[^\]]*\])?((?:[<>=!~]=?[^,"\s]+)(?:,[<>=!~]=?[^,"\s]+)*)?"?'
)
_USES_RE = re.compile(r"uses:\s*([\w.\-]+/[\w.\-]+)@([0-9a-f]{40})\s*(?:#\s*(\S+))?") _USES_RE = re.compile(r"uses:\s*([\w.\-]+/[\w.\-]+)@([0-9a-f]{40})\s*(?:#\s*(\S+))?")
+25 -9
View File
@@ -480,6 +480,15 @@ def _affinity_tokens(text: str) -> set[str]:
} }
def _owner_tokens(file_entry: dict) -> set[str]:
"""Tokens naming the profile that asks for the file."""
tokens: set[str] = set()
for owner in (file_entry.get("source_profile"), file_entry.get("source_emulator")):
if owner:
tokens |= _affinity_tokens(str(owner).replace(" ", ""))
return tokens
def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]: def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]:
"""Same-named candidates, the likeliest first. """Same-named candidates, the likeliest first.
@@ -495,16 +504,17 @@ def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str
return paths return paths
hint = dest_hint or file_entry.get("path") or file_entry.get("destination") or "" 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 "") wanted = _affinity_tokens(hint.rsplit("/", 1)[0] if "/" in hint else "")
for owner in (file_entry.get("source_profile"), file_entry.get("source_emulator")): owners = _owner_tokens(file_entry)
if owner:
wanted |= _affinity_tokens(str(owner).replace(" ", ""))
sized = any(file_entry.get(k) for k in ("size", "min_size", "max_size")) sized = any(file_entry.get(k) for k in ("size", "min_size", "max_size"))
def score(path: str) -> tuple[int, int]: def score(path: str) -> tuple[int, bool, int]:
fits = 0 fits = 0
if sized and os.path.exists(path): if sized and os.path.exists(path):
fits = int(size_fits(file_entry, os.path.getsize(path))) fits = int(size_fits(file_entry, os.path.getsize(path)))
return fits, len(wanted & _affinity_tokens(path.rsplit("/", 1)[0])) tokens = _affinity_tokens(path.rsplit("/", 1)[0])
# The owner's own copy ranks above any shared tree segment: NetherSX2's
# shaders/common/ beat armsx2's own fxaa.fx on two directory tokens.
return fits, bool(owners & tokens), len(wanted & tokens)
return sorted(paths, key=score, reverse=True) return sorted(paths, key=score, reverse=True)
@@ -721,12 +731,17 @@ def resolve_local_file(
own_file_absent = True own_file_absent = True
if len(candidates) > 1: if len(candidates) > 1:
depth = len(tail.split("/")) depth = len(tail.split("/"))
# At equal depth the owner's own copy: super3's Games.xml was
# served to Supermodel-Dojo by index order alone.
owners = _owner_tokens(file_entry)
candidates = sorted( candidates = sorted(
candidates, candidates,
key=lambda h: len( key=lambda h: (
files_db.get(h, {}).get("path", "").split("/") len(files_db.get(h, {}).get("path", "").split("/")) - depth,
) not owners & _affinity_tokens(
- depth, files_db.get(h, {}).get("path", "").rsplit("/", 1)[0]
),
),
) )
for match_sha1 in candidates: for match_sha1 in candidates:
if match_sha1 not in files_db: if match_sha1 not in files_db:
@@ -1718,6 +1733,7 @@ from artifacts import ( # noqa: E402,F401
write_if_changed, write_if_changed,
ArtifactLockBusy, ArtifactLockBusy,
artifact_lock, artifact_lock,
hold_artifact_lock,
_TIMESTAMP_PATTERNS, _TIMESTAMP_PATTERNS,
_strip_timestamps, _strip_timestamps,
) )
+155 -92
View File
@@ -20,6 +20,7 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, os.path.dirname(__file__))
from common import ( from common import (
_norm_system_id,
get_mame_clone_map, get_mame_clone_map,
list_registered_platforms, list_registered_platforms,
load_database, load_database,
@@ -28,7 +29,10 @@ from common import (
name_match_size_ok, name_match_size_ok,
parse_md5_list, parse_md5_list,
require_yaml, require_yaml,
resolve_local_file,
runs_standalone,
) )
from validation import outside_gap_scope
yaml = require_yaml() yaml = require_yaml()
@@ -44,6 +48,9 @@ def load_platform_files(
"""Collect declared filenames + data_directories per system. """Collect declared filenames + data_directories per system.
Restricted to *platforms* when given, otherwise every registered platform. Restricted to *platforms* when given, otherwise every registered platform.
Keyed by the normalized system ID: a profile writes `sega-megacd` where
RetroArch writes `sega-mega-cd`, and an exact match called 924 declared
files undeclared.
""" """
declared = {} declared = {}
platform_data_dirs = {} platform_data_dirs = {}
@@ -55,58 +62,68 @@ def load_platform_files(
for fe in system.get("files", []): for fe in system.get("files", []):
name = fe.get("name", "") name = fe.get("name", "")
if name: if name:
declared.setdefault(sys_id, set()).add(name) declared.setdefault(_norm_system_id(sys_id), set()).add(name)
for dd in system.get("data_directories", []): for dd in system.get("data_directories", []):
ref = dd.get("ref", "") ref = dd.get("ref", "")
if ref: if ref:
platform_data_dirs.setdefault(sys_id, set()).add(ref) platform_data_dirs.setdefault(_norm_system_id(sys_id), set()).add(ref)
return declared, platform_data_dirs return declared, platform_data_dirs
def _build_supplemental_index( def _build_supplemental_index(
data_root: str = "data", bios_root: str = "bios" data_root: str = "data", bios_root: str = "bios"
) -> set[str]: ) -> set[str]:
"""Build a set of filenames and directory names in data/ and inside bios/ ZIPs.""" """Build a set of filenames and directory names in data/ and inside bios/ ZIPs.
A directory is indexed only with its trailing slash: a bare name matched a
file entry of the same name, and BasiliskII's required `ROM` read as held
because cpcemu keeps a directory called ROM.
"""
names: set[str] = set() names: set[str] = set()
root_path = Path(data_root) root_path = Path(data_root)
if root_path.is_dir(): if root_path.is_dir():
for fpath in root_path.rglob("*"): for fpath in root_path.rglob("*"):
if fpath.name.startswith("."): if fpath.name.startswith("."):
continue continue
names.add(fpath.name) if fpath.is_file():
names.add(fpath.name.lower()) names.add(fpath.name)
names.add(fpath.name.lower())
else:
names.add(fpath.name + "/")
names.add(fpath.name.lower() + "/")
if fpath.is_dir(): if fpath.is_dir():
# Also index relative path from data/subdir/ for directory entries # Also index relative path from data/subdir/ for directory entries
parts = fpath.relative_to(root_path).parts parts = fpath.relative_to(root_path).parts
if len(parts) > 1: if len(parts) > 1:
rel = "/".join(parts[1:]) rel = "/".join(parts[1:])
names.add(rel)
names.add(rel + "/") names.add(rel + "/")
names.add(rel.lower())
names.add(rel.lower() + "/") names.add(rel.lower() + "/")
bios_path = Path(bios_root) bios_path = Path(bios_root)
if bios_path.is_dir(): if bios_path.is_dir():
# Index directory names for directory-type entries (e.g., "nestopia/samples/moepro/") # Index directory names for directory-type entries (e.g., "nestopia/samples/moepro/")
for dpath in bios_path.rglob("*"): for dpath in bios_path.rglob("*"):
if dpath.is_dir() and not dpath.name.startswith("."): if dpath.is_dir() and not dpath.name.startswith("."):
names.add(dpath.name)
names.add(dpath.name.lower())
names.add(dpath.name + "/") names.add(dpath.name + "/")
names.add(dpath.name.lower() + "/") names.add(dpath.name.lower() + "/")
import zipfile names |= _zip_member_names(bios_path)
return names
for zpath in bios_path.rglob("*.zip"):
try: def _zip_member_names(bios_path: Path) -> set[str]:
with zipfile.ZipFile(zpath) as zf: """Basenames of the files inside every ZIP under bios/, both cases."""
for member in zf.namelist(): import zipfile
if not member.endswith("/"):
basename = ( names: set[str] = set()
member.rsplit("/", 1)[-1] if "/" in member else member for zpath in bios_path.rglob("*.zip"):
) try:
names.add(basename) with zipfile.ZipFile(zpath) as zf:
names.add(basename.lower()) members = [m for m in zf.namelist() if not m.endswith("/")]
except (zipfile.BadZipFile, OSError): except (zipfile.BadZipFile, OSError) as exc:
pass print(f" WARNING: unreadable archive {zpath}: {exc}", file=sys.stderr)
continue
for member in members:
basename = member.rsplit("/", 1)[-1]
names.update((basename, basename.lower()))
return names return names
@@ -168,15 +185,108 @@ def _resolve_source(
by_name_lower[canonical.lower()] by_name_lower[canonical.lower()]
): ):
return "bios" return "bios"
# data/ supplemental index if data_names and _data_hit(fname, basename, file_entry, data_names):
if data_names: return "data"
if fname in data_names or key in data_names:
return "data"
if basename and (basename in data_names or basename.lower() in data_names):
return "data"
return None return None
def _data_hit(
fname: str, basename: str | None, file_entry: dict | None, data_names: set[str]
) -> bool:
"""Whether data/ or a ZIP holds the name: a directory entry looks for a
directory, a file entry for a file."""
looked_up = [fname, fname.lower()] + ([basename, basename.lower()] if basename else [])
if fname.endswith("/") or (file_entry or {}).get("type") == "directory":
looked_up = [name.rstrip("/") + "/" for name in looked_up]
return any(name in data_names for name in looked_up)
class FileConsumers:
"""Who reads each collected file, joined through the resolver.
The site joined database files to emulators and platforms by bare name:
each of the four pak0.pk3 of four games was attributed to every engine
reading a pak0.pk3. A consumer here is an entry that resolves to the
file by the evidence the pack uses, so a homonym is nobody's file.
"""
def __init__(self, db: dict, zip_contents: dict | None = None) -> None:
self._db = db
self._zip_contents = zip_contents
self._sha1_by_path = {
path: sha1
for sha1, entry in db.get("files", {}).items()
for path in {entry.get("path", ""), *entry.get("paths", [])}
if path
}
self.platforms: dict[str, set[str]] = {}
self.emulators: dict[str, set[str]] = {}
def sha1_of(self, entry: dict, dest: str = "") -> str | None:
"""The collected file an entry resolves to, or None."""
if not entry.get("name"):
return None
path, status = resolve_local_file(
entry, self._db, self._zip_contents, dest_hint=dest
)
if not path or status in ("not_found", "hash_mismatch"):
return None
return self._sha1_by_path.get(path)
def add_platforms(self, configs: dict[str, dict]) -> None:
for name, config in configs.items():
for system in config.get("systems", {}).values():
for entry in system.get("files", []):
sha1 = self.sha1_of(entry, entry.get("destination", ""))
if sha1:
self.platforms.setdefault(sha1, set()).add(name)
def add_emulators(self, profiles: dict[str, dict]) -> None:
for name, profile in profiles.items():
if profile.get("type") == "alias":
continue
for entry in profile.get("files", []):
sha1 = self.sha1_of(entry, entry.get("path") or "")
if sha1:
self.emulators.setdefault(sha1, set()).add(name)
def entry_source(f: dict, index: dict) -> str | None:
"""Where the collection holds a profile entry, or None.
By name, path and alias, then by any hash the entry declares. The gap
report and the site pages read this one function, so a file is held or
missing in the same way on both.
"""
by_name, by_name_lower = index["by_name"], index["by_name_lower"]
data_names, by_path_suffix = index.get("data_names"), index["by_path_suffix"]
db_files = index["db_files"]
fname = f.get("name", "")
path_field = f.get("path") or ""
for candidate in [fname, *([path_field] if path_field != fname else []),
*(f.get("aliases") or [])]:
if not candidate:
continue
source = _resolve_source(
candidate, by_name, by_name_lower, data_names, by_path_suffix, f, db_files,
)
if source is not None:
return source
def values(field: str) -> list[str]:
raw = f.get(field) or []
return [str(v).lower() for v in (raw if isinstance(raw, list) else [raw]) if v]
held = (
any(index["by_md5"].get(v) for v in parse_md5_list(f.get("md5")))
or any(v in db_files for v in values("sha1"))
# rust_dos names its SC-55 ROMs by sha256 alone
or any(v in index["by_sha256"] for v in values("sha256"))
or any(index["by_crc32"].get(v) for v in values("crc32"))
)
return "bios" if held else None
def _resolve_archive_source( def _resolve_archive_source(
archive_name: str, archive_name: str,
by_name: dict[str, list], by_name: dict[str, list],
@@ -197,6 +307,7 @@ def _cross_reference_profile(
declared: dict[str, set[str]], declared: dict[str, set[str]],
report: dict, report: dict,
index: dict, index: dict,
standalone_cores: set[str] | None = None,
) -> None: ) -> None:
"""Compare one emulator profile against what the platforms declare. """Compare one emulator profile against what the platforms declare.
@@ -207,10 +318,7 @@ def _cross_reference_profile(
""" """
by_name = index["by_name"] by_name = index["by_name"]
by_name_lower = index["by_name_lower"] by_name_lower = index["by_name_lower"]
by_md5 = index["by_md5"]
by_crc32 = index["by_crc32"]
by_path_suffix = index["by_path_suffix"] by_path_suffix = index["by_path_suffix"]
db_files = index["db_files"]
data_names = index["data_names"] data_names = index["data_names"]
all_declared = index["all_declared"] all_declared = index["all_declared"]
emu_files = profile.get("files", []) emu_files = profile.get("files", [])
@@ -225,8 +333,11 @@ def _cross_reference_profile(
else: else:
platform_names = set() platform_names = set()
for sys_id in systems: for sys_id in systems:
platform_names.update(declared.get(sys_id, set())) platform_names.update(declared.get(_norm_system_id(sys_id), set()))
# The build a platform runs decides which entries it reads, as in
# find_undeclared_files: without a platform, the libretro build.
is_standalone = runs_standalone(emu_name, profile, standalone_cores or set())
gaps = [] gaps = []
covered = [] covered = []
unsourceable_list: list[dict] = [] unsourceable_list: list[dict] = []
@@ -258,26 +369,7 @@ def _cross_reference_profile(
}) })
continue continue
# Skip pattern placeholders (e.g., <bios>.bin, <user-selected>.bin) if outside_gap_scope(f, is_standalone):
if "<" in fname or ">" in fname or "*" in fname:
continue
# Skip UI-imported files with explicit path: null (not resolvable by pack)
if "path" in f and f["path"] is None:
continue
# Skip standalone-only files
file_mode = f.get("mode", "both")
if file_mode == "standalone":
continue
# Skip files loaded from non-system directories (save_dir, content_dir)
load_from = f.get("load_from", "")
if load_from and load_from != "system_dir":
continue
# Skip filename-agnostic files (handled by agnostic scan)
if f.get("agnostic"):
continue continue
archive = f.get("archive") archive = f.get("archive")
@@ -339,44 +431,7 @@ def _cross_reference_profile(
if storage in ("release", "large_file"): if storage in ("release", "large_file"):
source = "large_file" source = "large_file"
else: else:
source = _resolve_source( source = entry_source(f, index)
fname, by_name, by_name_lower, data_names, by_path_suffix,
f, db_files,
)
if source is None:
path_field = f.get("path", "")
if path_field and path_field != fname:
source = _resolve_source(
path_field, by_name, by_name_lower,
data_names, by_path_suffix, f, db_files,
)
# Try the alternate names the emulator accepts, like
# resolve_local_file does
if source is None:
for alias in f.get("aliases") or []:
source = _resolve_source(
alias, by_name, by_name_lower,
data_names, by_path_suffix, f, db_files,
)
if source is not None:
break
# Try MD5 hash match
if source is None:
for md5_val in parse_md5_list(f.get("md5")):
if by_md5.get(md5_val):
source = "bios"
break
# Try SHA1 hash match
if source is None:
raw_sha1 = f.get("sha1", "")
sha1_values = raw_sha1 if isinstance(raw_sha1, list) else [raw_sha1]
if any(value and value in db_files for value in sha1_values):
source = "bios"
# Try CRC32 hash match
if source is None:
crc32 = str(f.get("crc32", "")).lower()
if crc32 and by_crc32.get(crc32):
source = "bios"
if source is None: if source is None:
source = "missing" source = "missing"
@@ -421,6 +476,7 @@ def cross_reference(
platform_data_dirs: dict[str, set[str]] | None = None, platform_data_dirs: dict[str, set[str]] | None = None,
data_names: set[str] | None = None, data_names: set[str] | None = None,
all_declared: set[str] | None = None, all_declared: set[str] | None = None,
standalone_cores: set[str] | None = None,
) -> dict: ) -> dict:
"""Compare emulator profiles against platform declarations. """Compare emulator profiles against platform declarations.
@@ -442,6 +498,7 @@ def cross_reference(
by_name_lower = {k.lower(): k for k in by_name} by_name_lower = {k.lower(): k for k in by_name}
by_md5 = db.get("indexes", {}).get("by_md5", {}) by_md5 = db.get("indexes", {}).get("by_md5", {})
by_crc32 = db.get("indexes", {}).get("by_crc32", {}) by_crc32 = db.get("indexes", {}).get("by_crc32", {})
by_sha256 = db.get("indexes", {}).get("by_sha256", {})
by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {}) by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {})
db_files = db.get("files", {}) db_files = db.get("files", {})
report = {} report = {}
@@ -451,6 +508,7 @@ def cross_reference(
"by_name_lower": by_name_lower, "by_name_lower": by_name_lower,
"by_md5": by_md5, "by_md5": by_md5,
"by_crc32": by_crc32, "by_crc32": by_crc32,
"by_sha256": by_sha256,
"by_path_suffix": by_path_suffix, "by_path_suffix": by_path_suffix,
"db_files": db_files, "db_files": db_files,
"data_names": data_names, "data_names": data_names,
@@ -458,7 +516,7 @@ def cross_reference(
} }
for emu_name, profile in profiles.items(): for emu_name, profile in profiles.items():
_cross_reference_profile( _cross_reference_profile(
emu_name, profile, declared, report, index emu_name, profile, declared, report, index, standalone_cores
) )
return report return report
@@ -535,6 +593,7 @@ def main():
if args.target and not args.platform: if args.target and not args.platform:
parser.error("--target requires --platform") parser.error("--target requires --platform")
standalone_cores: set[str] = set()
if args.platform: if args.platform:
from common import load_target_config, resolve_platform_cores from common import load_target_config, resolve_platform_cores
@@ -546,6 +605,7 @@ def main():
config = load_platform_config(args.platform, args.platforms_dir) config = load_platform_config(args.platform, args.platforms_dir)
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores) relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
profiles = {k: v for k, v in profiles.items() if k in relevant} profiles = {k: v for k, v in profiles.items() if k in relevant}
standalone_cores = set(map(str, config.get("standalone_cores", [])))
if not profiles: if not profiles:
print("No emulator profiles found.", file=sys.stderr) print("No emulator profiles found.", file=sys.stderr)
@@ -556,7 +616,10 @@ def main():
) )
db = load_database(args.db) db = load_database(args.db)
data_names = _build_supplemental_index() data_names = _build_supplemental_index()
report = cross_reference(profiles, declared, db, plat_data_dirs, data_names) report = cross_reference(
profiles, declared, db, plat_data_dirs, data_names,
standalone_cores=standalone_cores,
)
if args.json: if args.json:
print(json.dumps(report, indent=2)) print(json.dumps(report, indent=2))
+46 -13
View File
@@ -38,6 +38,19 @@ def _is_placeholder(name: str) -> bool:
return "<" in name or ">" in name or "*" in name return "<" in name or ">" in name or "*" in name
def _field_sets(entry: dict | None) -> dict[str, set[str]]:
return {f: set(_hash_values(entry or {}, f)) for f in HASH_FIELDS}
def _single_size(value: object) -> int | None:
"""One size, or None: a list names several accepted revisions, which a
single size field cannot state (int() on it stopped the whole export)."""
if isinstance(value, list):
distinct = {int(v) for v in value}
return distinct.pop() if len(distinct) == 1 else None
return int(value)
@dataclass @dataclass
class NativeFile: class NativeFile:
"""One file as the platform will read it, after correction.""" """One file as the platform will read it, after correction."""
@@ -61,19 +74,26 @@ class NativeFile:
"""Which side's content fields describe the file: one side, or both. """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 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 platform's sha1, a record no file satisfies. The truth decides alone
only where they agree on some hash; where they contradict, the truth only on a contradiction: a hash both sides declare without a value in
decides alone; where the truth declares none, the platform does. common, or two declared sizes. Different fields prove nothing: the
truth's crc32 beside Batocera's md5 for bios7.bin is one dump, and
reading it as a contradiction emptied the md5 the frontend checks.
""" """
truth = {f: set(_hash_values(self.truth or {}, f)) for f in HASH_FIELDS} truth, plat = _field_sets(self.truth), _field_sets(self.platform)
plat = {f: set(_hash_values(self.platform or {}, f)) for f in HASH_FIELDS}
if not any(truth.values()): if not any(truth.values()):
return "platform" return "platform"
if not any(plat.values()): if not any(plat.values()):
return "truth" return "truth"
if any(truth[f] & plat[f] for f in HASH_FIELDS): shared = [f for f in HASH_FIELDS if truth[f] and plat[f]]
if any(truth[f] & plat[f] for f in shared):
return "both" return "both"
return "truth" return "truth" if shared or self._sizes_differ() else "both"
def _sizes_differ(self) -> bool:
t_size = (self.truth or {}).get("size")
p_size = (self.platform or {}).get("size")
return isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size
def hashes(self, field_name: str) -> list[str]: def hashes(self, field_name: str) -> list[str]:
"""Accepted values for a hash, truth first when it has an opinion. """Accepted values for a hash, truth first when it has an opinion.
@@ -136,7 +156,7 @@ class NativeFile:
order = (self.platform, self.truth) if source == "platform" else (self.truth, self.platform) order = (self.platform, self.truth) if source == "platform" else (self.truth, self.platform)
for entry in order: for entry in order:
if entry and entry.get("size"): if entry and entry.get("size"):
return int(entry["size"]) return _single_size(entry["size"])
return None return None
def native(self, key: str, default: object = "") -> object: def native(self, key: str, default: object = "") -> object:
@@ -302,14 +322,27 @@ def build_native_model(
if candidate.truth is None if candidate.truth is None
] ]
def by_destination(candidate: NativeFile) -> bool: def by_destination(candidate: NativeFile, t_dest: str = t_dest) -> bool:
# Dolphin writes dolphin-emu/Sys/GC/USA/IPL.bin for the truth's
# GC/USA/IPL.bin: a destination ending in the path is the file.
theirs = _match_key(candidate.platform or {})[0] theirs = _match_key(candidate.platform or {})[0]
return bool(t_dest) and theirs == t_dest return bool(t_dest) and (theirs == t_dest or theirs.endswith("/" + t_dest))
def by_name(candidate: NativeFile) -> bool: def by_name(
theirs = _match_key(candidate.platform or {})[1] candidate: NativeFile,
t_dest: str = t_dest,
t_name: str = t_name,
truth_entry: dict = truth_entry,
) -> bool:
theirs_dest, theirs = _match_key(candidate.platform or {})
if not t_name or theirs != t_name: if not t_name or theirs != t_name:
return False return False
# Two declared directories that differ name two files: the
# truth's BeebFile/BIOS.rom is not Recalbox's np2kai/bios.rom.
if "/" in t_dest and "/" in theirs_dest and not theirs_dest.endswith(
"/" + t_dest.rsplit("/", 1)[0].rsplit("/", 1)[-1] + "/" + t_name
):
return False
# A shared name with two declared sizes is two files: fbneo's # 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. # 480-byte boot.bin is not RomM's 2 MB Dreamcast boot.bin.
t_size = truth_entry.get("size") t_size = truth_entry.get("size")
@@ -318,7 +351,7 @@ def build_native_model(
isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size
) )
def by_hash(candidate: NativeFile) -> bool: def by_hash(candidate: NativeFile, t_hashes: set = t_hashes) -> bool:
if not t_hashes: if not t_hashes:
return False return False
theirs = { theirs = {
+12
View File
@@ -41,6 +41,18 @@ class Exporter(BaseExporter):
def carries() -> frozenset[str]: def carries() -> frozenset[str]:
return frozenset({"md5"}) return frozenset({"md5"})
@classmethod
def writable(cls, fe: NativeFile, require: str = "") -> bool:
"""An addition the format can state whole.
checkBios compares one md5 for a plain file (altmd5 is read only for
a zippedFile): written with the first of four, retrodream's boot.bin
refused the three other dumps the core accepts.
"""
if fe.platform is None and len(set(fe.hashes("md5"))) > 1:
return False
return super().writable(fe, require)
@staticmethod @staticmethod
def native_sources() -> dict[str, str]: def native_sources() -> dict[str, str]:
return {"batocera-systems": SOURCE_URL} return {"batocera-systems": SOURCE_URL}
+14 -2
View File
@@ -11,7 +11,7 @@ from __future__ import annotations
import re import re
from .base_exporter import BaseExporter from .base_exporter import BaseExporter
from .baseline import NativeFile, NativeSystem, Report from .baseline import NativeFile, NativeSystem, Report, _hash_values
SOURCE_URL = ( SOURCE_URL = (
"https://raw.githubusercontent.com/TASEmulators/BizHawk/master" "https://raw.githubusercontent.com/TASEmulators/BizHawk/master"
@@ -60,6 +60,14 @@ class Exporter(BaseExporter):
# hang off exist nowhere else. # hang off exist nowhere else.
return True return True
def states(self, fe: NativeFile, field_name: str) -> bool:
"""A name that resolves to several SHA1s is left alone, so its
correction is not a change the database receives."""
touchable = getattr(self, "_touchable", None)
if field_name == "sha1" and touchable is not None and fe.name.casefold() not in touchable:
return False
return super().states(fe, field_name)
@staticmethod @staticmethod
def _unambiguous(systems: dict[str, NativeSystem]) -> dict[str, NativeFile]: def _unambiguous(systems: dict[str, NativeSystem]) -> dict[str, NativeFile]:
"""Files whose name identifies exactly one entry with a SHA1. """Files whose name identifies exactly one entry with a SHA1.
@@ -72,7 +80,10 @@ class Exporter(BaseExporter):
seen: dict[str, list[NativeFile]] = {} seen: dict[str, list[NativeFile]] = {}
for system in systems.values(): for system in systems.values():
for fe in system.files: for fe in system.files:
if fe.hash("sha1"): # A SHA1 BizHawk declares is BizHawk's own code, the authority
# for its cores: the truth's MSX.rom came from FBNeo and would
# have made MSXHawk refuse the dump it accepts.
if fe.hash("sha1") and not _hash_values(fe.platform or {}, "sha1"):
seen.setdefault(fe.name.casefold(), []).append(fe) seen.setdefault(fe.name.casefold(), []).append(fe)
resolved: dict[str, NativeFile] = {} resolved: dict[str, NativeFile] = {}
for name, entries in seen.items(): for name, entries in seen.items():
@@ -96,6 +107,7 @@ class Exporter(BaseExporter):
) )
index = self._unambiguous(systems) index = self._unambiguous(systems)
self._touchable = set(index)
def commented_out(text: str, position: int) -> bool: def commented_out(text: str, position: int) -> bool:
"""Whether the call sits on a line the compiler never sees. """Whether the call sits on a line the compiler never sees.
+31 -2
View File
@@ -90,8 +90,35 @@ class Exporter(BaseExporter):
def states(self, fe: NativeFile, field_name: str) -> bool: def states(self, fe: NativeFile, field_name: str) -> bool:
if field_name in fe.filled: if field_name in fe.filled:
return False return False
if field_name == "md5" and fe.native_system in self._refused_systems():
# The array was left as EmuDeck wrote it: nothing was corrected.
return False
return super().states(fe, field_name) return super().states(fe, field_name)
def _expected_md5s(
self, systems: dict[str, NativeSystem], name: str, system_id: str
) -> list[str]:
"""What a check must now hold; a refused rewrite keeps EmuDeck's own array."""
if name in getattr(self, "_withdrawn", {}):
return []
return self._md5s(systems, system_id)
def _refused_systems(self) -> set[str]:
withdrawn = getattr(self, "_withdrawn", {})
return {FUNCTION_HASH_MAP[name] for name in withdrawn if name in FUNCTION_HASH_MAP}
@staticmethod
def _corrected_away(systems: dict[str, NativeSystem], system_id: str) -> set[str]:
"""The platform md5 values a correction of ours replaces."""
return {
value
for system in systems.values()
if system.native_id == system_id
for fe in system.files
if "md5" in fe.corrections
for value in _hash_values(fe.platform or {}, "md5")
}
@classmethod @classmethod
def _md5s(cls, systems: dict[str, NativeSystem], system_id: str) -> list[str]: def _md5s(cls, systems: dict[str, NativeSystem], system_id: str) -> list[str]:
"""Every MD5 the system accepts, in a stable order, deduplicated.""" """Every MD5 the system accepts, in a stable order, deduplicated."""
@@ -151,7 +178,9 @@ class Exporter(BaseExporter):
# and our model does not was deleted: a user whose dump matched # and our model does not was deleted: a user whose dump matched
# it would stop passing the check. A rewrite that withdraws one # it would stop passing the check. A rewrite that withdraws one
# is refused and reported instead. # is refused and reported instead.
withdrawn = theirs - set(md5s) # A correction replaces the value it corrects, which is not a
# withdrawal; any other value missing from ours would be.
withdrawn = theirs - set(md5s) - self._corrected_away(systems, system_id)
if withdrawn: if withdrawn:
self._withdrawn.setdefault(name, set()).update(withdrawn) self._withdrawn.setdefault(name, set()).update(withdrawn)
elif set(md5s) != theirs: elif set(md5s) != theirs:
@@ -179,7 +208,7 @@ class Exporter(BaseExporter):
if name not in defined: if name not in defined:
issues.append(f"check absent from the output: {name}") issues.append(f"check absent from the output: {name}")
continue continue
md5s = self._md5s(systems, system_id) md5s = self._expected_md5s(systems, name, system_id)
if not md5s: if not md5s:
continue continue
body = next( body = next(
+10 -1
View File
@@ -13,7 +13,7 @@ import zipfile
from collections import OrderedDict from collections import OrderedDict
from .base_exporter import BaseExporter from .base_exporter import BaseExporter
from .baseline import NativeSystem, Report from .baseline import NativeFile, NativeSystem, Report
SOURCE_URL = ( SOURCE_URL = (
"https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip" "https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip"
@@ -57,6 +57,13 @@ class Exporter(BaseExporter):
with zipfile.ZipFile(io.BytesIO(raw)) as archive: with zipfile.ZipFile(io.BytesIO(raw)) as archive:
return {_DB_NAME: archive.read(_DB_NAME).decode("utf-8")} return {_DB_NAME: archive.read(_DB_NAME).decode("utf-8")}
def states(self, fe: NativeFile, field_name: str) -> bool:
"""A correction whose path the database does not hold is not written."""
written = getattr(self, "_written", None)
if written is not None and id(fe) not in written:
return False
return super().states(fe, field_name)
def _by_path(self, systems: dict[str, NativeSystem]) -> dict[str, object]: def _by_path(self, systems: dict[str, NativeSystem]) -> dict[str, object]:
indexed: dict[str, object] = {} indexed: dict[str, object] = {}
for system in systems.values(): for system in systems.values():
@@ -80,11 +87,13 @@ class Exporter(BaseExporter):
) )
database = json.loads(original, object_pairs_hook=OrderedDict) database = json.loads(original, object_pairs_hook=OrderedDict)
indexed = self._by_path(systems) indexed = self._by_path(systems)
self._written = set()
for path, entry in database.get("files", {}).items(): for path, entry in database.get("files", {}).items():
fe = indexed.get(path) fe = indexed.get(path)
if fe is None: if fe is None:
continue continue
self._written.add(id(fe))
md5 = fe.hash("md5") md5 = fe.hash("md5")
if md5: if md5:
entry["hash"] = md5 entry["hash"] = md5
+17 -4
View File
@@ -29,6 +29,9 @@ SOURCE_URL = (
class Exporter(BaseExporter): class Exporter(BaseExporter):
"""Write RomM's known_bios_files.json, corrected.""" """Write RomM's known_bios_files.json, corrected."""
# (system, filename) keys the platform itself declares, set by render.
_platform_keys: frozenset[tuple[str, str]] = frozenset()
@staticmethod @staticmethod
def platform_name() -> str: def platform_name() -> str:
return "romm" return "romm"
@@ -61,16 +64,20 @@ class Exporter(BaseExporter):
""" """
return native_id in SLUG_MAP return native_id in SLUG_MAP
@classmethod def writable(self, fe: NativeFile, require: str = "") -> bool: # noqa: ARG002
def writable(cls, fe: NativeFile, require: str = "") -> bool:
"""What RomM already ships stays; the conditions gate additions. """What RomM already ships stays; the conditions gate additions.
An entry of theirs that could never verify is still theirs, and the An entry of theirs that could never verify is still theirs, and the
round trip is not the place to decide otherwise. round trip is not the place to decide otherwise. An addition whose
slug:filename key the platform already holds cannot be written: the
fixture is a dict, and the second write replaced RomM's Dreamcast
boot.bin with fbneo's 480-byte file of the same name.
""" """
if fe.platform is not None: if fe.platform is not None:
return True return True
return cls._verifiable(fe) and cls._known_platform(fe.native_system) if (fe.native_system, fe.name) in self._platform_keys:
return False
return self._verifiable(fe) and self._known_platform(fe.native_system)
def render( def render(
self, self,
@@ -80,6 +87,12 @@ class Exporter(BaseExporter):
scraped: dict | None = None, scraped: dict | None = None,
) -> dict[str, str]: ) -> dict[str, str]:
output: OrderedDict[str, dict] = OrderedDict() output: OrderedDict[str, dict] = OrderedDict()
self._platform_keys = {
(system.native_id, fe.name)
for system in systems.values()
for fe in system.files
if fe.platform is not None
}
for system in sorted(systems.values(), key=lambda s: s.native_id): for system in sorted(systems.values(), key=lambda s: s.native_id):
for fe in sorted(system.files, key=lambda f: f.name): for fe in sorted(system.files, key=lambda f: f.name):
+15 -4
View File
@@ -6,6 +6,8 @@ libretro gives them, matching libretro-database/dat/System.dat.
from __future__ import annotations from __future__ import annotations
import re
import sys import sys
from pathlib import Path from pathlib import Path
@@ -21,9 +23,17 @@ SOURCE_URL = (
) )
def _quote(name: str) -> str: _QUOTED_NAME = re.compile(r'rom \( name "([^"]+)"')
"""Quote a ROM name the way the original does: only when it must be."""
return f'"{name}"' if any(c in name for c in ' ()') else name
def _quote(name: str, quoted: frozenset[str] = frozenset()) -> str:
"""Quote a ROM name the way the original does.
Where it must be, and where the maintainers chose to: the file quotes
246 names it did not need to, and writing them bare put a quoting change
on every one of those lines of the diff.
"""
return f'"{name}"' if name in quoted or any(c in name for c in ' ()') else name
class Exporter(BaseExporter): class Exporter(BaseExporter):
@@ -108,6 +118,7 @@ class Exporter(BaseExporter):
) -> dict[str, str]: ) -> dict[str, str]:
lines = self._header(originals, scraped) lines = self._header(originals, scraped)
lines.extend(["game (", '\tname "System"', '\tcomment "System"']) lines.extend(["game (", '\tname "System"', '\tcomment "System"'])
quoted = frozenset(_QUOTED_NAME.findall(originals.get(self.native_filename(), "")))
for system, files in sorted( for system, files in sorted(
self.exportable(systems), key=lambda pair: pair[0].native_id self.exportable(systems), key=lambda pair: pair[0].native_id
@@ -116,7 +127,7 @@ class Exporter(BaseExporter):
for fe in files: for fe in files:
if not any(fe.hash(h) for h in ("crc32", "md5", "sha1")): if not any(fe.hash(h) for h in ("crc32", "md5", "sha1")):
continue continue
parts = [f"name {_quote(self._rom_name(fe))}"] parts = [f"name {_quote(self._rom_name(fe), quoted)}"]
size = fe.size() size = fe.size()
if size: if size:
parts.append(f"size {size}") parts.append(f"size {size}")
+20 -2
View File
@@ -15,6 +15,7 @@ import json
import os import os
import shutil import shutil
import sys import sys
import tempfile
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@@ -270,6 +271,24 @@ def _load_gitignored_large_files() -> set[str]:
} }
def _restore(cached: str, path: str) -> None:
"""Copy a fetched asset to its registered path, whole or not at all.
An interrupted copy at the final name is a truncated file the next scan
hashes and publishes; a scratch beside it is swapped in only once full.
"""
directory = os.path.dirname(path)
os.makedirs(directory, exist_ok=True)
fd, scratch = tempfile.mkstemp(dir=directory, prefix=".restore-")
os.close(fd)
try:
shutil.copy2(cached, scratch)
os.replace(scratch, path)
finally:
if os.path.exists(scratch):
os.remove(scratch)
def _preserve_large_file_entries(files: dict, db_path: str) -> int: def _preserve_large_file_entries(files: dict, db_path: str) -> int:
"""Keep the entries of release assets the checkout does not hold. """Keep the entries of release assets the checkout does not hold.
@@ -309,8 +328,7 @@ def _preserve_large_file_entries(files: dict, db_path: str) -> int:
expected_md5=entry.get("md5", ""), expected_md5=entry.get("md5", ""),
) )
if cached and not os.path.exists(path): if cached and not os.path.exists(path):
os.makedirs(os.path.dirname(path), exist_ok=True) _restore(cached, path)
shutil.copy2(cached, path)
files[sha1] = entry files[sha1] = entry
count += 1 count += 1
return count return count
+201 -115
View File
@@ -73,6 +73,9 @@ from nativemode import (
reads_file_contents, reads_file_contents,
) )
from validation import ( from validation import (
existence_discrepancy,
destination_owners,
validated_choice,
inner_rom_check, inner_rom_check,
settle_mismatch, settle_mismatch,
agnostic_substitute, agnostic_substitute,
@@ -201,6 +204,18 @@ def _system_tag(system_filter: list[str] | None) -> str:
return "_" + "_".join(display_parts) return "_" + "_".join(display_parts)
_UNSAFE_IN_NAME = re.compile(r'[/\\:*?"<>|]')
def _name_part(text: str, space: str = "") -> str:
"""A display name made safe to stand in a file name.
'FinalBurn Neo (CPS-1/CPS-2)' named a directory that did not exist; a
Windows user cannot write ':' or '?' either.
"""
return _UNSAFE_IN_NAME.sub("-", text.replace(" ", space))
def _platform_pack_stem( def _platform_pack_stem(
group_platforms: list[str], representative: str, platforms_dir: str group_platforms: list[str], representative: str, platforms_dir: str
) -> str: ) -> str:
@@ -211,14 +226,14 @@ def _platform_pack_stem(
""" """
rep_cfg = load_platform_config(representative, platforms_dir) rep_cfg = load_platform_config(representative, platforms_dir)
version = rep_cfg.get("version", rep_cfg.get("dat_version", "")) version = rep_cfg.get("version", rep_cfg.get("dat_version", ""))
version_tag = f"_{version.replace(' ', '')}" if version else "" version_tag = f"_{_name_part(str(version))}" if version else ""
if len(group_platforms) <= 1: if len(group_platforms) <= 1:
return rep_cfg.get("platform", representative).replace(" ", "_") + version_tag return _name_part(rep_cfg.get("platform", representative), "_") + version_tag
names = [ names = [
load_platform_config(p, platforms_dir).get("platform", p) load_platform_config(p, platforms_dir).get("platform", p)
for p in group_platforms for p in group_platforms
] ]
return "_".join(n.replace(" ", "") for n in names) + version_tag return "_".join(_name_part(n) for n in names) + version_tag
@@ -562,13 +577,30 @@ def _select_variants(
return region_drops, region_fallbacks, slot_undecidable return region_drops, region_fallbacks, slot_undecidable
def _platform_pack_name(
platform_name: str,
platforms_dir: str,
narrowings: list[tuple[str, str]],
system_filter: list[str] | None,
pack_name: str | None = None,
) -> str:
"""File name of a platform pack: stem, narrowing tags, system tag.
A caller naming the pack itself (a --split part) is taken at its word.
"""
if pack_name:
return pack_name
stem = _platform_pack_stem([platform_name], platform_name, platforms_dir)
tags = "".join(tag for tag, _label in narrowings)
return f"{stem}{tags}_BIOS_Pack{_system_tag(system_filter)}.zip"
def generate_pack( def generate_pack(
platform_name: str, platform_name: str,
platforms_dir: str, platforms_dir: str,
db: dict, db: dict,
bios_dir: str, bios_dir: str,
output_dir: str, output_dir: str,
include_extras: bool = False,
emulators_dir: str = "emulators", emulators_dir: str = "emulators",
zip_contents: dict | None = None, zip_contents: dict | None = None,
data_registry: dict | None = None, data_registry: dict | None = None,
@@ -577,15 +609,21 @@ def generate_pack(
required_only: bool = False, required_only: bool = False,
system_filter: list[str] | None = None, system_filter: list[str] | None = None,
precomputed_extras: list[dict] | None = None, precomputed_extras: list[dict] | None = None,
extras_only: bool = False,
source: str = "full", source: str = "full",
flatten: bool = True, flatten: bool = True,
regions: list[str] | None = None, regions: list[str] | None = None,
target_name: str | None = None, target_name: str | None = None,
one_per_slot: bool = False, one_per_slot: bool = False,
offline: bool | None = None, offline: bool | None = None,
pack_name: str | None = None,
) -> str | None: ) -> str | None:
"""Generate a ZIP pack for a platform. """Generate a ZIP pack for a platform.
``pack_name`` replaces the derived file name: a split part is named after
its group, and a name built from every system of a hundred-system group
is longer than a file name can be.
Returns the path to the generated ZIP, or None on failure. Returns the path to the generated ZIP, or None on failure.
""" """
config = load_platform_config(platform_name, platforms_dir) config = load_platform_config(platform_name, platforms_dir)
@@ -603,10 +641,12 @@ def generate_pack(
narrowings = _narrowings( narrowings = _narrowings(
source, regions, target_name, one_per_slot, required_only source, regions, target_name, one_per_slot, required_only
) )
narrow_tags = "".join(tag for tag, _label in narrowings) zip_path = os.path.join(
stem = _platform_pack_stem([platform_name], platform_name, platforms_dir) output_dir,
zip_name = f"{stem}{narrow_tags}_BIOS_Pack{_system_tag(system_filter)}.zip" _platform_pack_name(
zip_path = os.path.join(output_dir, zip_name) platform_name, platforms_dir, narrowings, system_filter, pack_name
),
)
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
# Case-insensitive dedup only for platforms targeting Windows/macOS. # Case-insensitive dedup only for platforms targeting Windows/macOS.
@@ -643,6 +683,7 @@ def generate_pack(
for name in resolve_platform_cores(config, emu_profiles) for name in resolve_platform_cores(config, emu_profiles)
} }
validation_index = _build_validation_index(platform_profiles) validation_index = _build_validation_index(platform_profiles)
validation_owners = destination_owners(platform_profiles)
slot_overrides = slots.pack_overrides( slot_overrides = slots.pack_overrides(
config, emu_profiles or {}, db, zip_contents, data_registry config, emu_profiles or {}, db, zip_contents, data_registry
) )
@@ -675,6 +716,10 @@ def generate_pack(
) )
return None return None
pack_systems = filtered pack_systems = filtered
if extras_only:
# The part of a split that carries the core extras no platform system
# owns: the platform's own files and trees went to the other parts.
pack_systems = {}
preferred_entries: dict[str, int] = {} preferred_entries: dict[str, int] = {}
if source != "truth": if source != "truth":
@@ -851,8 +896,12 @@ def generate_pack(
file_reasons[dedup_key] = "not found" file_reasons[dedup_key] = "not found"
continue continue
if status == "hash_mismatch" and hash_mismatch_excludes_file( # Batocera hashes the member, never the ZIP: a zipped_file entry
verification_mode # is checked inside the archive whatever the outer resolution
# said, as verify does. An archive without the member passed.
inner_checked = bool(file_entry.get("zipped_file") and local_path)
if hash_mismatch_excludes_file(verification_mode) and (
status == "hash_mismatch" or inner_checked
): ):
zf_name = file_entry.get("zipped_file") zf_name = file_entry.get("zipped_file")
if zf_name and local_path: if zf_name and local_path:
@@ -888,15 +937,9 @@ def generate_pack(
# their code never reads the bytes. An upstream hash that # their code never reads the bytes. An upstream hash that
# contradicts a local dump is reported, not acted on. # contradicts a local dump is reported, not acted on.
if status == "hash_mismatch" and local_path: if status == "hash_mismatch" and local_path:
declared = file_entry.get("md5", "") or file_entry.get(
"sha1", ""
)
actual = compute_hashes(local_path)
file_reasons.setdefault( file_reasons.setdefault(
dedup_key, dedup_key,
f"packed per {platform_display} existence check; " existence_discrepancy(file_entry, local_path, platform_display),
f"declared hash {declared}, file md5 "
f"{actual['md5']} sha1 {actual['sha1']}",
) )
file_status.setdefault(dedup_key, "ok") file_status.setdefault(dedup_key, "ok")
@@ -904,33 +947,15 @@ def generate_pack(
# Platform verification (existence/md5) is the authority for pack status. # Platform verification (existence/md5) is the authority for pack status.
# Emulator checks are supplementary -logged but don't downgrade. # Emulator checks are supplementary -logged but don't downgrade.
# When a discrepancy is found, try to find a file satisfying both. # When a discrepancy is found, try to find a file satisfying both.
if ( if file_status.get(dedup_key) == "ok":
file_status.get(dedup_key) == "ok" local_path, disagreement = validated_choice(
and local_path file_entry, local_path, db, validation_index, bios_dir,
and validation_index digest_algorithm(verification_mode), dest, validation_owners,
):
fname = file_entry.get("name", "")
check = check_file_validation(
local_path, fname, validation_index, bios_dir
) )
if check: if disagreement:
reason, emus_list = check file_reasons.setdefault(
better = find_validated_variant( dedup_key, f"{platform_display} says OK but {disagreement}"
file_entry,
db,
local_path,
validation_index,
bios_dir,
platform_digest=digest_algorithm(verification_mode),
) )
if better:
local_path = better
else:
emus = ", ".join(emus_list)
file_reasons.setdefault(
dedup_key,
f"{platform_display} says OK but {emus} says {reason}",
)
if already_packed: if already_packed:
continue continue
@@ -1222,6 +1247,38 @@ def _archive_is_optional(archive_entry: dict, archive_name: str, files: list[dic
) )
def _pack_data_tree(
zf: zipfile.ZipFile,
local_cache: str,
dd_dest: str,
seen_destinations: set[str],
seen_parents: set[str],
seen_lower: set[str],
) -> int:
"""Write one data directory into an emulator pack, under its shared lock.
A refresh swaps the tree by rename; without the lock a walk half done
read the old tree and wrote files of the new one.
"""
added = 0
with cache_lock(local_cache, shared=True):
for root, _dirs, filenames in os.walk(local_cache):
for fname in filenames:
src = os.path.join(root, fname)
rel = os.path.relpath(src, local_cache)
full = f"{dd_dest}/{rel}" if dd_dest else rel
if full.lower() in seen_lower or _has_path_conflict(
full, seen_destinations, seen_parents
):
continue
seen_destinations.add(full)
_register_path(full, seen_destinations, seen_parents)
seen_lower.add(full.lower())
_add_pack_member(zf, src, full)
added += 1
return added
def generate_emulator_pack( def generate_emulator_pack(
profile_names: list[str], profile_names: list[str],
emulators_dir: str, emulators_dir: str,
@@ -1233,6 +1290,7 @@ def generate_emulator_pack(
required_only: bool = False, required_only: bool = False,
regions: list[str] | None = None, regions: list[str] | None = None,
offline: bool | None = None, offline: bool | None = None,
platforms_dir: str | None = None,
) -> str | None: ) -> str | None:
"""Generate a ZIP pack for specific emulator profiles.""" """Generate a ZIP pack for specific emulator profiles."""
all_profiles = load_emulator_profiles(emulators_dir, skip_aliases=False) all_profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
@@ -1246,7 +1304,7 @@ def generate_emulator_pack(
return None return None
# ZIP naming # ZIP naming
display_names = [p.get("emulator", n).replace(" ", "") for n, p in selected] display_names = [_name_part(p.get("emulator", n)) for n, p in selected]
validation_index = _build_validation_index(dict(selected)) validation_index = _build_validation_index(dict(selected))
narrow_tags = "".join( narrow_tags = "".join(
tag tag
@@ -1259,7 +1317,9 @@ def generate_emulator_pack(
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
region_drops: set[str] = ( region_drops: set[str] = (
emulator_region_drops(selected, standalone, regions) if regions else set() emulator_region_drops(selected, standalone, regions, required_only)
if regions
else set()
) )
total_files = 0 total_files = 0
@@ -1272,7 +1332,7 @@ def generate_emulator_pack(
seen_hashes: set[str] = set() # SHA1 dedup for same file, different path seen_hashes: set[str] = set() # SHA1 dedup for same file, different path
data_dir_notices: list[str] = [] data_dir_notices: list[str] = []
data_registry = load_data_dir_registry( data_registry = load_data_dir_registry(
os.path.join(os.path.dirname(__file__), "..", "platforms") platforms_dir or os.path.join(os.path.dirname(__file__), "..", "platforms")
) )
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
@@ -1303,20 +1363,9 @@ def generate_emulator_pack(
prefix = pack_structure.get(mode_key, "") prefix = pack_structure.get(mode_key, "")
if prefix: if prefix:
dd_dest = f"{prefix}/{dd_dest}" if dd_dest else prefix dd_dest = f"{prefix}/{dd_dest}" if dd_dest else prefix
for root, _dirs, filenames in os.walk(local_cache): total_files += _pack_data_tree(
for fname in filenames: zf, local_cache, dd_dest, seen_destinations, seen_parents, seen_lower
src = os.path.join(root, fname) )
rel = os.path.relpath(src, local_cache)
full = f"{dd_dest}/{rel}" if dd_dest else rel
if full.lower() in seen_lower:
continue
if _has_path_conflict(full, seen_destinations, seen_parents):
continue
seen_destinations.add(full)
_register_path(full, seen_destinations, seen_parents)
seen_lower.add(full.lower())
_add_pack_member(zf, src, full)
total_files += 1
if not files: if not files:
print(f" No files needed for {profile.get('emulator', emu_name)}") print(f" No files needed for {profile.get('emulator', emu_name)}")
@@ -1503,6 +1552,7 @@ def generate_system_pack(
required_only: bool = False, required_only: bool = False,
regions: list[str] | None = None, regions: list[str] | None = None,
offline: bool | None = None, offline: bool | None = None,
platforms_dir: str | None = None,
) -> str | None: ) -> str | None:
"""Generate a ZIP pack for all emulators supporting given system IDs.""" """Generate a ZIP pack for all emulators supporting given system IDs."""
profiles = load_emulator_profiles(emulators_dir) profiles = load_emulator_profiles(emulators_dir)
@@ -1539,36 +1589,41 @@ def generate_system_pack(
# Use system-based ZIP name # Use system-based ZIP name
sys_display = "_".join( sys_display = "_".join(
"_".join(w.title() for w in sid.split("-")) for sid in system_ids _name_part("_".join(w.title() for w in sid.split("-"))) for sid in system_ids
) )
result = generate_emulator_pack( # Built in a scratch directory: under its emulator name in output_dir it
matching, # overwrote, then carried away, an --emulator pack already there.
emulators_dir, os.makedirs(output_dir, exist_ok=True)
db, scratch = tempfile.mkdtemp(prefix=".system-", dir=output_dir)
bios_dir, try:
output_dir, result = generate_emulator_pack(
standalone, matching,
zip_contents, emulators_dir,
required_only=required_only, db,
regions=regions, bios_dir,
offline=offline, scratch,
) standalone,
if result: zip_contents,
# Rename to system-based name. Every dimension goes through the one required_only=required_only,
# list: keeping only the region tag made --system X --required-only regions=regions,
# overwrite the pack built without it. offline=offline,
platforms_dir=platforms_dir,
)
if not result:
return None
# Every dimension goes through the one list: keeping only the region
# tag made --system X --required-only overwrite the pack built without it.
tags = "".join( tags = "".join(
tag tag
for tag, _label in _narrowings( for tag, _label in _narrowings(
"full", regions, None, False, required_only, standalone=standalone "full", regions, None, False, required_only, standalone=standalone
) )
) )
new_name = f"{sys_display}{tags}_BIOS_Pack.zip" new_path = os.path.join(output_dir, f"{sys_display}{tags}_BIOS_Pack.zip")
new_path = os.path.join(output_dir, new_name) os.replace(result, new_path)
if new_path != result: return new_path
os.rename(result, new_path) finally:
result = new_path shutil.rmtree(scratch, ignore_errors=True)
return result
def list_platforms(platforms_dir: str) -> list[str]: def list_platforms(platforms_dir: str) -> list[str]:
@@ -1653,10 +1708,13 @@ def generate_split_packs(
# Two groupings write different files; without the tag they accumulate in # Two groupings write different files; without the tag they accumulate in
# one directory under one SHA256SUMS.txt. # one directory under one SHA256SUMS.txt.
group_tag = "" if group_by == "system" else f"_By{group_by.title()}" group_tag = "" if group_by == "system" else f"_By{group_by.title()}"
split_dir = os.path.join( # The parts carry the version, so the directory does too: a rescrape that
output_dir, # moves the version would otherwise leave the old parts beside the new
f"{platform_display.replace(' ', '_')}{split_tags}{group_tag}_Split", # under one SHA256SUMS.txt.
) version = config.get("version", config.get("dat_version", ""))
ver_tag = f"_{version.replace(' ', '')}" if version else ""
part_prefix = f"{platform_display.replace(' ', '_')}{ver_tag}{split_tags}"
split_dir = os.path.join(output_dir, f"{part_prefix}{group_tag}_Split")
os.makedirs(split_dir, exist_ok=True) os.makedirs(split_dir, exist_ok=True)
systems = config.get("systems", {}) systems = config.get("systems", {})
@@ -1702,25 +1760,14 @@ def generate_split_packs(
target_name=target_name, target_name=target_name,
one_per_slot=one_per_slot, one_per_slot=one_per_slot,
offline=offline, offline=offline,
pack_name=f"{part_prefix}_{_name_part(group_name, '_')}_BIOS_Pack.zip",
) )
if zip_path: if zip_path:
version = config.get("version", config.get("dat_version", ""))
ver_tag = f"_{version.replace(' ', '')}" if version else ""
narrow_tags = "".join(
tag
for tag, _label in _narrowings(
source, regions, target_name, one_per_slot, required_only
)
)
safe_group = group_name.replace(" ", "_")
new_name = f"{platform_display.replace(' ', '_')}{ver_tag}{narrow_tags}_{safe_group}_BIOS_Pack.zip"
new_path = os.path.join(split_dir, new_name)
if new_path != zip_path:
os.rename(zip_path, new_path)
zip_path = new_path
results.append(zip_path) results.append(zip_path)
# Warn about extras that couldn't be distributed (emulators without systems: field) # Extras whose system the platform does not declare, or that name none,
# belong to no group: they go in a part of their own, or the parts would
# not add up to the full pack (4507 of RetroArch's 5430 were left out).
distributed = { distributed = {
id(fe) id(fe)
for fe in _extras_for_systems( for fe in _extras_for_systems(
@@ -1729,12 +1776,17 @@ def generate_split_packs(
} }
undistributed = [fe for fe in all_extras if id(fe) not in distributed] undistributed = [fe for fe in all_extras if id(fe) not in distributed]
if undistributed: if undistributed:
emus = sorted({fe.get("source_emulator", "?") for fe in undistributed}) zip_path = generate_pack(
print( platform_name, platforms_dir, db, bios_dir, split_dir,
f" NOTE: {len(undistributed)} core extras from {len(emus)} emulators " emulators_dir=emulators_dir, zip_contents=zip_contents,
f"not in split packs (missing systems: field in profiles: " data_registry=data_registry, emu_profiles=emu_profiles,
f"{', '.join(emus[:5])}{'...' if len(emus) > 5 else ''})" target_cores=target_cores, required_only=required_only,
precomputed_extras=undistributed, extras_only=True, source=source,
regions=regions, target_name=target_name, one_per_slot=one_per_slot,
offline=offline, pack_name=f"{part_prefix}_Other_Cores_BIOS_Pack.zip",
) )
if zip_path:
results.append(zip_path)
return results return results
@@ -1792,7 +1844,7 @@ def generate_md5_pack(
) )
) )
zip_name = ( zip_name = (
f"{context_name.replace(' ', '_')}_Custom{custom_tags}_BIOS_Pack.zip" f"{_name_part(context_name, '_')}_Custom{custom_tags}_BIOS_Pack.zip"
) )
zip_path = os.path.join(output_dir, zip_name) zip_path = os.path.join(output_dir, zip_name)
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
@@ -2309,7 +2361,6 @@ def _run_platform_packs(
db, db,
args.bios_dir, args.bios_dir,
args.output_dir, args.output_dir,
include_extras=args.include_extras,
emulators_dir=args.emulators_dir, emulators_dir=args.emulators_dir,
zip_contents=zip_contents, zip_contents=zip_contents,
data_registry=data_registry, data_registry=data_registry,
@@ -2411,6 +2462,31 @@ def _refuse_for_all_variants(args, parser) -> None:
parser.error(f"{flag} is incompatible with --verify-packs") parser.error(f"{flag} is incompatible with --verify-packs")
def _refuse_refresh_data(args, parser) -> None:
"""Only the platform pack path refreshes the data directories.
Elsewhere the cache on disk was packed as it stood, and --offline wins
over the refresh without a word.
"""
if not args.refresh_data:
return
for flag, on in (
("--offline", args.offline),
("--emulator", args.emulator),
("--system", args.system and not (args.platform or args.all)),
("--from-md5", args.from_md5 or args.from_md5_file),
):
if on:
parser.error(f"--refresh-data is incompatible with {flag}")
def _refuse_include_archived(args, parser) -> None:
"""Only --all chooses among registered platforms; every other mode names
its own platform, emulator, system or hash."""
if args.include_archived and not args.all:
parser.error("--include-archived requires --all")
def _refuse_unapplied_flags(args, parser) -> None: def _refuse_unapplied_flags(args, parser) -> None:
"""Refuse every flag the requested mode would not apply. """Refuse every flag the requested mode would not apply.
@@ -2418,6 +2494,7 @@ def _refuse_unapplied_flags(args, parser) -> None:
about an artifact the caller did not name. Run before any quick-exit about an artifact the caller did not name. Run before any quick-exit
mode, since --verify-packs and --manifest-targets return early. mode, since --verify-packs and --manifest-targets return early.
""" """
_refuse_include_archived(args, parser)
# Parsed before the quick-exit modes: --verify-packs returns early and # Parsed before the quick-exit modes: --verify-packs returns early and
# still needs the region priority list to narrow its expectation. # still needs the region priority list to narrow its expectation.
args.regions = [] args.regions = []
@@ -2430,6 +2507,7 @@ def _refuse_unapplied_flags(args, parser) -> None:
parser.error("--region is incompatible with --manifest-targets") parser.error("--region is incompatible with --manifest-targets")
if args.one_per_slot and args.manifest_targets: if args.one_per_slot and args.manifest_targets:
parser.error("--one-per-slot is incompatible with --manifest-targets") parser.error("--one-per-slot is incompatible with --manifest-targets")
_refuse_refresh_data(args, parser)
_refuse_for_all_variants(args, parser) _refuse_for_all_variants(args, parser)
mode = next( mode = next(
@@ -2523,11 +2601,6 @@ def main():
parser.add_argument("--db", default=DEFAULT_DB_FILE, help="Path to database.json") parser.add_argument("--db", default=DEFAULT_DB_FILE, help="Path to database.json")
parser.add_argument("--bios-dir", default=DEFAULT_BIOS_DIR) parser.add_argument("--bios-dir", default=DEFAULT_BIOS_DIR)
parser.add_argument("--output-dir", "-o", default=DEFAULT_OUTPUT_DIR) parser.add_argument("--output-dir", "-o", default=DEFAULT_OUTPUT_DIR)
parser.add_argument(
"--include-extras",
action="store_true",
help="(no-op) Core requirements are always included",
)
parser.add_argument("--emulators-dir", default="emulators") parser.add_argument("--emulators-dir", default="emulators")
parser.add_argument( parser.add_argument(
"--offline", "--offline",
@@ -2700,6 +2773,7 @@ def main():
required_only=args.required_only, required_only=args.required_only,
regions=getattr(args, "regions", None), regions=getattr(args, "regions", None),
offline=args.offline, offline=args.offline,
platforms_dir=args.platforms_dir,
) )
if not built: if not built:
sys.exit(1) sys.exit(1)
@@ -2720,6 +2794,7 @@ def main():
required_only=args.required_only, required_only=args.required_only,
regions=getattr(args, "regions", None), regions=getattr(args, "regions", None),
offline=args.offline, offline=args.offline,
platforms_dir=args.platforms_dir,
) )
if not built: if not built:
sys.exit(1) sys.exit(1)
@@ -3039,6 +3114,8 @@ def generate_manifest(
name: emu_profiles[name] name: emu_profiles[name]
for name in _platform_cores(config, emu_profiles) for name in _platform_cores(config, emu_profiles)
} }
manifest_validation = _build_validation_index(manifest_profiles)
manifest_owners = destination_owners(manifest_profiles)
# Load registry for install metadata # Load registry for install metadata
registry: dict = {} registry: dict = {}
@@ -3179,6 +3256,12 @@ def generate_manifest(
): ):
record_omission(full_dest, file_entry, sys_id, status, None) record_omission(full_dest, file_entry, sys_id, status, None)
continue continue
# The variant the pack ships when an emulator rejects the
# resolved file: the installer must place the same bytes.
local_path, _disagreement = validated_choice(
file_entry, local_path, db, manifest_validation, bios_dir,
digest_algorithm(verification_mode), dest, manifest_owners,
)
# Get SHA1 and size. The installer fetches by hash, so record # Get SHA1 and size. The installer fetches by hash, so record
# the copy this repo holds: an upstream hash carried by no # the copy this repo holds: an upstream hash carried by no
@@ -3354,12 +3437,15 @@ def _target_cores_for(
) -> set[str] | None: ) -> set[str] | None:
"""Cores a target leaves available, or None when no target was asked for. """Cores a target leaves available, or None when no target was asked for.
None means no narrowing, which is what an unfiltered pack expects. None means no narrowing, which is what an unfiltered pack expects. Read
the way --all builds: a platform without a target file was packed
unnarrowed under the target's name, and checking that pack must not
raise where building it did not.
""" """
if not target_name: if not target_name:
return None return None
cache, _kept = build_target_cores_cache( cache, _kept = build_target_cores_cache(
[platform_name], target_name, platforms_dir [platform_name], target_name, platforms_dir, is_all=True
) )
return cache.get(platform_name) return cache.get(platform_name)
+69 -24
View File
@@ -15,6 +15,7 @@ import json
import os import os
import sys import sys
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, os.path.dirname(__file__))
from common import ( from common import (
@@ -33,17 +34,24 @@ import release_record
from verify import verify_platform from verify import verify_platform
# The repository's profiles wherever the script is run from: a bare
# "emulators" read the working directory, and a run elsewhere counted 0 cores.
_EMULATORS_DIR = str(Path(__file__).resolve().parent.parent / "emulators")
def compute_coverage( def compute_coverage(
platform_name: str, platform_name: str,
platforms_dir: str, platforms_dir: str,
db: dict, db: dict,
data_registry: dict | None = None, data_registry: dict | None = None,
supplemental_names: set[str] | None = None, supplemental_names: set[str] | None = None,
emulators_dir: str = _EMULATORS_DIR,
) -> dict: ) -> dict:
config = load_platform_config(platform_name, platforms_dir) config = load_platform_config(platform_name, platforms_dir)
result = verify_platform( result = verify_platform(
config, config,
db, db,
emulators_dir,
data_dir_registry=data_registry, data_dir_registry=data_registry,
supplemental_names=supplemental_names, supplemental_names=supplemental_names,
) )
@@ -59,7 +67,7 @@ def compute_coverage(
core_missing = len(undeclared) - core_present core_missing = len(undeclared) - core_present
# Files a profile marks unsourceable never reach the undeclared list, so # Files a profile marks unsourceable never reach the undeclared list, so
# the gap they represent has to be counted back in. # the gap they represent has to be counted back in.
profiles = load_emulator_profiles("emulators") profiles = load_emulator_profiles(emulators_dir)
unsourceable_names = { unsourceable_names = {
f.get("name", "") f.get("name", "")
for emu in resolve_platform_cores(config, profiles) for emu in resolve_platform_cores(config, profiles)
@@ -110,6 +118,44 @@ def release_totals(platform_name: str, record: dict) -> tuple[int | None, int |
return pack["files"], pack["extracted_size"] return pack["files"], pack["extracted_size"]
def extract_targets(platforms_dir: str) -> list[tuple[str, str]]:
"""(display name, folder) per registered platform, where its pack extracts.
Read from the registry so the README, the site and the pack guide answer
from one place. Sorted by display name.
"""
rows = [
(load_platform_config(key, platforms_dir).get("platform", key), entry["extract_to"])
for key, entry in load_platform_registry(platforms_dir).items()
if entry.get("extract_to")
]
return sorted(rows, key=lambda row: row[0].casefold())
def extract_notes(platforms_dir: str) -> list[str]:
"""A pack whose entries carry their own root extracts above the BIOS folder."""
folders = dict(extract_targets(platforms_dir))
notes = []
for key in sorted(load_platform_registry(platforms_dir)):
config = load_platform_config(key, platforms_dir)
display = config.get("platform", key)
if config.get("base_destination") == "" and display in folders:
notes.append(
f"The {display} pack already carries its own `bios/` folder, so it"
f" extracts into `{folders[display]}` rather than into the BIOS folder."
)
return notes
def _extract_cells(platforms_dir: str) -> dict[str, str]:
"""The download table's "Extract to" cell, by display name."""
return {display: f"`{folder}`" for display, folder in extract_targets(platforms_dir)}
def _paragraphs(texts: list[str]) -> list[str]:
return [line for text in texts for line in ("", text)]
def download_table( def download_table(
coverages: dict, coverages: dict,
archived: set[str], archived: set[str],
@@ -356,20 +402,7 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
# Where the pack itself is extracted, which is not always the BIOS folder: # Where the pack itself is extracted, which is not always the BIOS folder:
# a pack whose entries already carry their own root (RetroDECK) extracts # a pack whose entries already carry their own root (RetroDECK) extracts
# one level above it. # one level above it.
extract_paths = { extract_paths = _extract_cells(platforms_dir)
"RetroArch": "`system/`",
"Lakka": "`/storage/system/`",
"Batocera": "`/userdata/bios/`",
"BizHawk": "`Firmware/`",
"Recalbox": "`/recalbox/share/bios/`",
"RetroBat": "`bios/`",
"RetroPie": "`~/RetroPie/BIOS/`",
"RetroDECK": "`~/retrodeck/`",
"EmuDeck": "`~/Emulation/bios/`",
"RomM": "`bios/{platform_slug}/`",
"ROCKNIX": "`/storage/roms/bios/`",
"MiSTer FPGA": "`/media/fat/games/`",
}
archived = { archived = {
name name
for name, entry in load_platform_registry(platforms_dir).items() for name, entry in load_platform_registry(platforms_dir).items()
@@ -382,12 +415,10 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
) )
) )
lines.extend(_paragraphs(extract_notes(platforms_dir)))
if archived: if archived:
lines.extend( lines.extend(
[ [
"",
"The RetroDECK pack already contains its own `bios/` folder, so it"
" extracts into `~/retrodeck/` rather than into the BIOS folder.",
"", "",
"\\* Archived: the configuration is kept and packs are still built," "\\* Archived: the configuration is kept and packs are still built,"
" but upstream is no longer scraped on a schedule.", " but upstream is no longer scraped on a schedule.",
@@ -641,8 +672,14 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def generate_contributing() -> str: def contributing_text(link, title: str, site_words: str) -> str:
return f"""# Contributing to RetroBIOS """The contribution guide, one text for the repository and the site.
Two copies drifted apart: four steps to add a platform here, five on the
site. *link* renders a site page (absolute URL for the README, relative
.md for MkDocs); *site_words* names the site from where the text is read.
"""
return f"""{title}
## Add a BIOS file ## Add a BIOS file
@@ -651,7 +688,7 @@ def generate_contributing() -> str:
3. Variants (alternate hashes for the same file): `bios/Manufacturer/Console/.variants/` 3. Variants (alternate hashes for the same file): `bios/Manufacturer/Console/.variants/`
4. Open a Pull Request - hashes are verified automatically and reported as a comment 4. Open a Pull Request - hashes are verified automatically and reported as a comment
The [dump provenance]({SITE_URL}provenance/) page lists catalogued dumps still The [dump provenance]({link("provenance")}) page lists catalogued dumps still
missing from the collection, with their hashes. A file matching one of those is missing from the collection, with their hashes. A file matching one of those is
the most useful contribution. the most useful contribution.
@@ -662,7 +699,7 @@ the most useful contribution.
3. Register it in `platforms/_registry.yml` 3. Register it in `platforms/_registry.yml`
4. Generate the platform YAML and test: `python scripts/verify.py --platform <name>` 4. Generate the platform YAML and test: `python scripts/verify.py --platform <name>`
Full walkthrough: [adding a platform]({SITE_URL}wiki/adding-a-platform/). Full walkthrough: [adding a platform]({link("wiki/adding-a-platform")}).
## Add an emulator profile ## Add an emulator profile
@@ -672,7 +709,7 @@ Full walkthrough: [adding a platform]({SITE_URL}wiki/adding-a-platform/).
4. Write the YAML to `emulators/<name>.yml` 4. Write the YAML to `emulators/<name>.yml`
5. Test: `python scripts/cross_reference.py --emulator <name>` 5. Test: `python scripts/cross_reference.py --emulator <name>`
Full walkthrough: [profiling guide]({SITE_URL}wiki/profiling/). Full walkthrough: [profiling guide]({link("wiki/profiling")}).
## File conventions ## File conventions
@@ -697,10 +734,18 @@ configs, validates the YAML against the schemas, runs the test suite, and posts
a report on the PR. a report on the PR.
Contributors who add platform support are credited in the README, Contributors who add platform support are credited in the README,
on the documentation site, and in the BIOS packs. {site_words}, and in the BIOS packs.
""" """
def generate_contributing() -> str:
return contributing_text(
lambda page: f"{SITE_URL}{page}/",
"# Contributing to RetroBIOS",
"on the documentation site",
)
def main(): def main():
parser = argparse.ArgumentParser(description="Generate slim README.md") parser = argparse.ArgumentParser(description="Generate slim README.md")
parser.add_argument("--db", default="database.json") parser.add_argument("--db", default="database.json")
+138 -233
View File
@@ -31,20 +31,20 @@ from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, os.path.dirname(__file__))
from common import ( from common import (
build_zip_contents_index,
compute_composition, compute_composition,
count_catalog_matched, count_catalog_matched,
list_registered_platforms, list_registered_platforms,
load_database, load_database,
load_emulator_profiles, load_emulator_profiles,
load_provenance_snapshots, load_provenance_snapshots,
parse_md5_list,
preferred_profile,
require_yaml, require_yaml,
resolve_platform_cores,
unique_emulator_profiles, unique_emulator_profiles,
upstream_profile_index,
write_if_changed as _write_artifact, write_if_changed as _write_artifact,
yaml_load, yaml_load,
) )
from cross_reference import FileConsumers
from nativemode import reads_file_contents from nativemode import reads_file_contents
@@ -53,7 +53,13 @@ from nativemode import reads_file_contents
yaml = require_yaml() yaml = require_yaml()
from generate_readme import compute_coverage, release_totals from generate_readme import (
compute_coverage,
contributing_text,
extract_notes,
extract_targets,
release_totals,
)
from profile_sync import source_ref_values, split_source_ref from profile_sync import source_ref_values, split_source_ref
from provenance_report import build_report from provenance_report import build_report
import release_record import release_record
@@ -201,8 +207,22 @@ def _timestamp() -> str:
# Home page # Home page
def _extract_table(platforms_dir: str) -> list[str]:
"""The home page's extraction table, indented for its admonition."""
rows = [
f" | {display} | `{folder}` |"
for display, folder in extract_targets(platforms_dir)
]
notes = [f" {note}" for note in extract_notes(platforms_dir)]
return [" | Platform | Extract to |", " |----------|-----------|", *rows, "", *notes]
def generate_home( def generate_home(
db: dict, coverages: dict, profiles: dict, registry: dict | None = None db: dict,
coverages: dict,
profiles: dict,
registry: dict | None = None,
platforms_dir: str = "platforms",
) -> str: ) -> str:
total_files = db.get("total_files", 0) total_files = db.get("total_files", 0)
total_size = db.get("total_size", 0) total_size = db.get("total_size", 0)
@@ -318,24 +338,9 @@ def generate_home(
"", "",
'??? info "Where to extract"', '??? info "Where to extract"',
"", "",
" | Platform | Extract to |", *_extract_table(platforms_dir),
" |----------|-----------|", " Every other pack extracts straight into the BIOS folder."
" | RetroArch | `system/` |", " [Full instructions per setup](which-pack.md).",
" | Batocera | `/userdata/bios/` |",
" | BizHawk | `Firmware/` |",
" | EmuDeck | `~/Emulation/bios/` |",
" | Lakka | `/storage/system/` |",
" | MiSTer FPGA | `/media/fat/games/` |",
" | ROCKNIX | `/storage/roms/bios/` |",
" | Recalbox | `/recalbox/share/bios/` |",
" | RetroBat | `bios/` |",
" | RetroDECK | `~/retrodeck/` |",
" | RetroPie | `~/RetroPie/BIOS/` |",
" | RomM | `bios/{platform_slug}/` |",
"",
" The RetroDECK pack already carries its own `bios/` folder, so it "
"extracts one level above it. Every other pack extracts straight into "
"the BIOS folder. [Full instructions per setup](which-pack.md).",
"", "",
] ]
) )
@@ -525,7 +530,7 @@ def build_emulator_gap_report(
The gap analysis page and the published gaps export must not compute this The gap analysis page and the published gaps export must not compute this
twice and drift; both read this one report. twice and drift; both read this one report.
""" """
from common import expand_platform_declared_names from common import _norm_system_id, expand_platform_declared_names
from cross_reference import cross_reference as run_cross_reference from cross_reference import cross_reference as run_cross_reference
all_declared: set[str] = set() all_declared: set[str] = set()
@@ -538,7 +543,7 @@ def build_emulator_gap_report(
for fe in system.get("files", []): for fe in system.get("files", []):
fname = fe.get("name", "") fname = fe.get("name", "")
if fname: if fname:
declared.setdefault(sys_id, set()).add(fname) declared.setdefault(_norm_system_id(sys_id), set()).add(fname)
unique_profiles = { unique_profiles = {
k: v k: v
@@ -1647,8 +1652,7 @@ def generate_provenance_page(db: dict, report: dict) -> str:
def generate_system_page( def generate_system_page(
manufacturer: str, manufacturer: str,
consoles: dict[str, list], consoles: dict[str, list],
platform_files: dict[str, set], consumers: FileConsumers,
emulator_files: dict[str, dict],
) -> str: ) -> str:
manufacturer.lower().replace(" ", "-") manufacturer.lower().replace(" ", "-")
lines = [ lines = [
@@ -1671,13 +1675,9 @@ def generate_system_page(
md5_full = f.get("md5", "unknown") md5_full = f.get("md5", "unknown")
size = _fmt_size(f.get("size", 0)) size = _fmt_size(f.get("size", 0))
# Cross-reference # Who reads this file, not who reads a file of this name
plats = sorted(p for p, names in platform_files.items() if name in names) plats = sorted(consumers.platforms.get(sha1_full, ()))
emus = sorted( emus = sorted(consumers.emulators.get(sha1_full, ()))
e
for e, data in emulator_files.items()
if name in data.get("files", set())
)
# Truncated hashes for readability # Truncated hashes for readability
sha1_short = sha1_full[:12] if sha1_full != "unknown" else "-" sha1_short = sha1_full[:12] if sha1_full != "unknown" else "-"
@@ -1873,14 +1873,18 @@ def _file_badges(f: dict, in_repo: bool) -> list[str]:
badges.append( badges.append(
'<span class="rb-badge rb-badge-muted">optional</span>' '<span class="rb-badge rb-badge-muted">optional</span>'
) )
if not in_repo: if in_repo:
badges.append(
'<span class="rb-badge rb-badge-warning">missing</span>'
)
elif in_repo:
badges.append( badges.append(
'<span class="rb-badge rb-badge-success">in repo</span>' '<span class="rb-badge rb-badge-success">in repo</span>'
) )
elif f.get("unsourceable"):
badges.append(
'<span class="rb-badge rb-badge-muted">unsourceable</span>'
)
else:
badges.append(
'<span class="rb-badge rb-badge-warning">missing</span>'
)
if hle: if hle:
badges.append( badges.append(
'<span class="rb-badge rb-badge-info">HLE fallback</span>' '<span class="rb-badge rb-badge-info">HLE fallback</span>'
@@ -1931,7 +1935,7 @@ def _file_badges(f: dict, in_repo: bool) -> list[str]:
def _render_emulator_file( def _render_emulator_file(
f: dict, f: dict,
profile: dict, profile: dict,
platform_files: dict | None, consumers: FileConsumers | None,
files: list, files: list,
_file_available, _file_available,
) -> list[str]: ) -> list[str]:
@@ -2045,10 +2049,9 @@ def _render_emulator_file(
details.append( details.append(
f"Source: {_source_ref_markdown(profile, source_ref)}" f"Source: {_source_ref_markdown(profile, source_ref)}"
) )
if platform_files: if consumers:
plats = sorted( sha1 = consumers.sha1_of(f, f.get("path") or "")
p for p, names in platform_files.items() if fname in names plats = sorted(consumers.platforms.get(sha1, ())) if sha1 else []
)
if plats: if plats:
plat_links = [_platform_link(p, p, "../") for p in plats] plat_links = [_platform_link(p, p, "../") for p in plats]
details.append(f"Platforms: {', '.join(plat_links)}") details.append(f"Platforms: {', '.join(plat_links)}")
@@ -2217,55 +2220,75 @@ def _availability_check(db: dict, data_names):
missing while the gap report called it held would describe a missing while the gap report called it held would describe a
different collection on two pages of the same site. different collection on two pages of the same site.
""" """
from cross_reference import _resolve_source from cross_reference import entry_source
by_name = db.get("indexes", {}).get("by_name", {}) by_name = db.get("indexes", {}).get("by_name", {})
by_name_lower = {k.lower(): k for k in by_name} by_name_lower = {k.lower(): k for k in by_name}
by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {}) by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {})
by_md5 = db.get("indexes", {}).get("by_md5", {})
db_files = db.get("files", {}) index = {
"by_name": by_name,
"by_name_lower": by_name_lower,
"by_path_suffix": by_path_suffix,
"by_md5": db.get("indexes", {}).get("by_md5", {}),
"by_sha256": db.get("indexes", {}).get("by_sha256", {}),
"by_crc32": db.get("indexes", {}).get("by_crc32", {}),
"db_files": db.get("files", {}),
"data_names": data_names,
}
def _file_available(f: dict) -> bool: def _file_available(f: dict) -> bool:
"""Check if a file is available using the same resolution as cross_reference.""" """Check if a file is available using the same resolution as cross_reference."""
fname = f.get("name", "") # cross_reference sets these aside before resolving: the file found
if not fname: # under the name belongs to another game or release.
if not f.get("name") or f.get("unsourceable"):
return False return False
storage = f.get("storage", "") if f.get("storage", "") in ("release", "large_file"):
if storage in ("release", "large_file"):
return True return True
src = _resolve_source( return entry_source(f, index) is not None
fname, by_name, by_name_lower, data_names, by_path_suffix,
f, db_files,
)
if src is not None:
return True
path_field = f.get("path", "")
if path_field and path_field != fname:
src = _resolve_source(
path_field, by_name, by_name_lower, data_names,
by_path_suffix, f, db_files,
)
if src is not None:
return True
md5_raw = f.get("md5", "")
if md5_raw:
for md5_val in parse_md5_list(md5_raw):
if by_md5.get(md5_val):
return True
# A profile lists several sha1 when the code accepts several dumps.
sha1 = f.get("sha1") or []
if any(value in db_files for value in ([sha1] if isinstance(sha1, str) else sha1)):
return True
return False
return _file_available return _file_available
def _emulator_file_summary(files: list[dict], available) -> list[str]:
"""The counts above an emulator's file table, and its categories."""
in_repo_count = sum(1 for f in files if available(f))
unsourceable_count = sum(1 for f in files if f.get("unsourceable"))
missing_count = len(files) - in_repo_count - unsourceable_count
req_count = sum(1 for f in files if f.get("required"))
hle_count = sum(1 for f in files if f.get("hle_fallback"))
held = f"{in_repo_count} in repo, {missing_count} missing"
if unsourceable_count:
held += f", {unsourceable_count} unsourceable"
parts = [
f"**{len(files)} files**",
f"{req_count} required, {len(files) - req_count} optional",
held,
]
if hle_count:
parts.append(f"{hle_count} with HLE fallback")
lines = [" | ".join(parts)]
categories = [f.get("category", "bios") for f in files]
if "game_data" in categories or "bios_zip" in categories:
cats = [
f"{categories.count(key)} {label}"
for key, label in (
("bios", "BIOS"), ("game_data", "game data"), ("bios_zip", "BIOS ZIPs")
)
if categories.count(key)
]
lines.append(f"Categories: {', '.join(cats)}")
lines.append("")
return lines
def generate_emulator_page( def generate_emulator_page(
name: str, name: str,
profile: dict, profile: dict,
db: dict, db: dict,
platform_files: dict | None = None, consumers: FileConsumers | None = None,
data_names: set[str] | None = None, data_names: set[str] | None = None,
) -> str: ) -> str:
if profile.get("type") == "alias": if profile.get("type") == "alias":
@@ -2377,40 +2400,13 @@ def generate_emulator_page(
else: else:
_file_available = _availability_check(db, data_names) _file_available = _availability_check(db, data_names)
# Stats by category lines.extend(_emulator_file_summary(files, _file_available))
bios_files = [f for f in files if f.get("category", "bios") == "bios"]
game_data = [f for f in files if f.get("category") == "game_data"]
bios_zips = [f for f in files if f.get("category") == "bios_zip"]
in_repo_count = sum(1 for f in files if _file_available(f))
missing_count = len(files) - in_repo_count
req_count = sum(1 for f in files if f.get("required"))
opt_count = len(files) - req_count
hle_count = sum(1 for f in files if f.get("hle_fallback"))
parts = [f"**{len(files)} files**"]
parts.append(f"{req_count} required, {opt_count} optional")
parts.append(f"{in_repo_count} in repo, {missing_count} missing")
if hle_count:
parts.append(f"{hle_count} with HLE fallback")
lines.append(" | ".join(parts))
if game_data or bios_zips:
cats = []
if bios_files:
cats.append(f"{len(bios_files)} BIOS")
if game_data:
cats.append(f"{len(game_data)} game data")
if bios_zips:
cats.append(f"{len(bios_zips)} BIOS ZIPs")
lines.append(f"Categories: {', '.join(cats)}")
lines.append("")
# File table # File table
for f in files: for f in files:
lines.extend( lines.extend(
_render_emulator_file( _render_emulator_file(
f, profile, platform_files, files, _file_available f, profile, consumers, files, _file_available
) )
) )
@@ -2941,7 +2937,7 @@ def generate_gap_analysis(
def _render_xref_by_platform( def _render_xref_by_platform(
coverages: dict, unique: dict, core_to_profile: dict coverages: dict, unique: dict
) -> list[str]: ) -> list[str]:
"""Which cores each platform pulls in, and what they need.""" """Which cores each platform pulls in, and what they need."""
lines: list[str] = [] lines: list[str] = []
@@ -2960,26 +2956,14 @@ def _render_xref_by_platform(
) )
lines.append("") lines.append("")
# Resolve which profiles this platform uses # The cores the builder and verify resolve: a hand copy here dropped
if platform_cores == "all_libretro": # the libretro set a list naming retroarch pulls in, and the system
matched = { # fallback EmuDeck relies on.
k: v for k, v in unique.items() if "libretro" in v.get("type", "") matched = {
} key: unique[key]
elif isinstance(platform_cores, list): for key in resolve_platform_cores(config, unique)
matched = {} if key in unique
for cname in platform_cores: }
cname_str = str(cname)
if cname_str in unique:
matched[cname_str] = unique[cname_str]
elif cname_str in core_to_profile:
pkey = core_to_profile[cname_str]
matched[pkey] = unique[pkey]
else:
# Fallback: system intersection
psystems = set(config.get("systems", {}).keys())
matched = {
k: v for k, v in unique.items() if set(v.get("systems", [])) & psystems
}
if platform_cores == "all_libretro": if platform_cores == "all_libretro":
lines.append(f" **{len(matched)} cores** (all libretro)") lines.append(f" **{len(matched)} cores** (all libretro)")
@@ -3043,7 +3027,7 @@ def _render_xref_by_platform(
def _render_xref_by_upstream( def _render_xref_by_upstream(
coverages: dict, unique: dict, core_to_profile: dict coverages: dict, unique: dict
) -> list[str]: ) -> list[str]:
"""The same relation read the other way: one row per upstream.""" """The same relation read the other way: one row per upstream."""
lines: list[str] = [] lines: list[str] = []
@@ -3068,20 +3052,8 @@ def _render_xref_by_upstream(
# Build platform membership per core # Build platform membership per core
platform_membership: dict[str, set[str]] = {} platform_membership: dict[str, set[str]] = {}
for pname, cov in coverages.items(): for pname, cov in coverages.items():
config = cov["config"] for key in resolve_platform_cores(cov["config"], unique):
pcores = config.get("cores", []) platform_membership.setdefault(key, set()).add(pname)
if pcores == "all_libretro":
for k, v in unique.items():
if "libretro" in v.get("type", ""):
platform_membership.setdefault(k, set()).add(pname)
elif isinstance(pcores, list):
for cname in pcores:
cname_str = str(cname)
if cname_str in unique:
platform_membership.setdefault(cname_str, set()).add(pname)
elif cname_str in core_to_profile:
pkey = core_to_profile[cname_str]
platform_membership.setdefault(pkey, set()).add(pname)
for upstream_url in sorted(by_upstream.keys()): for upstream_url in sorted(by_upstream.keys()):
cores = by_upstream[upstream_url] cores = by_upstream[upstream_url]
@@ -3118,12 +3090,6 @@ def generate_cross_reference(
k: v for k, v in profiles.items() if v.get("type") not in ("alias", "test") k: v for k, v in profiles.items() if v.get("type") not in ("alias", "test")
} }
# Core name -> the profile it designates, the rule target filtering uses.
index = upstream_profile_index(unique)
core_to_profile: dict[str, str] = {
core: preferred_profile(index, core) for core in index
}
total_cores = len(unique) total_cores = len(unique)
total_upstreams = len({ total_upstreams = len({
p.get("upstream", p.get("source", "")) p.get("upstream", p.get("source", ""))
@@ -3147,72 +3113,17 @@ def generate_cross_reference(
"", "",
] ]
lines.extend(_render_xref_by_platform(coverages, unique, core_to_profile)) lines.extend(_render_xref_by_platform(coverages, unique))
lines.extend(_render_xref_by_upstream(coverages, unique, core_to_profile)) lines.extend(_render_xref_by_upstream(coverages, unique))
lines.extend(["", f"*Generated on {_timestamp()}*"]) lines.extend(["", f"*Generated on {_timestamp()}*"])
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def generate_contributing() -> str: def generate_contributing() -> str:
return """# Contributing - RetroBIOS return contributing_text(
lambda page: f"{page}.md", "# Contributing - RetroBIOS", "on this site"
## Add a BIOS file )
1. Fork this repository
2. Place the file in `bios/Manufacturer/Console/filename`
3. Variants (alternate hashes for the same file): place in `bios/Manufacturer/Console/.variants/`
4. Open a Pull Request - hashes are verified automatically and reported as a comment
The [dump provenance](provenance.md) page lists catalogued dumps still missing
from the collection, with their hashes. A file matching one of those is the
most useful contribution.
## Add a platform
1. Create a scraper in `scripts/scraper/` (inherit `BaseScraper`)
2. Read the platform's upstream source to determine how it checks BIOS files
3. Add an entry to `platforms/_registry.yml`
4. Generate the platform YAML config
5. Test: `python scripts/verify.py --platform <name>`
Full walkthrough: [adding a platform](wiki/adding-a-platform.md).
## Add an emulator profile
1. Clone the emulator's source code, upstream and libretro port
2. Trace the file loading from the entry point, not from a keyword grep
3. Document every file the code loads, with a `source_ref` line reference
4. Write the YAML to `emulators/<name>.yml`
5. Test: `python scripts/cross_reference.py --emulator <name>`
Full walkthrough: [profiling guide](wiki/profiling.md).
## File conventions
- `bios/Manufacturer/Console/filename` for canonical files
- `bios/Manufacturer/Console/.variants/filename.sha1prefix` for alternate versions
- Files >50 MB go in GitHub release assets (`large-files` release)
- RPG Maker and ScummVM directories are excluded from deduplication
- Two paths differing only by case break clones on Windows and macOS;
`tests/test_no_case_collisions.py` enforces this
## Before opening a PR
```bash
python -m unittest discover tests
python scripts/pipeline.py --offline
```
## PR validation
CI computes SHA1/MD5/CRC32 for every new file, checks them against the platform
configs, validates the YAML against the schemas, runs the test suite, and posts
a report on the PR.
Contributors who add platform support are credited in the README, on this site,
and in the BIOS packs.
"""
# Wiki pages # Wiki pages
@@ -3338,30 +3249,21 @@ def generate_wiki_data_model(db: dict, profiles: dict) -> str:
# Build cross-reference indexes # Build cross-reference indexes
def _build_platform_file_index(coverages: dict) -> dict[str, set]: def _build_file_consumers(db: dict, coverages: dict, profiles: dict) -> FileConsumers:
"""Map platform_name -> set of declared file names.""" """Which platforms and emulators read each collected file."""
index = {} consumers = FileConsumers(db, build_zip_contents_index(db))
for name, cov in coverages.items(): consumers.add_platforms({name: cov["config"] for name, cov in coverages.items()})
names = set() consumers.add_emulators(profiles)
config = cov["config"] return consumers
for system in config.get("systems", {}).values():
for fe in system.get("files", []):
names.add(fe.get("name", ""))
index[name] = names
return index
def _build_emulator_file_index(profiles: dict) -> dict[str, dict]: def _build_emulator_file_index(profiles: dict) -> dict[str, dict]:
"""Map emulator_name -> {files: set, systems: set} for cross-reference.""" """Map emulator_name -> {systems: set}, for the platform pages."""
index = {} return {
for name, profile in profiles.items(): name: {"systems": set(profile.get("systems", []))}
if profile.get("type") == "alias": for name, profile in profiles.items()
continue if profile.get("type") != "alias"
index[name] = { }
"files": {f.get("name", "") for f in profile.get("files", [])},
"systems": set(profile.get("systems", [])),
}
return index
# mkdocs.yml nav generator # mkdocs.yml nav generator
@@ -3687,7 +3589,8 @@ def main():
for name in sorted(platform_names): for name in sorted(platform_names):
try: try:
cov = compute_coverage( cov = compute_coverage(
name, args.platforms_dir, db, data_registry, suppl_names name, args.platforms_dir, db, data_registry, suppl_names,
emulators_dir=args.emulators_dir,
) )
coverages[name] = cov coverages[name] = cov
print( print(
@@ -3704,13 +3607,15 @@ def main():
) )
# Build cross-reference indexes # Build cross-reference indexes
platform_files = _build_platform_file_index(coverages) consumers = _build_file_consumers(db, coverages, profiles)
emulator_files = _build_emulator_file_index(profiles) emulator_files = _build_emulator_file_index(profiles)
# Generate home # Generate home
print("Generating home page...") print("Generating home page...")
write_if_changed( write_if_changed(
str(docs / "index.md"), generate_home(db, coverages, profiles, registry) str(docs / "index.md"), generate_home(
db, coverages, profiles, registry, args.platforms_dir
)
) )
stats = compute_stats(db, coverages, profiles) stats = compute_stats(db, coverages, profiles)
@@ -3759,7 +3664,7 @@ def main():
) )
for mfr, consoles in manufacturers.items(): for mfr, consoles in manufacturers.items():
slug = mfr.lower().replace(" ", "-") slug = mfr.lower().replace(" ", "-")
page = generate_system_page(mfr, consoles, platform_files, emulator_files) page = generate_system_page(mfr, consoles, consumers)
write_if_changed(str(docs / "systems" / f"{slug}.md"), page) write_if_changed(str(docs / "systems" / f"{slug}.md"), page)
# Generate emulator pages # Generate emulator pages
@@ -3773,7 +3678,7 @@ def main():
if profile.get("type") not in ("alias", "test") if profile.get("type") not in ("alias", "test")
} }
for name, profile in public_profiles.items(): for name, profile in public_profiles.items():
page = generate_emulator_page(name, profile, db, platform_files, suppl_names) page = generate_emulator_page(name, profile, db, consumers, suppl_names)
write_if_changed(str(docs / "emulators" / f"{name}.md"), page) write_if_changed(str(docs / "emulators" / f"{name}.md"), page)
# Generate cross-reference page # Generate cross-reference page
+59 -16
View File
@@ -163,11 +163,15 @@ def _fetch_asset(
hashes, expected_sha1, expected_md5 hashes, expected_sha1, expected_md5
): ):
return cached return cached
else: elif not _replaced_on_release(name, cached, offline):
# A verified copy of this asset that answers another hash: the # A copy of this asset that answers another hash: the caller
# caller wants a different revision under the same name. Keeping # wants a different revision under the same name, and the
# it is what lets the next caller for the primary find it. # release still serves this one. Keeping it is what lets the
# next caller for the primary find it.
return None return None
# Otherwise the release was re-uploaded since this copy was cached
# (gh release upload --clobber): only a download can answer, and
# it replaces the copy only if it verifies.
if offline: if offline:
return None return None
@@ -179,20 +183,9 @@ def _fetch_asset(
dir=dest_dir, prefix=os.path.basename(cached) + ".", suffix=".tmp" dir=dest_dir, prefix=os.path.basename(cached) + ".", suffix=".tmp"
) )
os.close(tmp_fd) os.close(tmp_fd)
# GitHub rewrites spaces to dots in release asset names, so a file whose
# name contains spaces is published under a dotted name.
candidates = [name]
if " " in name:
candidates.append(name.replace(" ", "."))
try: try:
downloaded = False downloaded = False
for candidate in candidates: for candidate, url in _asset_urls(name):
encoded_name = urllib.parse.quote(candidate)
url = (
f"https://github.com/{LARGE_FILES_REPO}/releases/download/"
f"{LARGE_FILES_RELEASE}/{encoded_name}"
)
try: try:
req = urllib.request.Request(url, headers={"User-Agent": "retrobios/1.0"}) req = urllib.request.Request(url, headers={"User-Agent": "retrobios/1.0"})
with urllib.request.urlopen(req, timeout=300) as resp: with urllib.request.urlopen(req, timeout=300) as resp:
@@ -220,6 +213,56 @@ def _fetch_asset(
_drop(tmp_path) _drop(tmp_path)
def _asset_urls(name: str) -> list[tuple[str, str]]:
"""Release URLs an asset may be published under, with their names.
GitHub rewrites spaces to dots in release asset names, so a file whose
name contains spaces is published under a dotted name.
"""
candidates = [name]
if " " in name:
candidates.append(name.replace(" ", "."))
return [
(
candidate,
(
f"https://github.com/{LARGE_FILES_REPO}/releases/download/"
f"{LARGE_FILES_RELEASE}/{urllib.parse.quote(candidate)}"
),
)
for candidate in candidates
]
def _replaced_on_release(name: str, cached: str, offline: bool) -> bool:
"""Whether the release now serves other bytes than the cached copy.
Sizes are compared, as check_release_assets does: a request per asset,
no download. Offline or unreadable, the copy is assumed current.
"""
if offline:
return False
served = _served_size(name)
return served is not None and served != os.path.getsize(cached)
def _served_size(name: str) -> int | None:
"""Size of the asset the release serves now, or None when unreadable."""
for candidate, url in _asset_urls(name):
try:
req = urllib.request.Request(
url, method="HEAD", headers={"User-Agent": "retrobios/1.0"}
)
with urllib.request.urlopen(req, timeout=60) as resp:
length = resp.headers.get("Content-Length")
except (urllib.error.URLError, OSError, http.client.HTTPException) as exc:
print(f" large file {candidate}: {exc}", file=sys.stderr)
continue
if length and length.isdigit():
return int(length)
return None
def _matches(hashes: dict, expected_sha1: str, expected_md5: str) -> bool: def _matches(hashes: dict, expected_sha1: str, expected_md5: str) -> bool:
"""Whether computed hashes answer the caller's declaration.""" """Whether computed hashes answer the caller's declaration."""
if expected_sha1 and hashes["sha1"].lower() != expected_sha1.lower(): if expected_sha1 and hashes["sha1"].lower() != expected_sha1.lower():
+10 -5
View File
@@ -14,6 +14,7 @@ from common import resolve_local_file
from common import runs_standalone from common import runs_standalone
from common import sanitize_pack_path from common import sanitize_pack_path
from common import size_fits from common import size_fits
from validation import read_from_system_dir
def _emulator_systems_index(emu_profiles: dict | None) -> dict[str, list[str]]: def _emulator_systems_index(emu_profiles: dict | None) -> dict[str, list[str]]:
"""Map both the profile key and its display name to the profile's systems. """Map both the profile key and its display name to the profile's systems.
@@ -564,9 +565,7 @@ def _collect_emulator_extras(
continue continue
if file_mode == "libretro" and is_standalone: if file_mode == "libretro" and is_standalone:
continue continue
# Skip files loaded from non-system directories (save_dir, content_dir) if not read_from_system_dir(f):
load_from = f.get("load_from", "")
if load_from and load_from != "system_dir":
continue continue
if is_standalone: if is_standalone:
raw = f.get("standalone_path") or f.get("path") or fname raw = f.get("standalone_path") or f.get("path") or fname
@@ -709,7 +708,10 @@ def platform_region_groups(
return groups, extra_dests return groups, extra_dests
def emulator_region_drops( def emulator_region_drops(
selected: list[tuple[str, dict]], standalone: bool, regions: list[str] selected: list[tuple[str, dict]],
standalone: bool,
regions: list[str],
required_only: bool = False,
) -> set[str]: ) -> set[str]:
"""Destinations an emulator-mode `--region` withdraws. """Destinations an emulator-mode `--region` withdraws.
@@ -726,7 +728,10 @@ def emulator_region_drops(
region_groups: dict[str, list[tuple[str, str]]] = {} region_groups: dict[str, list[tuple[str, str]]] = {}
for emu_name, profile in sorted(selected): for emu_name, profile in sorted(selected):
structure = profile.get("pack_structure") structure = profile.get("pack_structure")
for fe in filter_files_by_mode(profile.get("files", []), standalone): # Decided over what the pack keeps, as on the platform side: an
# optional winner removed afterwards left its slot empty.
entries = filter_files_by_mode(profile.get("files", []), standalone)
for fe in _kept(entries, required_only):
dest = _resolve_destination(fe, structure, standalone) dest = _resolve_destination(fe, structure, standalone)
if dest: if dest:
group_id = _emulator_region_group(emu_name, profile, fe) group_id = _emulator_region_group(emu_name, profile, fe)
+14 -17
View File
@@ -26,6 +26,7 @@ Usage:
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import contextlib
import re import re
import subprocess import subprocess
import sys import sys
@@ -33,7 +34,7 @@ import time
from pathlib import Path from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
from common import ArtifactLockBusy, artifact_lock from common import ArtifactLockBusy, artifact_lock, hold_artifact_lock
def run(cmd: list[str], label: str) -> tuple[bool, str]: def run(cmd: list[str], label: str) -> tuple[bool, str]:
@@ -119,11 +120,11 @@ def parse_pack_exclusions(output: str) -> dict[str, int]:
for line in output.splitlines(): for line in output.splitlines():
label_match = re.match(r"Generating (?:shared )?pack for (.+)\.\.\.", line) label_match = re.match(r"Generating (?:shared )?pack for (.+)\.\.\.", line)
if label_match: if label_match:
current_label = re.sub( # The same reading as parse_pack_counts: a reduced variant must
r"\s+\[source=[^\]]+\]$", "", label_match.group(1).strip() # not overwrite the full pack's count under the shared label.
) current_label = _full_pack_label(label_match.group(1).strip())
continue continue
if "files packed" not in line: if not current_label or "files packed" not in line:
continue continue
excluded_match = re.search(r"(\d+) unsafe excluded", line) excluded_match = re.search(r"(\d+) unsafe excluded", line)
exclusions[current_label] = ( exclusions[current_label] = (
@@ -317,12 +318,6 @@ def main():
parser.add_argument( parser.add_argument(
"--output-dir", default="dist", help="Pack output directory (default: dist/)" "--output-dir", default="dist", help="Pack output directory (default: dist/)"
) )
# --include-extras is now a no-op: core requirements are always included
parser.add_argument(
"--include-extras",
action="store_true",
help="(no-op) Core requirements are always included",
)
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)") parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
parser.add_argument("--source", choices=["platform", "truth", "full"], default="full") parser.add_argument("--source", choices=["platform", "truth", "full"], default="full")
parser.add_argument("--all-variants", action="store_true") parser.add_argument("--all-variants", action="store_true")
@@ -346,11 +341,14 @@ def main():
# A second run on the same output directory is refused before any work: # A second run on the same output directory is refused before any work:
# the database rebuild alone takes minutes, and the reader holding the # the database rebuild alone takes minutes, and the reader holding the
# directory would otherwise see the answer only after all of it. # directory would otherwise see the answer only after all of it. The
if not args.skip_packs and Path(args.output_dir).is_dir(): # hold lasts until this process exits: taken step by step, it let another
# run purge the packs between the build and the integrity check, which
# then reported every platform SKIP and passed.
held = contextlib.ExitStack()
if not args.skip_packs:
try: try:
with artifact_lock(args.output_dir): held.enter_context(hold_artifact_lock(args.output_dir))
pass
except ArtifactLockBusy as exc: except ArtifactLockBusy as exc:
print(f"ERROR: {exc}") print(f"ERROR: {exc}")
sys.exit(1) sys.exit(1)
@@ -525,8 +523,6 @@ def main():
pack_cmd.append("--include-archived") pack_cmd.append("--include-archived")
if args.offline: if args.offline:
pack_cmd.append("--offline") pack_cmd.append("--offline")
if args.include_extras:
pack_cmd.append("--include-extras")
if args.target: if args.target:
pack_cmd.extend(["--target", args.target]) pack_cmd.extend(["--target", args.target])
if args.source != "full": if args.source != "full":
@@ -667,6 +663,7 @@ def main():
print(f"{'=' * 60}") print(f"{'=' * 60}")
print(f" Pipeline {'COMPLETE' if all_ok else 'FINISHED WITH ERRORS'}") print(f" Pipeline {'COMPLETE' if all_ok else 'FINISHED WITH ERRORS'}")
print(f"{'=' * 60}") print(f"{'=' * 60}")
held.close()
sys.exit(0 if all_ok else 1) sys.exit(0 if all_ok else 1)
+187 -67
View File
@@ -71,6 +71,11 @@ class RefPart:
end: int | None end: int | None
raw: str = "" raw: str = ""
@property
def last(self) -> int | None:
"""Last cited line: the end of a range, or the single line."""
return self.end or self.start
@dataclass(frozen=True) @dataclass(frozen=True)
class AnchorResult: class AnchorResult:
@@ -227,6 +232,24 @@ def collect_tokens(entry: dict) -> list[str]:
return tokens return tokens
_VERSION_NOTE_RE = re.compile(r"\(\s*v?(\d+(?:\.\d+)+)\s*\)")
def cites_another_version(part: RefPart, core_version: object) -> str | None:
"""The version a part's annotation names, when it is not the documented one.
A profile may cite two programs side by side, `main.cpp:407-411 (v0.0.11)`
beside its v0.16.0 pin. The annotation was dropped as prose and the part
compared with the pin, where it fell on unrelated code yet read ANCHORED.
"""
match = _VERSION_NOTE_RE.search(part.raw or "")
if not match:
return None
declared = core_version.values() if isinstance(core_version, dict) else [core_version]
documented = {str(v or "").strip().lstrip("vV") for v in declared}
return None if match.group(1) in documented else match.group(1)
def is_external_citation(path: str) -> bool: def is_external_citation(path: str) -> bool:
"""True when a ref names a project rather than a path in a known repo. """True when a ref names a project rather than a path in a known repo.
@@ -657,8 +680,8 @@ def anchor_block(
pin = _normalize(pin_lines) pin = _normalize(pin_lines)
head = _normalize(head_lines) head = _normalize(head_lines)
lo, hi = start - 1, end lo, hi = start - 1, end
if lo < 0 or lo >= len(pin): if lo < 0 or hi > len(pin):
return AnchorResult("GONE", None, None, []) return AnchorResult("GONE", None, None, [], "cited range ends past the end of the file")
if not any(pin[lo:hi]): if not any(pin[lo:hi]):
return AnchorResult("CHANGED", None, None, [], "cited range is blank") return AnchorResult("CHANGED", None, None, [], "cited range is blank")
@@ -892,17 +915,22 @@ def anchor_part(
reason = "written against HEAD, pin names an older revision" reason = "written against HEAD, pin names an older revision"
return PartResult(part, "GONE", None, None, None, [], reason, slug, url) return PartResult(part, "GONE", None, None, None, [], reason, slug, url)
if part.start > len(pin_lines) and _written_against_head(part, head_lines): end = part.last
if end > len(pin_lines):
# The file is there and the line is not yet: the pinned revision is # The file is there and the line is not yet: the pinned revision is
# shorter than the one the ref was written against. nestopia cited # shorter than the one the ref was written against. nestopia cited
# the palette and database loads at 2041 and 2063, which is where # the palette and database loads at 2041 and 2063, which is where
# HEAD carries them, over a pin four hundred lines shorter. # HEAD carries them, over a pin four hundred lines shorter. A range
return PartResult( # that only ends past the file is no better: dolphin cited 239-323 in
part, "GONE", None, None, None, [], # a 317-line NANDImporter.cpp, and the block found above the end
"written against HEAD, pin names an older revision", slug, url, # anchored it.
reason = (
"written against HEAD, pin names an older revision"
if _written_against_head(part, head_lines)
else "cited range ends past the end of the file"
) )
return PartResult(part, "GONE", None, None, None, [], reason, slug, url)
end = part.end or part.start
anchored = anchor_block(pin_lines, head_lines, part.start, end) anchored = anchor_block(pin_lines, head_lines, part.start, end)
note = anchored.reason note = anchored.reason
if anchored.status in REVIEW_STATUSES: if anchored.status in REVIEW_STATUSES:
@@ -1150,7 +1178,7 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
) )
if part.start is None: if part.start is None:
return PartResult(part, "ANCHORED", None, None, None, []) return PartResult(part, "ANCHORED", None, None, None, [])
if part.start > len(pin_lines): if part.last > len(pin_lines):
return PartResult( return PartResult(
part, "GONE", None, None, None, [], "beyond the end of the file" part, "GONE", None, None, None, [], "beyond the end of the file"
) )
@@ -1167,7 +1195,7 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
# per line, so the window reaches forward as far as the entry has members. # per line, so the window reaches forward as far as the entry has members.
reach = SELF_CHECK_CONTEXT + len(tokens) reach = SELF_CHECK_CONTEXT + len(tokens)
lo = max(0, part.start - 1 - SELF_CHECK_CONTEXT) lo = max(0, part.start - 1 - SELF_CHECK_CONTEXT)
hi = min(len(pin_lines), (part.end or part.start) + reach) hi = min(len(pin_lines), part.last + reach)
window = "\n".join(pin_lines[lo:hi]).lower() window = "\n".join(pin_lines[lo:hi]).lower()
if any(token in window for token in tokens): if any(token in window for token in tokens):
return PartResult(part, "ANCHORED", None, None, None, []) return PartResult(part, "ANCHORED", None, None, None, [])
@@ -1183,8 +1211,8 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
{ {
index index
for index, line in enumerate(pin_lines, 1) for index, line in enumerate(pin_lines, 1)
for token in tokens for token in hash_tokens
if token in line.lower() if token.lower() in line.lower()
} }
) )
if not elsewhere: if not elsewhere:
@@ -1670,12 +1698,26 @@ def build_report(
view, _ = resolve_path(part.path, part.start, tuple(tokens)) view, _ = resolve_path(part.path, part.start, tuple(tokens))
return view.pin == view.head return view.pin == view.head
def judge(part, flag, tokens, hashes) -> PartResult:
other = cites_another_version(part, profile.get("core_version"))
if other:
# ti99sim cites v0.0.11 beside its v0.16.0 pin: no revision of
# the pinned repository holds that program, so it cannot anchor.
return PartResult(
part, "EXTERNAL", None, None, None, [],
f"annotated for version {other}, not the documented one",
)
if flag:
return verify_at_pin(
part, fetch(PIN, part.path, part.start, tokens), tokens, hashes
)
return anchor_across_views(part, tokens)
staged = [] staged = []
for entry_name, ref, tokens, hashes, ref_parts, citation in refs: for entry_name, ref, tokens, hashes, ref_parts, citation in refs:
flags = [self_checked(part, tokens) for part in ref_parts] flags = [self_checked(part, tokens) for part in ref_parts]
judged = [ judged = [
verify_at_pin(part, fetch(PIN, part.path, part.start, tokens), tokens, hashes) judge(part, flag, tokens, hashes)
if flag else anchor_across_views(part, tokens)
for part, flag in zip(ref_parts, flags) for part, flag in zip(ref_parts, flags)
] ]
staged.append((entry_name, ref, citation, reconcile_self_check(judged, flags))) staged.append((entry_name, ref, citation, reconcile_self_check(judged, flags)))
@@ -2459,6 +2501,15 @@ def bump_commit(
if pending_recale(report, accept_changed, text): if pending_recale(report, accept_changed, text):
return False return False
document = yaml.safe_load(text) document = yaml.safe_load(text)
if any(isinstance(document.get(f), dict) for f in ("source_commit", "upstream_commit")):
# A pin per build mode names one revision per repository, and the
# report heads only the primary one: rewriting str(dict) was a
# refused write on mesence, supermodel and xemu.
print(
f"{path.stem}: pins keyed by build mode are advanced by hand",
file=sys.stderr,
)
return False
upstream_moves = bool( upstream_moves = bool(
report.upstream_head and report.upstream_pin != report.upstream_head report.upstream_head and report.upstream_pin != report.upstream_head
) )
@@ -2466,9 +2517,6 @@ def bump_commit(
# Rewriting the pin to the value it already holds is not a bump, and # Rewriting the pin to the value it already holds is not a bump, and
# announcing it buries the profiles that did move. # announcing it buries the profiles that did move.
return False 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 = dict(document)
expected["source_commit"] = report.head expected["source_commit"] = report.head
new_text = _set_pin(text, document, "source_commit", report.head, "profiled_date") new_text = _set_pin(text, document, "source_commit", report.head, "profiled_date")
@@ -2622,10 +2670,14 @@ def _realign_part(
def _writing_pairs( def _writing_pairs(
document: dict, repos: list, revisions: list[tuple[str, dict]], intro_sha: str document: dict, repos: list, revisions: list[tuple[str, dict]], intro_sha: str
) -> tuple[list, list[str]]: ) -> tuple[list[list], list[str]]:
"""(repo, writing pin, current pin) per declared repository, and the """Every reading of where the text was written, and the ambiguities.
repositories whose writing pin the history cannot single out."""
pairs = [] Each reading lists (repo, writing pin, current pin) per declared
repository. A repository whose text lived under several pins gives one
reading per pin; the caller settles them when they all agree.
"""
options: list[list] = []
ambiguous: list[str] = [] ambiguous: list[str] = []
for pin_field, repo in repos: for pin_field, repo in repos:
current = document.get(f"{pin_field}_commit") current = document.get(f"{pin_field}_commit")
@@ -2634,9 +2686,58 @@ def _writing_pairs(
written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit") written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit")
if len(written) > 1: if len(written) > 1:
ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}") ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}")
elif written and written[0] != current: # The current pin among the candidates reads as "no move" (None) for
pairs.append((repo, written[0], current)) # that repository, which the other candidates must then agree with.
return pairs, ambiguous options.append([
(repo, pin, current) if pin != current else None for pin in written
])
readings = [
[pair for pair in combo if pair is not None]
for combo in itertools.product(*options)
]
return readings, ambiguous
def _realign_citation(
citation: Citation,
readings: list[list],
ambiguous: list[str],
cache_dir: str,
offline: bool,
) -> tuple[dict[int, tuple[int, int, str | None]], list[str]]:
"""The moves one prose run needs, and what blocks them.
Several pins under one text: the history cannot say which the text
describes, but when the cited range lands in the same place from every
one of them the question does not matter. When the readings disagree,
moving from the wrong one rewrites a correct citation onto someone
else's code, with nothing to show it happened.
"""
moves: dict[int, tuple[int, int, str | None]] = {}
blocked: list[str] = []
for index, part in enumerate(citation.parts):
outcomes = {
repr(result): result
for result in (
_realign_part(part, pairs, cache_dir, offline) if pairs else None
for pairs in readings
)
}
if len(outcomes) > 1:
blocked.extend(
f"pin moved under this text: {item}; the readings disagree"
for item in ambiguous
)
continue
outcome = next(iter(outcomes.values()))
if outcome is None:
continue
state, payload = outcome
if state == "ok":
moves[index] = (payload[0], payload[1], None)
else:
blocked.append(payload)
return moves, blocked
def realign_prose( def realign_prose(
@@ -2697,30 +2798,10 @@ def realign_prose(
if intro_sha is None: if intro_sha is None:
# The scalar is not committed yet: written now, under this pin. # The scalar is not committed yet: written now, under this pin.
continue continue
pairs, ambiguous = _writing_pairs(document, repos, revisions, intro_sha) readings, ambiguous = _writing_pairs(document, repos, revisions, intro_sha)
if ambiguous: if not any(readings):
# Moving from the wrong one rewrites a correct citation onto
# someone else's code, with nothing to show it happened.
messages.extend(
f"read again: {citation.field}: {citation.ref} (pin moved under "
f"this text: {item}; the history does not say which it describes)"
for item in ambiguous
)
continue continue
if not pairs: moves, blocked = _realign_citation(citation, readings, ambiguous, cache_dir, offline)
continue
moves: dict[int, tuple[int, int, str | None]] = {}
blocked: list[str] = []
for index, part in enumerate(citation.parts):
outcome = _realign_part(part, pairs, cache_dir, offline)
if outcome is None:
continue
state, payload = outcome
if state == "ok":
start, end = payload
moves[index] = (start, end, None)
else:
blocked.append(payload)
if blocked: if blocked:
# Half a run must not move: the untouched ranges would read as # Half a run must not move: the untouched ranges would read as
# already realigned when they were never even located. # already realigned when they were never even located.
@@ -2747,14 +2828,39 @@ def realign_prose(
return messages return messages
def _refuse_dirty_tree(emulators_dir: str) -> None:
"""Writes need a clean profile tree to roll back to."""
try:
dirty = emulators_dir_is_dirty(emulators_dir)
except RuntimeError as e:
print(f"{e}. Pass --force to write anyway.", file=sys.stderr)
raise SystemExit(1) from e
if dirty:
print(
f"{emulators_dir} carries uncommitted changes. "
"Commit them first or pass --force.",
file=sys.stderr,
)
raise SystemExit(1)
def emulators_dir_is_dirty(emulators_dir: str) -> bool: def emulators_dir_is_dirty(emulators_dir: str) -> bool:
"""True when the profile directory carries uncommitted changes.""" """True when the profile directory carries uncommitted changes.
Git is asked from inside the directory, so a profile tree that lives in
another repository answers for itself. A directory git cannot read is
never reported clean: the writes have nothing to roll back to.
"""
result = subprocess.run( result = subprocess.run(
["git", "status", "--porcelain", "--", emulators_dir], ["git", "-C", emulators_dir, "status", "--porcelain", "--", "."],
capture_output=True, capture_output=True,
text=True, text=True,
check=False, check=False,
) )
if result.returncode:
raise RuntimeError(
f"cannot read the git status of {emulators_dir}: {result.stderr.strip()}"
)
return bool(result.stdout.strip()) return bool(result.stdout.strip())
@@ -2856,16 +2962,40 @@ def _check_quota(count: int, offline: bool) -> None:
raise SystemExit(1) raise SystemExit(1)
def _stale_prose_blocks(name: str, path: Path, args) -> bool:
"""Whether prose written under an older pin has to move first.
Anchored from the current pin, a recale or a bump follows someone else's
code (boom3 FileSystem.cpp:2125, written at b810234e, an unrelated
OpenFileRead at 132dfddb). It moves first, from the pin it was written at.
"""
stale = realign_prose(path, args.cache_dir, args.offline, dry_run=True)
if stale:
print(
f"{name}: {len(stale)} prose citation(s) written under an older pin; "
"run --realign-prose or read them before the pin moves",
file=sys.stderr,
)
return bool(stale)
def _backfill(args, name: str, profile: dict, report: ProfileReport, path: Path) -> None:
"""Write the pin a profile without source_commit was written at."""
if not report.pin or profile.get("source_commit"):
return
if args.dry_run:
print(f"{name}: would write source_commit {report.pin[:7]}")
elif backfill_commit(path, report.pin):
print(f"{name}: source_commit {report.pin[:7]}")
def _apply_writes(args, name: str, profile: dict, report: ProfileReport) -> None: def _apply_writes(args, name: str, profile: dict, report: ProfileReport) -> None:
path = Path(args.emulators_dir) / f"{name}.yml" path = Path(args.emulators_dir) / f"{name}.yml"
if not path.is_file(): if not path.is_file():
return return
if args.backfill_commits and report.pin and not profile.get("source_commit"): if args.backfill_commits:
if args.dry_run: _backfill(args, name, profile, report, path)
print(f"{name}: would write source_commit {report.pin[:7]}") if not (args.rebase_refs or args.bump_commit) or _stale_prose_blocks(name, path, args):
elif backfill_commit(path, report.pin):
print(f"{name}: source_commit {report.pin[:7]}")
if not (args.rebase_refs or args.bump_commit):
return return
with tempfile.TemporaryDirectory() as scratch: with tempfile.TemporaryDirectory() as scratch:
# Always work on a copy. A dry run reports what it left there; a real # Always work on a copy. A dry run reports what it left there; a real
@@ -3123,18 +3253,8 @@ def main() -> None:
args.backfill_commits or args.rebase_refs or args.bump_commit args.backfill_commits or args.rebase_refs or args.bump_commit
or args.realign_prose or args.realign_prose
) )
if ( if writes and not args.dry_run and not args.force:
writes _refuse_dirty_tree(args.emulators_dir)
and not args.dry_run
and not args.force
and emulators_dir_is_dirty(args.emulators_dir)
):
print(
f"{args.emulators_dir} carries uncommitted changes. "
"Commit them first or pass --force.",
file=sys.stderr,
)
raise SystemExit(1)
if args.realign_prose: if args.realign_prose:
for name in selected: for name in selected:
+14 -5
View File
@@ -255,16 +255,25 @@ def fallback_groups(
index: dict[str, dict], index: dict[str, dict],
requested: list[str], requested: list[str],
) -> list[str]: ) -> list[str]:
"""Group IDs where no candidate matched, so the whole group was kept.""" """Group IDs where no candidate matched, so the whole group was kept.
A world file, or one that declares no region at all, answers every
region: the C64 kernal VICE loads by default is untagged, and a group
holding it beside a Japanese and a Swedish kernal does have a BIOS for
North America.
"""
if not requested: if not requested:
return [] return []
out: list[str] = [] out: list[str] = []
for group_id, members in groups.items(): for group_id, members in groups.items():
ranked = _competing_ranks(members, index, requested) ranked = _competing_ranks(members, index, requested)
has_world = any( served = any(
WORLD in lookup_regions(index, destination, name) not regions or WORLD in regions
for destination, name in members for regions in (
lookup_regions(index, destination, name)
for destination, name in members
)
) )
if ranked and not has_world and min(r for r, _ in ranked) == len(requested): if ranked and not served and min(r for r, _ in ranked) == len(requested):
out.append(group_id) out.append(group_id)
return sorted(out) return sorted(out)
+30 -13
View File
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import os
import sys import sys
import urllib.error import urllib.error
import urllib.request import urllib.request
@@ -225,22 +226,38 @@ class BaseScraper(ABC):
... ...
def fetch_github_latest_version(repo: str) -> str | None: def github_headers() -> dict[str, str]:
"""Fetch the latest release version tag from a GitHub repo.""" """Headers for api.github.com, authenticated when a token is set.
Anonymous calls share a quota of 60 per hour, so a scrape run after a
few others gets 403 for every request.
"""
headers = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
def fetch_github_latest_version(repo: str) -> str:
"""Return the tag of the latest release of a GitHub repo.
Raises instead of returning a fallback: a platform YAML written without
the release it was read from cannot be patched back by the exporter.
"""
url = f"https://api.github.com/repos/{repo}/releases/latest" url = f"https://api.github.com/repos/{repo}/releases/latest"
try: try:
req = urllib.request.Request( req = urllib.request.Request(url, headers=github_headers())
url,
headers={
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
},
)
with urllib.request.urlopen(req, timeout=15) as resp: with urllib.request.urlopen(req, timeout=15) as resp:
data = json.loads(resp.read()) tag = json.loads(resp.read()).get("tag_name", "")
return data.get("tag_name", "") except (urllib.error.URLError, json.JSONDecodeError) as e:
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError): raise RuntimeError(f"cannot read the latest release of {repo}: {e}") from e
return None if not tag:
raise RuntimeError(f"the latest release of {repo} names no tag")
return tag
class _PlatformDumper(yaml.SafeDumper): class _PlatformDumper(yaml.SafeDumper):
+28 -41
View File
@@ -19,7 +19,12 @@ from pathlib import Path
from common import yaml_load from common import yaml_load
from .base_scraper import BaseScraper, BiosRequirement, requirement_entry from .base_scraper import (
BaseScraper,
BiosRequirement,
github_headers,
requirement_entry,
)
PLATFORM_NAME = "batocera" PLATFORM_NAME = "batocera"
@@ -38,39 +43,32 @@ def pick_stable_tag(names: list[str]) -> str | None:
return max(stable)[1] if stable else None return max(stable)[1] if stable else None
def fetch_stable_tag() -> str | None: def fetch_stable_tag() -> str:
"""Return the newest stable batocera-N(.M) tag name.""" """Return the newest stable batocera-N(.M) tag name.
import json
import os
import urllib.error
import urllib.request
Raises rather than falling back to master: the YAML records the tag
it was read from, and the exporter patches that revision.
"""
url = "https://api.github.com/repos/batocera-linux/batocera.linux/tags?per_page=100" url = "https://api.github.com/repos/batocera-linux/batocera.linux/tags?per_page=100"
headers = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
try: try:
req = urllib.request.Request(url, headers=headers) req = urllib.request.Request(url, headers=github_headers())
with urllib.request.urlopen(req, timeout=15) as resp: with urllib.request.urlopen(req, timeout=15) as resp:
tags = json.loads(resp.read()) tags = json.loads(resp.read())
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError): except (urllib.error.URLError, json.JSONDecodeError) as e:
return None raise RuntimeError(f"cannot list batocera tags: {e}") from e
return pick_stable_tag([tag["name"] for tag in tags]) tag = pick_stable_tag([tag["name"] for tag in tags])
if not tag:
raise RuntimeError("no stable batocera tag among the latest 100")
return tag
_STABLE_TAG = fetch_stable_tag() or "master"
SOURCE_URL = ( SOURCE_URL = (
f"{_RAW_BASE}/{_STABLE_TAG}" _RAW_BASE + "/{tag}"
"/package/batocera/core/batocera-scripts/scripts/batocera-systems" "/package/batocera/core/batocera-scripts/scripts/batocera-systems"
) )
CONFIGGEN_DEFAULTS_URL = ( CONFIGGEN_DEFAULTS_URL = (
f"{_RAW_BASE}/{_STABLE_TAG}" _RAW_BASE + "/{tag}"
"/package/batocera/core/batocera-configgen/configs/" "/package/batocera/core/batocera-configgen/configs/"
"configgen-defaults.yml" "configgen-defaults.yml"
) )
@@ -186,8 +184,9 @@ def _resolve_truncated_md5(md5: str, md5_index: dict[str, str]) -> str:
class Scraper(BaseScraper): class Scraper(BaseScraper):
"""Scraper for batocera-systems Python dict.""" """Scraper for batocera-systems Python dict."""
def __init__(self, url: str = SOURCE_URL): def __init__(self):
super().__init__(url=url) self.tag = fetch_stable_tag()
super().__init__(url=SOURCE_URL.format(tag=self.tag))
def _fetch_cores(self) -> tuple[list[str], list[str]]: def _fetch_cores(self) -> tuple[list[str], list[str]]:
"""Extract core names and standalone cores from configgen-defaults.yml. """Extract core names and standalone cores from configgen-defaults.yml.
@@ -195,16 +194,17 @@ class Scraper(BaseScraper):
Returns (all_cores, standalone_cores) where standalone_cores are Returns (all_cores, standalone_cores) where standalone_cores are
those with emulator != "libretro". those with emulator != "libretro".
""" """
url = CONFIGGEN_DEFAULTS_URL.format(tag=self.tag)
try: try:
req = urllib.request.Request( req = urllib.request.Request(
CONFIGGEN_DEFAULTS_URL, url,
headers={"User-Agent": "retrobios-scraper/1.0"}, headers={"User-Agent": "retrobios-scraper/1.0"},
) )
with urllib.request.urlopen(req, timeout=30) as resp: with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read().decode("utf-8") raw = resp.read().decode("utf-8")
except urllib.error.URLError as e: except urllib.error.URLError as e:
raise ConnectionError( raise ConnectionError(
f"Failed to fetch {CONFIGGEN_DEFAULTS_URL}: {e}" f"Failed to fetch {url}: {e}"
) from e ) from e
data = yaml_load(raw) data = yaml_load(raw)
cores: set[str] = set() cores: set[str] = set()
@@ -372,25 +372,12 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req)) systems[req.system]["files"].append(requirement_entry(req))
batocera_version = ""
if _STABLE_TAG != "master":
batocera_version = _STABLE_TAG.removeprefix("batocera-")
if not batocera_version:
# Preserve existing version when fetch fails (offline mode)
existing = (
Path(__file__).resolve().parents[2] / "platforms" / "batocera.yml"
)
if existing.exists():
with open(existing) as f:
old = yaml_load(f) or {}
batocera_version = str(old.get("version", ""))
cores, standalone = self._fetch_cores() cores, standalone = self._fetch_cores()
result = { result = {
"platform": "Batocera", "platform": "Batocera",
"version": batocera_version or "", "version": self.tag.removeprefix("batocera-"),
"homepage": "https://batocera.org", "homepage": "https://batocera.org",
"source": SOURCE_URL, "source": self.url,
"base_destination": "bios", "base_destination": "bios",
"hash_type": "md5", "hash_type": "md5",
"verification_mode": "md5", "verification_mode": "md5",
+5 -8
View File
@@ -41,10 +41,8 @@ PLATFORM_NAME = "bizhawk"
GITHUB_REPO = "TASEmulators/BizHawk" GITHUB_REPO = "TASEmulators/BizHawk"
_STABLE_TAG = fetch_github_latest_version(GITHUB_REPO) or "master"
SOURCE_URL = ( SOURCE_URL = (
f"https://raw.githubusercontent.com/TASEmulators/BizHawk/{_STABLE_TAG}" "https://raw.githubusercontent.com/TASEmulators/BizHawk/{tag}"
"/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs" "/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs"
) )
@@ -332,7 +330,8 @@ class Scraper(BaseScraper):
"""BizHawk firmware database scraper.""" """BizHawk firmware database scraper."""
def __init__(self): def __init__(self):
super().__init__(url=SOURCE_URL) self.tag = fetch_github_latest_version(GITHUB_REPO)
super().__init__(url=SOURCE_URL.format(tag=self.tag))
def validate_format(self, raw_data: str) -> bool: def validate_format(self, raw_data: str) -> bool:
return "FirmwareDatabase" in raw_data and "FirmwareAndOption" in raw_data return "FirmwareDatabase" in raw_data and "FirmwareAndOption" in raw_data
@@ -372,13 +371,11 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req)) systems[req.system]["files"].append(requirement_entry(req))
version = _STABLE_TAG if _STABLE_TAG != "master" else ""
return { return {
"platform": "BizHawk", "platform": "BizHawk",
"version": version, "version": self.tag,
"homepage": "https://tasvideos.org/BizHawk", "homepage": "https://tasvideos.org/BizHawk",
"source": SOURCE_URL, "source": self.url,
"base_destination": "Firmware", "base_destination": "Firmware",
"hash_type": "sha1", "hash_type": "sha1",
"verification_mode": "sha1", "verification_mode": "sha1",
+1 -1
View File
@@ -295,7 +295,7 @@ class Scraper(BaseScraper):
def fetch_metadata(self) -> dict: def fetch_metadata(self) -> dict:
"""Fetch version info from GitHub.""" """Fetch version info from GitHub."""
version = fetch_github_latest_version("libretro/libretro-core-info") version = fetch_github_latest_version("libretro/libretro-core-info")
return {"version": version or ""} return {"version": version}
def main(): def main():
+11 -15
View File
@@ -22,6 +22,7 @@ try:
BaseScraper, BaseScraper,
BiosRequirement, BiosRequirement,
fetch_github_latest_version, fetch_github_latest_version,
github_headers,
requirement_entry, requirement_entry,
) )
except ImportError: except ImportError:
@@ -29,6 +30,7 @@ except ImportError:
BaseScraper, BaseScraper,
BiosRequirement, BiosRequirement,
fetch_github_latest_version, fetch_github_latest_version,
github_headers,
requirement_entry, requirement_entry,
) )
@@ -426,13 +428,7 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req)) systems[req.system]["files"].append(requirement_entry(req))
version = "" version = fetch_github_latest_version("dragoonDorise/EmuDeck")
try:
v = fetch_github_latest_version("dragoonDorise/EmuDeck")
if v:
version = v
except (ConnectionError, ValueError, OSError):
pass
cores = self._fetch_installed_emulators() cores = self._fetch_installed_emulators()
@@ -468,17 +464,15 @@ class Scraper(BaseScraper):
skip = {"retroarch_maincfg", "retroarch"} skip = {"retroarch_maincfg", "retroarch"}
try: try:
req = urllib.request.Request( req = urllib.request.Request(api_url, headers=github_headers())
api_url, with urllib.request.urlopen(req, timeout=30) as resp:
headers={"User-Agent": "retrobios-scraper/1.0"}, data = json.loads(resp.read())
) except (urllib.error.URLError, json.JSONDecodeError) as e:
data = json.loads(urllib.request.urlopen(req, timeout=30).read()) raise RuntimeError(f"cannot list EmuDeck EmuScripts: {e}") from e
except (urllib.error.URLError, OSError):
return []
cores: list[str] = [] cores: list[str] = []
seen: set[str] = set() seen: set[str] = set()
for entry in data: for entry in data if isinstance(data, list) else []:
name = entry.get("name", "") name = entry.get("name", "")
if not name.endswith(".sh"): if not name.endswith(".sh"):
continue continue
@@ -493,6 +487,8 @@ class Scraper(BaseScraper):
if core not in seen: if core not in seen:
seen.add(core) seen.add(core)
cores.append(core) cores.append(core)
if not cores:
raise RuntimeError(f"no emulator script in {api_url}")
return sorted(cores) return sorted(cores)
+27 -18
View File
@@ -10,6 +10,7 @@ from __future__ import annotations
import argparse import argparse
import json import json
import logging import logging
import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
@@ -101,24 +102,7 @@ def _extract_version() -> tuple[str, str]:
version = _version_from_resource_h() version = _version_from_resource_h()
# Last resort: use GitHub API for latest real release tag # Last resort: use GitHub API for latest real release tag
if version == "unknown": if version == "unknown":
try: version = _version_from_api()
import urllib.error
import urllib.request
req = urllib.request.Request(
"https://api.github.com/repos/finalburnneo/FBNeo/tags?per_page=10",
headers={"User-Agent": "retrobios-scraper/1.0"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
import json as json_mod
tags = json_mod.loads(resp.read())
for t in tags:
if t["name"] != "latest" and t["name"].startswith("v"):
version = t["name"]
break
except (urllib.error.URLError, OSError):
pass
sha_result = subprocess.run( sha_result = subprocess.run(
["git", "rev-parse", "HEAD"], ["git", "rev-parse", "HEAD"],
@@ -132,6 +116,31 @@ def _extract_version() -> tuple[str, str]:
return version, commit return version, commit
def _version_from_api() -> str:
"""The latest real release tag, or an error: "unknown" was written as
core_version and cached for a day."""
import json as json_mod
import urllib.error
import urllib.request
headers = {"User-Agent": "retrobios-scraper/1.0"}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
req = urllib.request.Request(
"https://api.github.com/repos/finalburnneo/FBNeo/tags?per_page=10", headers=headers
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
tags = json_mod.loads(resp.read())
except (urllib.error.URLError, OSError, ValueError) as exc:
raise RuntimeError(f"cannot determine the FBNeo version: {exc}") from exc
for tag in tags:
if tag["name"] != "latest" and tag["name"].startswith("v"):
return tag["name"]
raise RuntimeError("cannot determine the FBNeo version: no tag, resource.h or release")
def _version_from_resource_h() -> str: def _version_from_resource_h() -> str:
"""Fallback: parse VER_FULL_VERSION_STR from resource.h.""" """Fallback: parse VER_FULL_VERSION_STR from resource.h."""
resource_h = CLONE_DIR / "src" / "burner" / "resource.h" resource_h = CLONE_DIR / "src" / "burner" / "resource.h"
+52 -62
View File
@@ -15,6 +15,7 @@ from .base_scraper import (
BaseScraper, BaseScraper,
BiosRequirement, BiosRequirement,
fetch_github_latest_version, fetch_github_latest_version,
github_headers,
requirement_entry, requirement_entry,
) )
from .dat_parser import parse_dat, parse_dat_metadata, validate_dat_format from .dat_parser import parse_dat, parse_dat_metadata, validate_dat_format
@@ -100,6 +101,27 @@ SYSTEM_SLUG_MAP = {
} }
def _core_info_files() -> list[str]:
"""Paths of every .info file in libretro-core-info, or RuntimeError."""
import json
url = "https://api.github.com/repos/libretro/libretro-core-info/git/trees/master?recursive=1"
try:
req = urllib.request.Request(url, headers=github_headers())
with urllib.request.urlopen(req, timeout=30) as resp:
tree = json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError) as e:
raise RuntimeError(f"cannot list libretro-core-info: {e}") from e
info_files = [
item["path"]
for item in tree.get("tree", [])
if item["path"].endswith("_libretro.info")
]
if not info_files:
raise RuntimeError(f"no .info file in {url}")
return info_files
class Scraper(BaseScraper): class Scraper(BaseScraper):
"""Scraper for libretro System.dat.""" """Scraper for libretro System.dat."""
@@ -166,71 +188,39 @@ class Scraper(BaseScraper):
def _fetch_core_metadata(self) -> dict[str, dict]: def _fetch_core_metadata(self) -> dict[str, dict]:
"""Fetch per-core metadata from libretro-core-info .info files.""" """Fetch per-core metadata from libretro-core-info .info files."""
metadata = {} from .coreinfo_scraper import CORE_SYSTEM_MAP
try:
url = "https://api.github.com/repos/libretro/libretro-core-info/git/trees/master?recursive=1"
req = urllib.request.Request(
url,
headers={
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
},
)
with urllib.request.urlopen(req, timeout=30) as resp:
import json
tree = json.loads(resp.read()) metadata: dict[str, dict] = {}
for filename in _core_info_files():
core_name = filename.replace("_libretro.info", "")
system_slug = CORE_SYSTEM_MAP.get(core_name)
if not system_slug or system_slug in metadata:
continue
info_url = f"https://raw.githubusercontent.com/libretro/libretro-core-info/master/{filename}"
try:
req = urllib.request.Request(
info_url, headers={"User-Agent": "retrobios-scraper/1.0"}
)
with urllib.request.urlopen(req, timeout=10) as resp:
content = resp.read().decode("utf-8")
except urllib.error.URLError as e:
raise RuntimeError(f"cannot fetch {info_url}: {e}") from e
info_files = [ info = {}
item["path"] for line in content.split("\n"):
for item in tree.get("tree", []) key, sep, value = line.strip().partition(" = ")
if item["path"].endswith("_libretro.info") if sep:
] info[key.strip()] = value.strip().strip('"')
for filename in info_files: if int(info.get("firmware_count", "0")) == 0:
core_name = filename.replace("_libretro.info", "") continue
try: metadata[system_slug] = {
info_url = f"https://raw.githubusercontent.com/libretro/libretro-core-info/master/{filename}" "core": core_name,
req = urllib.request.Request( "manufacturer": info.get("manufacturer", ""),
info_url, headers={"User-Agent": "retrobios-scraper/1.0"} "display_name": info.get("display_name", "")
) or info.get("systemname", ""),
with urllib.request.urlopen(req, timeout=10) as resp: "docs": f"https://docs.libretro.com/library/{core_name}/",
content = resp.read().decode("utf-8") }
info = {}
for line in content.split("\n"):
line = line.strip()
if " = " in line:
key, _, value = line.partition(" = ")
info[key.strip()] = value.strip().strip('"')
fw_count = int(info.get("firmware_count", "0"))
if fw_count == 0:
continue
system_name = info.get("systemname", "")
manufacturer = info.get("manufacturer", "")
display_name = info.get("display_name", "")
info.get("categories", "")
# Map core to our system slug via firmware paths
from .coreinfo_scraper import CORE_SYSTEM_MAP
system_slug = CORE_SYSTEM_MAP.get(core_name)
if not system_slug:
continue
if system_slug not in metadata:
metadata[system_slug] = {
"core": core_name,
"manufacturer": manufacturer,
"display_name": display_name or system_name,
"docs": f"https://docs.libretro.com/library/{core_name}/",
}
except (urllib.error.URLError, urllib.error.HTTPError):
continue
except (ConnectionError, ValueError, OSError):
pass
return metadata return metadata
+21 -16
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
import argparse import argparse
import json import json
import os
import logging import logging
import shutil import shutil
import subprocess import subprocess
@@ -109,24 +110,28 @@ def _sparse_clone() -> None:
def _get_version() -> str: def _get_version() -> str:
# version.cpp is generated at build time, not in the repo. """The latest MAME release, from the GitHub API.
# Use GitHub API to get the latest release tag.
version.cpp is generated at build time, not in the repo. A failed lookup
raises: "unknown" was cached for a day and written as core_version.
"""
headers = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
url = "https://api.github.com/repos/mamedev/mame/releases/latest"
try: try:
req = urllib.request.Request( req = urllib.request.Request(url, headers=headers)
"https://api.github.com/repos/mamedev/mame/releases/latest",
headers={
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
},
)
with urllib.request.urlopen(req, timeout=10) as resp: with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read()) tag = json.loads(resp.read()).get("tag_name", "")
tag = data.get("tag_name", "") except (urllib.error.URLError, json.JSONDecodeError, OSError) as exc:
if tag: raise RuntimeError(f"cannot read the MAME release from {url}: {exc}") from exc
return _parse_version_tag(tag) if not tag:
except (urllib.error.URLError, json.JSONDecodeError, OSError): raise RuntimeError(f"no tag_name in {url}")
pass return _parse_version_tag(tag)
return "unknown"
def _parse_version_tag(tag: str) -> str: def _parse_version_tag(tag: str) -> str:
+12 -15
View File
@@ -21,7 +21,7 @@ from .base_scraper import BaseScraper, BiosRequirement, requirement_entry
PLATFORM_NAME = "recalbox" PLATFORM_NAME = "recalbox"
def _fetch_gitlab_stable_tag() -> str | None: def _fetch_gitlab_stable_tag() -> str:
"""Fetch the latest stable x.y.z tag from the Recalbox GitLab.""" """Fetch the latest stable x.y.z tag from the Recalbox GitLab."""
import json import json
import re import re
@@ -33,16 +33,16 @@ def _fetch_gitlab_stable_tag() -> str | None:
req = urllib.request.Request(url, headers={"User-Agent": "retrobios-scraper/1.0"}) req = urllib.request.Request(url, headers={"User-Agent": "retrobios-scraper/1.0"})
with urllib.request.urlopen(req, timeout=15) as resp: with urllib.request.urlopen(req, timeout=15) as resp:
tags = json.loads(resp.read()) tags = json.loads(resp.read())
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError): except (urllib.error.URLError, json.JSONDecodeError) as e:
return None raise RuntimeError(f"cannot list Recalbox tags: {e}") from e
stable = [t["name"] for t in tags if re.fullmatch(r"[0-9]+\.[0-9]+(\.[0-9]+)?", t["name"])] stable = [t["name"] for t in tags if re.fullmatch(r"[0-9]+\.[0-9]+(\.[0-9]+)?", t["name"])]
return stable[0] if stable else None if not stable:
raise RuntimeError("no stable Recalbox tag among the latest 50")
return stable[0]
_STABLE_TAG = _fetch_gitlab_stable_tag() or "master"
SOURCE_URL = ( SOURCE_URL = (
f"https://gitlab.com/recalbox/recalbox/-/raw/{_STABLE_TAG}/" "https://gitlab.com/recalbox/recalbox/-/raw/{tag}/"
"board/recalbox/fsoverlay/recalbox/share_init/system/" "board/recalbox/fsoverlay/recalbox/share_init/system/"
".emulationstation/es_bios.xml" ".emulationstation/es_bios.xml"
) )
@@ -109,8 +109,9 @@ def split_cores(names) -> tuple[list[str], list[str]]:
class Scraper(BaseScraper): class Scraper(BaseScraper):
"""Scraper for Recalbox es_bios.xml.""" """Scraper for Recalbox es_bios.xml."""
def __init__(self, url: str = SOURCE_URL): def __init__(self):
super().__init__(url=url) self.tag = _fetch_gitlab_stable_tag()
super().__init__(url=SOURCE_URL.format(tag=self.tag))
def _fetch_cores(self) -> tuple[list[str], list[str]]: def _fetch_cores(self) -> tuple[list[str], list[str]]:
"""Core names from es_bios.xml, and those Recalbox runs standalone. """Core names from es_bios.xml, and those Recalbox runs standalone.
@@ -217,16 +218,12 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req)) systems[req.system]["files"].append(requirement_entry(req))
version = _STABLE_TAG if _STABLE_TAG != "master" else ""
if not version:
version = "10.0"
cores, standalone = self._fetch_cores() cores, standalone = self._fetch_cores()
return { return {
"platform": "Recalbox", "platform": "Recalbox",
"version": version, "version": self.tag,
"homepage": "https://www.recalbox.com", "homepage": "https://www.recalbox.com",
"source": SOURCE_URL, "source": self.url,
"base_destination": "bios", "base_destination": "bios",
"hash_type": "md5", "hash_type": "md5",
"verification_mode": "md5", "verification_mode": "md5",
+1 -4
View File
@@ -160,10 +160,7 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req)) systems[req.system]["files"].append(requirement_entry(req))
version = "" version = fetch_github_latest_version(GITHUB_REPO)
tag = fetch_github_latest_version(GITHUB_REPO)
if tag:
version = tag
return { return {
"platform": "RetroBat", "platform": "RetroBat",
+1 -1
View File
@@ -431,7 +431,7 @@ class Scraper(BaseScraper):
from .base_scraper import fetch_github_latest_version from .base_scraper import fetch_github_latest_version
except ImportError: except ImportError:
from scraper.base_scraper import fetch_github_latest_version from scraper.base_scraper import fetch_github_latest_version
version = fetch_github_latest_version("RetroDECK/RetroDECK") or "" version = fetch_github_latest_version("RetroDECK/RetroDECK")
return { return {
"platform": "RetroDECK", "platform": "RetroDECK",
+1 -1
View File
@@ -181,7 +181,7 @@ class Scraper(BaseScraper):
return { return {
"platform": "ROCKNIX", "platform": "ROCKNIX",
"version": fetch_github_latest_version(GITHUB_REPO) or "", "version": fetch_github_latest_version(GITHUB_REPO),
"homepage": "https://rocknix.org", "homepage": "https://rocknix.org",
"source": SOURCE_URL, "source": SOURCE_URL,
"base_destination": "bios", "base_destination": "bios",
+6 -9
View File
@@ -44,10 +44,8 @@ PLATFORM_NAME = "romm"
GITHUB_REPO = "rommapp/romm" GITHUB_REPO = "rommapp/romm"
_STABLE_TAG = fetch_github_latest_version(GITHUB_REPO) or "master"
SOURCE_URL = ( SOURCE_URL = (
f"https://raw.githubusercontent.com/rommapp/romm/{_STABLE_TAG}" "https://raw.githubusercontent.com/rommapp/romm/{tag}"
"/backend/models/fixtures/known_bios_files.json" "/backend/models/fixtures/known_bios_files.json"
) )
@@ -124,8 +122,9 @@ FIRMWARE_MIRRORS: dict[str, tuple[str, ...]] = {
class Scraper(BaseScraper): class Scraper(BaseScraper):
"""Scraper for RomM known_bios_files.json.""" """Scraper for RomM known_bios_files.json."""
def __init__(self, url: str = SOURCE_URL): def __init__(self):
super().__init__(url=url) self.tag = fetch_github_latest_version(GITHUB_REPO)
super().__init__(url=SOURCE_URL.format(tag=self.tag))
self._parsed: dict | None = None self._parsed: dict | None = None
def _parse_json(self) -> dict: def _parse_json(self) -> dict:
@@ -212,14 +211,12 @@ class Scraper(BaseScraper):
systems[req.system]["files"].append(requirement_entry(req)) systems[req.system]["files"].append(requirement_entry(req))
version = _STABLE_TAG if _STABLE_TAG != "master" else ""
return { return {
"inherits": "emulatorjs", "inherits": "emulatorjs",
"platform": "RomM", "platform": "RomM",
"version": version, "version": self.tag,
"homepage": "https://romm.app", "homepage": "https://romm.app",
"source": SOURCE_URL, "source": self.url,
"base_destination": "bios", "base_destination": "bios",
"hash_type": "sha1", "hash_type": "sha1",
"verification_mode": "md5", "verification_mode": "md5",
@@ -27,25 +27,16 @@ PLATFORM_NAME = "batocera"
GITHUB_API = "https://api.github.com/repos/batocera-linux/batocera.linux/contents" GITHUB_API = "https://api.github.com/repos/batocera-linux/batocera.linux/contents"
try: try:
from ..base_scraper import github_headers
from ..batocera_scraper import fetch_stable_tag from ..batocera_scraper import fetch_stable_tag
except ImportError: except ImportError:
from scraper.base_scraper import github_headers
from scraper.batocera_scraper import fetch_stable_tag from scraper.batocera_scraper import fetch_stable_tag
_STABLE_TAG = fetch_stable_tag() or "master" RAW_BASE = "https://raw.githubusercontent.com/batocera-linux/batocera.linux/{tag}"
RAW_BASE = f"https://raw.githubusercontent.com/batocera-linux/batocera.linux/{_STABLE_TAG}"
CONFIG_IN_URL = f"{RAW_BASE}/package/batocera/core/batocera-system/Config.in" CONFIG_IN_PATH = "package/batocera/core/batocera-system/Config.in"
ES_SYSTEMS_URL = ( ES_SYSTEMS_PATH = "package/batocera/emulationstation/batocera-es-system/es_systems.yml"
f"{RAW_BASE}/package/batocera/emulationstation/batocera-es-system/es_systems.yml"
)
_HEADERS = {
"User-Agent": "retrobios-scraper/1.0",
"Accept": "application/vnd.github.v3+json",
}
import os as _os
if _os.environ.get("GITHUB_TOKEN"):
_HEADERS["Authorization"] = f"Bearer {_os.environ['GITHUB_TOKEN']}"
_TARGET_FLAG_RE = re.compile(r"^(BR2_PACKAGE_BATOCERA_TARGET_\w+)=y", re.MULTILINE) _TARGET_FLAG_RE = re.compile(r"^(BR2_PACKAGE_BATOCERA_TARGET_\w+)=y", re.MULTILINE)
@@ -68,26 +59,22 @@ _META_BLOCK_RE = re.compile(
) )
def _fetch(url: str, headers: dict | None = None) -> str | None: def _fetch(url: str, headers: dict | None = None) -> str:
h = headers or {"User-Agent": "retrobios-scraper/1.0"} h = headers or {"User-Agent": "retrobios-scraper/1.0"}
try: try:
req = urllib.request.Request(url, headers=h) req = urllib.request.Request(url, headers=h)
with urllib.request.urlopen(req, timeout=30) as resp: with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8") return resp.read().decode("utf-8")
except urllib.error.URLError as e: except urllib.error.URLError as e:
print(f" skip {url}: {e}", file=sys.stderr) raise RuntimeError(f"cannot fetch {url}: {e}") from e
return None
def _fetch_json(url: str) -> list | dict | None: def _fetch_json(url: str) -> list | dict:
text = _fetch(url, headers=_HEADERS) text = _fetch(url, headers=github_headers())
if text is None:
return None
try: try:
return json.loads(text) return json.loads(text)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f" json parse error {url}: {e}", file=sys.stderr) raise RuntimeError(f"cannot parse {url}: {e}") from e
return None
def _normalise_condition(raw: str) -> str: def _normalise_condition(raw: str) -> str:
@@ -272,40 +259,41 @@ class Scraper(BaseTargetScraper):
def __init__(self, url: str = "https://github.com/batocera-linux/batocera.linux"): def __init__(self, url: str = "https://github.com/batocera-linux/batocera.linux"):
super().__init__(url=url) super().__init__(url=url)
self.tag = ""
def _raw(self, path: str) -> str:
return f"{RAW_BASE.format(tag=self.tag)}/{path}"
def _list_boards(self) -> list[str]: def _list_boards(self) -> list[str]:
"""List batocera-*.board files from configs/ via GitHub API.""" """List batocera-*.board files from configs/ via GitHub API."""
data = _fetch_json(f"{GITHUB_API}/configs") data = _fetch_json(f"{GITHUB_API}/configs?ref={self.tag}")
if not data or not isinstance(data, list): boards = [
return []
return [
item["name"] item["name"]
for item in data for item in data
if isinstance(item, dict) if isinstance(item, dict)
and item.get("name", "").startswith("batocera-") and item.get("name", "").startswith("batocera-")
and item.get("name", "").endswith(".board") and item.get("name", "").endswith(".board")
] ] if isinstance(data, list) else []
if not boards:
raise RuntimeError(f"no batocera-*.board file in configs/ at {self.tag}")
return boards
def _fetch_board_flag(self, board_name: str) -> str | None: def _fetch_board_flag(self, board_name: str) -> str | None:
"""Fetch a board file and extract its BR2_PACKAGE_BATOCERA_TARGET_* flag.""" """Fetch a board file and extract its BR2_PACKAGE_BATOCERA_TARGET_* flag."""
url = f"{RAW_BASE}/configs/{board_name}" m = _TARGET_FLAG_RE.search(_fetch(self._raw(f"configs/{board_name}")))
text = _fetch(url)
if text is None:
return None
m = _TARGET_FLAG_RE.search(text)
return m.group(1) if m else None return m.group(1) if m else None
def fetch_targets(self) -> dict: def fetch_targets(self) -> dict:
"""Build per-board emulator availability map.""" """Build per-board emulator availability map."""
self.tag = fetch_stable_tag()
print(" fetching board list...", file=sys.stderr) print(" fetching board list...", file=sys.stderr)
boards = self._list_boards() boards = self._list_boards()
if not boards:
print(" warning: no boards found", file=sys.stderr)
print(" fetching Config.in...", file=sys.stderr) print(" fetching Config.in...", file=sys.stderr)
config_in_text = _fetch(CONFIG_IN_URL) config_in_url = self._raw(CONFIG_IN_PATH)
config_in_text = _fetch(config_in_url)
if not config_in_text: if not config_in_text:
raise RuntimeError(f"empty Config.in from {CONFIG_IN_URL}") raise RuntimeError(f"empty Config.in from {config_in_url}")
meta_rules = _parse_meta_flags(config_in_text) meta_rules = _parse_meta_flags(config_in_text)
selects = _parse_selects(config_in_text) selects = _parse_selects(config_in_text)
@@ -314,19 +302,20 @@ class Scraper(BaseTargetScraper):
file=sys.stderr, file=sys.stderr,
) )
if not selects: if not selects:
raise RuntimeError(f"no 'select BR2_PACKAGE_*' lines parsed from {CONFIG_IN_URL}") raise RuntimeError(f"no 'select BR2_PACKAGE_*' lines parsed from {config_in_url}")
print(" fetching es_systems.yml...", file=sys.stderr) print(" fetching es_systems.yml...", file=sys.stderr)
es_text = _fetch(ES_SYSTEMS_URL) es_systems_url = self._raw(ES_SYSTEMS_PATH)
es_text = _fetch(es_systems_url)
if not es_text: if not es_text:
raise RuntimeError(f"empty es_systems.yml from {ES_SYSTEMS_URL}") raise RuntimeError(f"empty es_systems.yml from {es_systems_url}")
package_to_emulators = _parse_es_systems(es_text) package_to_emulators = _parse_es_systems(es_text)
print( print(
f" parsed {len(package_to_emulators)} package->emulator mappings", f" parsed {len(package_to_emulators)} package->emulator mappings",
file=sys.stderr, file=sys.stderr,
) )
if not package_to_emulators: if not package_to_emulators:
raise RuntimeError(f"no package->emulator mappings parsed from {ES_SYSTEMS_URL}") raise RuntimeError(f"no package->emulator mappings parsed from {es_systems_url}")
targets: dict[str, dict] = {} targets: dict[str, dict] = {}
for board_name in sorted(boards): for board_name in sorted(boards):
+12 -5
View File
@@ -21,6 +21,8 @@ import sys
import zipfile import zipfile
from pathlib import Path from pathlib import Path
from artifacts import ArtifactLockBusy, artifact_lock
# "Each file included in a release must be under 2 GiB." # "Each file included in a release must be under 2 GiB."
ASSET_LIMIT = 2 * 1024**3 ASSET_LIMIT = 2 * 1024**3
@@ -222,12 +224,17 @@ def main() -> int:
help="a part stays under this many bytes (K, M, G suffixes; default 2G)", help="a part stays under this many bytes (K, M, G suffixes; default 2G)",
) )
args = parser.parse_args() args = parser.parse_args()
# The packs are rewritten in place by generate_pack under this lock: a
# build running beside the split mixes its bytes into the pack being
# read, or writes a pack that the split then deletes.
directory = args.target if args.target.is_dir() else args.target.parent
try: try:
if args.target.is_dir(): with artifact_lock(str(directory)):
published = split_directory(args.target, args.max_size) if args.target.is_dir():
else: published = split_directory(args.target, args.max_size)
published = split_pack(args.target, args.max_size) else:
except (OSError, ValueError, zipfile.BadZipFile) as exc: published = split_pack(args.target, args.max_size)
except (ArtifactLockBusy, OSError, ValueError, zipfile.BadZipFile) as exc:
print(f"Error: {exc}", file=sys.stderr) print(f"Error: {exc}", file=sys.stderr)
return 1 return 1
for path in published: for path in published:
+38 -5
View File
@@ -9,7 +9,7 @@ from __future__ import annotations
import sys import sys
from common import _norm_system_id, resolve_platform_cores, runs_standalone from common import _norm_system_id, resolve_platform_cores, runs_standalone
from validation import filter_files_by_mode from validation import filter_files_by_mode, read_from_system_dir
def _serialize_source_ref(sr: object) -> str: def _serialize_source_ref(sr: object) -> str:
@@ -165,12 +165,36 @@ def _same_file(files: list[dict], file_entry: dict, emu_name: str) -> dict | Non
same_name = [f for f in files if f["name"].lower() == name_lower] same_name = [f for f in files if f["name"].lower() == name_lower]
return ( return (
next((f for f in same_name if path and other_path(f) == path), None) 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) # Another core's file of the same name is the same file unless their
# contents disagree: beebem's 16 KB BBC BIOS.rom and np2kai's PC-98
# bios.rom merged, and the export wrote the BBC md5 over Recalbox's.
or next(
(f for f in same_name
if emu_name not in f.get("_cores", ()) and not _contents_disagree(f, file_entry)),
None,
)
# Revisions accepted under one name and no path fill one slot. # 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) or next((f for f in same_name if not path or not other_path(f)), None)
) )
def _declared(entry: dict, field: str) -> set[str]:
"""The values an entry declares for a hash field, one or a list."""
value = entry.get(field)
values = value if isinstance(value, list) else [value]
return {str(v).lower() for v in values if v}
def _contents_disagree(a: dict, b: dict) -> bool:
"""A hash both declare without a value in common, or two declared sizes."""
for field in ("sha1", "md5", "sha256", "crc32"):
ours, theirs = _declared(a, field), _declared(b, field)
if ours and theirs and not ours & theirs:
return True
size_a, size_b = a.get("size"), b.get("size")
return isinstance(size_a, int) and isinstance(size_b, int) and size_a != size_b
def _fold_into(existing: dict, file_entry: dict, emu_name: str) -> 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.""" """Add one core's declaration to the entry that already stands for it."""
existing["_cores"] = existing.get("_cores", set()) | {emu_name} existing["_cores"] = existing.get("_cores", set()) | {emu_name}
@@ -276,6 +300,16 @@ def _has_exploitable_data(entry: dict) -> bool:
) )
def _system_dir_files(profile: dict, standalone: bool) -> list[dict]:
"""Entries of the build the platform runs, read from the system directory."""
return list(
filter(
read_from_system_dir,
filter_files_by_mode(profile.get("files", []), standalone=standalone),
)
)
def generate_platform_truth( def generate_platform_truth(
platform_name: str, platform_name: str,
config: dict, config: dict,
@@ -354,9 +388,8 @@ def generate_platform_truth(
continue continue
cores_profiled.add(emu_name) cores_profiled.add(emu_name)
filtered = filter_files_by_mode( filtered = _system_dir_files(
profile.get("files", []), profile, runs_standalone(emu_name, profile, standalone_set)
standalone=runs_standalone(emu_name, profile, standalone_set),
) )
for fe in filtered: for fe in filtered:
+23 -1
View File
@@ -254,6 +254,28 @@ def _semantic_install_checks(path: Path, manifest: dict) -> list[str]:
return out return out
def unchecked_formats() -> list[str]:
"""Formats the schemas declare that this jsonschema install cannot check.
date-time and uri need the format extras; without them the ten format
constraints of the published schemas were accepted unread.
"""
used = {
match
for path in SCHEMAS.glob("*.json")
for match in re.findall(r'"format"\s*:\s*"([^"]+)"', path.read_text(encoding="utf-8"))
}
return sorted(used - set(FormatChecker().checkers))
def _format_errors() -> list[str]:
"""jsonschema skips a format it has no checker for: say so, and fail."""
return [
f"no checker for format {name}: install \"jsonschema[format-nongpl]\""
for name in unchecked_formats()
]
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument( parser.add_argument(
@@ -263,7 +285,7 @@ def main() -> int:
) )
args = parser.parse_args() args = parser.parse_args()
errors: list[str] = [] errors: list[str] = _format_errors()
errors.extend( errors.extend(
_validate_yaml_directory(ROOT / "emulators", "emulator.schema.json") _validate_yaml_directory(ROOT / "emulators", "emulator.schema.json")
) )
+114
View File
@@ -313,6 +313,39 @@ def validate_cli_modes(args, mode_attrs: list[str]) -> None:
raise SystemExit(f"Options are mutually exclusive: --{' --'.join(mode_attrs)}") raise SystemExit(f"Options are mutually exclusive: --{' --'.join(mode_attrs)}")
def read_from_system_dir(file_entry: dict) -> bool:
"""Whether the emulator reads this entry from the system directory.
A file loaded from the save, content or working directory is not a BIOS:
no platform lists it, no pack carries it, and the truth model must not
ask a platform to declare it.
"""
load_from = file_entry.get("load_from", "")
return not load_from or load_from == "system_dir"
def outside_gap_scope(file_entry: dict, is_standalone: bool) -> bool:
"""Whether an entry is a file the platform's pack would carry for this build.
Read by the gap report and by verify's undeclared-file pass, so both set
aside the same entries: a placeholder standing for a family of files, a
file the user imports through the UI (``path: null``), an entry of the
build the platform does not run, a file read outside the system
directory, and a filename-agnostic entry the builder scans for itself.
"""
name = file_entry.get("name", "")
if "<" in name or ">" in name or "*" in name:
return True
if "path" in file_entry and file_entry["path"] is None:
return True
mode = file_entry.get("mode")
if (mode == "standalone" and not is_standalone) or (
mode == "libretro" and is_standalone
):
return True
return not read_from_system_dir(file_entry) or bool(file_entry.get("agnostic"))
def filter_files_by_mode(files: list[dict], standalone: bool) -> list[dict]: def filter_files_by_mode(files: list[dict], standalone: bool) -> list[dict]:
"""Filter file entries by libretro/standalone mode.""" """Filter file entries by libretro/standalone mode."""
result = [] result = []
@@ -490,3 +523,84 @@ def settle_mismatch(
): ):
return "frontend_digest_exact" return "frontend_digest_exact"
return status return status
def destination_owners(profiles: dict) -> dict[str, list[tuple[str, str]]]:
"""For each file name, the path tail each emulator reads it under."""
owners: dict[str, list[tuple[str, str]]] = {}
for emu_name, profile in profiles.items():
for f in profile.get("files", []):
name = str(f.get("name") or "")
if not name:
continue
for declared in {f.get("path"), f.get("standalone_path")} - {None, ""} or {name}:
owners.setdefault(name.lower(), []).append(
(str(declared).replace("\\", "/").lower(), emu_name)
)
return owners
def _owners_of(destination: str, name: str, owners: dict) -> set[str]:
"""Emulators whose declared path is the longest tail of the destination."""
destination = destination.replace("\\", "/").lower()
matching = [
(tail, emu) for tail, emu in owners.get(name.lower(), [])
if destination == tail or destination.endswith("/" + tail)
]
if not matching:
return set()
longest = max(len(tail) for tail, _emu in matching)
return {emu for tail, emu in matching if len(tail) == longest}
def validated_choice(
file_entry: dict,
local_path: str | None,
db: dict,
validation_index: dict,
bios_dir: str,
platform_digest: str | None,
destination: str = "",
owners: dict | None = None,
) -> tuple[str | None, str | None]:
"""The file a platform destination ships, and the disagreement if any.
A frontend's own check can pass while an emulator it runs rejects the
file; a held variant both accept replaces it. The pack and the install
manifest both read this, so they name the same file. A destination
another emulator declares more precisely is that emulator's: ZEsarUX's
48 KB cpc6128.rom check has no say over ep128emu/roms/cpc6128.rom.
"""
if not local_path or not validation_index:
return local_path, None
name = file_entry.get("name", "")
rules = validation_index.get(name)
if rules and owners and destination:
owning = _owners_of(destination, name, owners)
if owning and not owning & set(rules["emulators"]):
return local_path, None
check = check_file_validation(
local_path, file_entry.get("name", ""), validation_index, bios_dir
)
if not check:
return local_path, None
better = find_validated_variant(
file_entry, db, local_path, validation_index, bios_dir,
platform_digest=platform_digest,
)
if better:
return better, None
reason, emulators = check
return local_path, f"{', '.join(emulators)} says {reason}"
def existence_discrepancy(file_entry: dict, local_path: str, platform_display: str) -> str:
"""What an existence platform is told when its declared hash contradicts
the file it loads: the file is kept, the gap is said out loud. The pack
and verify both print this one line."""
declared = file_entry.get("md5", "") or file_entry.get("sha1", "")
actual = compute_hashes(local_path)
return (
f"packed per {platform_display} existence check; declared hash {declared}, "
f"file md5 {actual['md5']} sha1 {actual['sha1']}"
)
+84 -66
View File
@@ -69,6 +69,9 @@ from nativemode import (
reads_file_contents, reads_file_contents,
) )
from validation import ( from validation import (
existence_discrepancy,
destination_owners,
validated_choice,
agnostic_substitute, agnostic_substitute,
_build_validation_index, _build_validation_index,
_parse_validation, _parse_validation,
@@ -76,10 +79,14 @@ from validation import (
check_file_validation, check_file_validation,
filter_files_by_mode, filter_files_by_mode,
find_validated_variant, find_validated_variant,
outside_gap_scope,
) )
DEFAULT_DB = "database.json" DEFAULT_DB = "database.json"
DEFAULT_PLATFORMS_DIR = "platforms" DEFAULT_PLATFORMS_DIR = "platforms"
# The repository's platforms wherever the script runs from, for library calls
# that pass no directory.
_REPO_PLATFORMS = os.path.join(os.path.dirname(__file__), "..", "platforms")
DEFAULT_EMULATORS_DIR = "emulators" DEFAULT_EMULATORS_DIR = "emulators"
@@ -117,6 +124,10 @@ def verify_entry_existence(
local_path: str | None, local_path: str | None,
validation_index: dict[str, dict] | None = None, validation_index: dict[str, dict] | None = None,
db: dict | None = None, db: dict | None = None,
destination: str = "",
owners: dict | None = None,
resolve_status: str = "",
platform_display: str = "",
) -> dict: ) -> dict:
"""RetroArch verification: path_is_valid() -file exists = OK.""" """RetroArch verification: path_is_valid() -file exists = OK."""
name = file_entry.get("name", "") name = file_entry.get("name", "")
@@ -124,22 +135,27 @@ def verify_entry_existence(
if not local_path: if not local_path:
return {"name": name, "status": Status.MISSING, "required": required} return {"name": name, "status": Status.MISSING, "required": required}
result = {"name": name, "status": Status.OK, "required": required} result = {"name": name, "status": Status.OK, "required": required}
if validation_index: if resolve_status == "hash_mismatch":
# The frontend reads no bytes, so the file stays; the declared hash
# it contradicts is reported, as the pack does.
result["discrepancy"] = existence_discrepancy(
file_entry, local_path, platform_display
)
return result
if not validation_index:
return result
if db:
# The pack ships the variant this returns, so a disagreement it
# settles is not reported.
_chosen, disagreement = validated_choice(
file_entry, local_path, db, validation_index, "bios", None,
destination, owners,
)
else:
check = check_file_validation(local_path, name, validation_index) check = check_file_validation(local_path, name, validation_index)
if check: disagreement = f"{', '.join(check[1])} says {check[0]}" if check else None
reason, emus_list = check if disagreement:
suppressed = False result["discrepancy"] = f"file present (OK) but {disagreement}"
if db:
better = find_validated_variant(
file_entry, db, local_path, validation_index,
)
if better:
suppressed = True
if not suppressed:
emus = ", ".join(emus_list)
result["discrepancy"] = (
f"file present (OK) but {emus} says {reason}"
)
return result return result
@@ -378,23 +394,7 @@ def _candidate_verdict(
""" """
if file_entry.get("unsourceable"): if file_entry.get("unsourceable"):
return "settled" return "settled"
# Placeholders stand for a family of files, not a file. if outside_gap_scope(file_entry, is_standalone):
if "<" in fname or ">" in fname or "*" in fname:
return "skip"
# An explicit null path means the user imports it through the UI.
if "path" in file_entry and file_entry["path"] is None:
return "skip"
file_mode = file_entry.get("mode")
if file_mode == "standalone" and not is_standalone:
return "skip"
if file_mode == "libretro" and is_standalone:
return "skip"
# Read from somewhere other than the system directory: not a BIOS gap.
load_from = file_entry.get("load_from", "")
if load_from and load_from != "system_dir":
return "skip"
# Filename-agnostic entries are answered by the builder's own scan.
if file_entry.get("agnostic"):
return "skip" return "skip"
if not include_all: if not include_all:
archive = file_entry.get("archive") archive = file_entry.get("archive")
@@ -802,6 +802,7 @@ def verify_platform(
if f.get("hle_fallback") if f.get("hle_fallback")
} }
validation_index = _build_validation_index(platform_profiles) validation_index = _build_validation_index(platform_profiles)
validation_owners = destination_owners(platform_profiles)
# Filter systems by target # Filter systems by target
if not target_cores: if not target_cores:
@@ -857,12 +858,19 @@ def verify_platform(
file_entry, sys_id, db, zip_contents, data_dir_registry, file_entry, sys_id, db, zip_contents, data_dir_registry,
slot_overrides, mode, platform_profiles, slot_overrides, mode, platform_profiles,
) )
destination = sanitize_pack_path(
file_entry.get("destination", file_entry.get("name", ""))
)
if not reads_file_contents(mode): if not reads_file_contents(mode):
result = verify_entry_existence( result = verify_entry_existence(
file_entry, file_entry,
local_path, local_path,
validation_index, validation_index,
db, db,
destination,
validation_owners,
resolve_status,
config.get("platform", ""),
) )
elif digest_algorithm(mode) == "sha1": elif digest_algorithm(mode) == "sha1":
result = verify_entry_sha1(file_entry, local_path) result = verify_entry_sha1(file_entry, local_path)
@@ -871,25 +879,13 @@ def verify_platform(
# Emulator-level validation: informational for platform packs. # Emulator-level validation: informational for platform packs.
# Platform verification (MD5) is the authority. Emulator # Platform verification (MD5) is the authority. Emulator
# mismatches are reported as discrepancies, not failures. # mismatches are reported as discrepancies, not failures.
if result["status"] == Status.OK and local_path and validation_index: if result["status"] == Status.OK:
fname = file_entry.get("name", "") _chosen, disagreement = validated_choice(
check = check_file_validation( file_entry, local_path, db, validation_index, "bios",
local_path, fname, validation_index, digest_algorithm(mode), destination, validation_owners,
) )
if check: if disagreement:
reason, emus_list = check result["discrepancy"] = f"{platform} says OK but {disagreement}"
better = find_validated_variant(
file_entry,
db,
local_path,
validation_index,
platform_digest=digest_algorithm(mode),
)
if not better:
emus = ", ".join(emus_list)
result["discrepancy"] = (
f"{platform} says OK but {emus} says {reason}"
)
result["system"] = sys_id result["system"] = sys_id
result["hle_fallback"] = hle_index.get(file_entry.get("name", ""), False) result["hle_fallback"] = hle_index.get(file_entry.get("name", ""), False)
result["ground_truth"] = build_ground_truth( result["ground_truth"] = build_ground_truth(
@@ -1126,8 +1122,11 @@ def _print_undeclared_section(result: dict, verbose: bool) -> None:
if not undeclared: if not undeclared:
return return
bios_files = [u for u in undeclared if u.get("category", "bios") == "bios"] # Everything that is not game data is firmware the core loads, archives
# included: a bios_zip sat in neither list, so a required one that was
# missing was never printed.
game_data = [u for u in undeclared if u.get("category", "bios") == "game_data"] game_data = [u for u in undeclared if u.get("category", "bios") == "game_data"]
bios_files = [u for u in undeclared if u.get("category", "bios") != "game_data"]
req_not_in_repo = [ req_not_in_repo = [
u u
@@ -1259,12 +1258,15 @@ def verify_emulator(
db: dict, db: dict,
standalone: bool = False, standalone: bool = False,
regions: list[str] | None = None, regions: list[str] | None = None,
platforms_dir: str = _REPO_PLATFORMS,
) -> dict: ) -> dict:
"""Verify files for specific emulator profiles. """Verify files for specific emulator profiles.
A region priority list narrows the report the same way a pack built with the A region priority list narrows the report the same way a pack built with the
same list would be narrowed, through the emulator pack's own drop set. same list would be narrowed, through the emulator pack's own drop set.
""" """
from packpaths import _resolve_destination
load_emulator_profiles(emulators_dir) load_emulator_profiles(emulators_dir)
zip_contents = build_zip_contents_index(db) zip_contents = build_zip_contents_index(db)
@@ -1276,9 +1278,7 @@ def verify_emulator(
# Build validation index from selected profiles only # Build validation index from selected profiles only
selected_profiles = {n: p for n, p in selected} selected_profiles = {n: p for n, p in selected}
validation_index = _build_validation_index(selected_profiles) validation_index = _build_validation_index(selected_profiles)
data_registry = load_data_dir_registry( data_registry = load_data_dir_registry(platforms_dir)
os.path.join(os.path.dirname(__file__), "..", "platforms")
)
details = [] details = []
file_status: dict[str, str] = {} file_status: dict[str, str] = {}
@@ -1300,8 +1300,6 @@ def verify_emulator(
standalone, standalone,
) )
if region_drops: if region_drops:
from packpaths import _resolve_destination
structure = profile.get("pack_structure") structure = profile.get("pack_structure")
files = [ files = [
fe fe
@@ -1489,7 +1487,11 @@ def verify_emulator(
result["system"] = file_entry.get("system", "") result["system"] = file_entry.get("system", "")
result["hle_fallback"] = hle result["hle_fallback"] = hle
result["ground_truth"] = build_ground_truth(name, validation_index) result["ground_truth"] = build_ground_truth(name, validation_index)
dest = file_entry.get("path", "") or name # The slot the emulator pack places this entry in: standalone_path
# under --standalone, the pack_structure prefix, the sanitised path.
dest = _resolve_destination(
file_entry, profile.get("pack_structure"), standalone
) or name
emu_results.setdefault(dest, []).append((result, required, hle)) emu_results.setdefault(dest, []).append((result, required, hle))
for dest, alternatives in emu_results.items(): for dest, alternatives in emu_results.items():
@@ -1556,6 +1558,7 @@ def verify_system(
db: dict, db: dict,
standalone: bool = False, standalone: bool = False,
regions: list[str] | None = None, regions: list[str] | None = None,
platforms_dir: str = _REPO_PLATFORMS,
) -> dict: ) -> dict:
"""Verify files for all emulators supporting given system IDs.""" """Verify files for all emulators supporting given system IDs."""
profiles = load_emulator_profiles(emulators_dir) profiles = load_emulator_profiles(emulators_dir)
@@ -1590,7 +1593,9 @@ def verify_system(
) )
sys.exit(1) sys.exit(1)
return verify_emulator(matching, emulators_dir, db, standalone, regions=regions) return verify_emulator(
matching, emulators_dir, db, standalone, regions=regions, platforms_dir=platforms_dir
)
def print_emulator_result(result: dict, verbose: bool = False) -> None: def print_emulator_result(result: dict, verbose: bool = False) -> None:
@@ -1718,11 +1723,29 @@ def _refuse_listing_narrowings(
("--system", args.system), ("--system", args.system),
("--region", getattr(args, "region", None)), ("--region", getattr(args, "region", None)),
("--target", getattr(args, "target", None)), ("--target", getattr(args, "target", None)),
("--standalone", args.standalone),
("--include-archived", args.include_archived),
("--json", args.json),
): ):
if on: if on:
parser.error(f"{flag} is incompatible with {listing}") parser.error(f"{flag} is incompatible with {listing}")
def _refuse_mode_flags(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
"""A flag the chosen mode does not read is refused, never ignored."""
for refused, message in (
(args.standalone and not (args.emulator or args.system),
"--standalone requires --emulator or --system"),
(args.include_archived and not args.all, "--include-archived requires --all"),
(args.target and not (args.platform or args.all),
"--target requires --platform or --all"),
(args.target and (args.emulator or args.system),
"--target is incompatible with --emulator and --system"),
):
if refused:
parser.error(message)
def _run_listing(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None: def _run_listing(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
if args.list_emulators: if args.list_emulators:
list_emulator_profiles(args.emulators_dir) list_emulator_profiles(args.emulators_dir)
@@ -1805,12 +1828,7 @@ def main():
parser.error( parser.error(
"--platform, --all, --emulator, and --system are mutually exclusive" "--platform, --all, --emulator, and --system are mutually exclusive"
) )
if args.standalone and not (args.emulator or args.system): _refuse_mode_flags(args, parser)
parser.error("--standalone requires --emulator or --system")
if args.target and not (args.platform or args.all):
parser.error("--target requires --platform or --all")
if args.target and (args.emulator or args.system):
parser.error("--target is incompatible with --emulator and --system")
with open(args.db) as f: with open(args.db) as f:
db = json.load(f) db = json.load(f)
@@ -1820,7 +1838,7 @@ def main():
names = [n.strip() for n in args.emulator.split(",") if n.strip()] names = [n.strip() for n in args.emulator.split(",") if n.strip()]
result = verify_emulator( result = verify_emulator(
names, args.emulators_dir, db, args.standalone, names, args.emulators_dir, db, args.standalone,
regions=requested_regions, regions=requested_regions, platforms_dir=args.platforms_dir,
) )
if args.json: if args.json:
result["details"] = [ result["details"] = [
@@ -1836,7 +1854,7 @@ def main():
system_ids = [s.strip() for s in args.system.split(",") if s.strip()] system_ids = [s.strip() for s in args.system.split(",") if s.strip()]
result = verify_system( result = verify_system(
system_ids, args.emulators_dir, db, args.standalone, system_ids, args.emulators_dir, db, args.standalone,
regions=requested_regions, regions=requested_regions, platforms_dir=args.platforms_dir,
) )
if args.json: if args.json:
result["details"] = [ result["details"] = [
+38 -1
View File
@@ -17,7 +17,7 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT / "scripts"))
from common import ArtifactLockBusy, artifact_lock # noqa: E402 from common import ArtifactLockBusy, artifact_lock, hold_artifact_lock # noqa: E402
class ArtifactLockTest(unittest.TestCase): class ArtifactLockTest(unittest.TestCase):
@@ -156,5 +156,42 @@ class PackLockCliTest(unittest.TestCase):
self.assertIn("is in use by another run", proc.stdout) self.assertIn("is in use by another run", proc.stdout)
class HeldLockTest(unittest.TestCase):
"""pipeline.py took the lock step by step: between the pack build and the
integrity check dist/ was free, another run's purge emptied it, and the
check reported every platform SKIP and passed."""
def setUp(self):
self.dir = tempfile.mkdtemp()
def _child_locks(self, env: dict) -> subprocess.CompletedProcess:
code = (
"import sys; sys.path.insert(0, 'scripts')\n"
"from common import artifact_lock\n"
f"with artifact_lock({self.dir!r}):\n pass\n"
)
return subprocess.run(
[sys.executable, "-c", code], cwd=str(REPO_ROOT), env=env,
capture_output=True, text=True, timeout=60, check=False,
)
def test_a_spawned_step_inherits_the_hold(self):
with hold_artifact_lock(self.dir):
self.assertEqual(self._child_locks(dict(os.environ)).returncode, 0)
def test_any_other_run_is_refused_for_the_whole_hold(self):
with hold_artifact_lock(self.dir):
env = {k: v for k, v in os.environ.items() if not k.startswith("RETROBIOS_")}
proc = self._child_locks(env)
self.assertNotEqual(proc.returncode, 0)
self.assertIn("ArtifactLockBusy", proc.stderr)
self.assertNotIn("RETROBIOS_HELD_ARTIFACT_DIR", os.environ)
self.assertEqual(self._child_locks(dict(os.environ)).returncode, 0)
def test_the_pipeline_holds_its_output_for_the_run(self):
source = (REPO_ROOT / "scripts" / "pipeline.py").read_text(encoding="utf-8")
self.assertIn("hold_artifact_lock(args.output_dir)", source)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+30
View File
@@ -511,6 +511,16 @@ class PipelineRegressions(unittest.TestCase):
]) ])
self.assertEqual(pipeline.parse_pack_counts(output), {"RetroArch": (535, 535)}) self.assertEqual(pipeline.parse_pack_counts(output), {"RetroArch": (535, 535)})
def test_exclusions_read_only_the_full_variant(self):
"""--all-variants printed six packs under one label; the last won."""
output = "\n".join([
"Generating pack for Batocera [source=full]...",
" a.zip: 5 files packed (5 baseline + 0 from cores), 5/5 files OK, 3 unsafe excluded",
"Generating pack for Batocera [source=truth, required]...",
" b.zip: 2 files packed (0 baseline + 2 from cores), 2/2 files OK",
])
self.assertEqual(pipeline.parse_pack_exclusions(output), {"Batocera": 3})
def test_slot_step_reaches_the_summary(self): def test_slot_step_reaches_the_summary(self):
source = Path(pipeline.__file__).read_text(encoding="utf-8") source = Path(pipeline.__file__).read_text(encoding="utf-8")
self.assertIn('results["slots"] = ok', source) self.assertIn('results["slots"] = ok', source)
@@ -560,6 +570,26 @@ class PipelineRegressions(unittest.TestCase):
self.assertTrue(full_models, truth_runs) self.assertTrue(full_models, truth_runs)
class SchemaFormatsAreChecked(unittest.TestCase):
"""date-time and uri need jsonschema's format extras; without them the
published format constraints were accepted unread."""
@unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema")
def test_a_missing_checker_is_named(self):
import validate_schemas # noqa: PLC0415
empty = mock.Mock(checkers={})
with mock.patch.object(validate_schemas, "FormatChecker", return_value=empty):
self.assertEqual(validate_schemas.unchecked_formats(), ["date-time", "uri"])
def test_every_install_asks_for_the_extras(self):
for path in (".github/workflows/validate.yml", ".github/workflows/deploy-site.yml",
"pyproject.toml"):
text = (ROOT / path).read_text(encoding="utf-8")
with self.subTest(path=path):
self.assertNotRegex(text, r"jsonschema(?!\[format)[=>]")
class ResolverRegressions(unittest.TestCase): class ResolverRegressions(unittest.TestCase):
def _database(self, entries: dict[str, Path], suffix: str | None = None) -> dict: def _database(self, entries: dict[str, Path], suffix: str | None = None) -> dict:
files = {} files = {}
+22
View File
@@ -304,5 +304,27 @@ class RepositoryWiringTests(unittest.TestCase):
self.assertIn("[data]", text) self.assertIn("[data]", text)
class RepeatedDestinations(unittest.TestCase):
def test_a_change_to_an_earlier_declaration_is_seen(self):
from check_freshness import diff_platform # noqa: PLC0415
def platform(first_md5: str) -> dict:
return {"systems": {"pce": {"files": [
{"name": "syscard3.pce", "destination": "syscard3.pce", "md5": first_md5},
{"name": "syscard3.pce", "destination": "syscard3.pce", "md5": "b" * 32},
]}}}
diff = diff_platform(platform("a" * 32), platform("c" * 32))
self.assertEqual(diff.files_changed, ["pce/syscard3.pce"])
class PinsWithExtras(unittest.TestCase):
def test_an_extras_bracket_keeps_its_pin(self):
from check_freshness import parse_pip_pins # noqa: PLC0415
pins = parse_pip_pins(' run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0"')
self.assertEqual(pins["jsonschema"], "==4.26.0")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+55
View File
@@ -0,0 +1,55 @@
"""CONTRIBUTING.md and the site's contributing page say the same thing.
Two generators wrote the page and drifted: four steps to add a platform in
the repository, five on the site, under two different titles.
"""
from __future__ import annotations
import re
import sys
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import generate_readme # noqa: E402
import generate_site # noqa: E402
def _normalized(text: str) -> str:
text = re.sub(r"\]\([^)]*\)", "](link)", text)
return "\n".join(text.splitlines()[1:]).replace("on this site", "on the documentation site")
class OneContributingText(unittest.TestCase):
def test_repository_and_site_pages_match(self):
self.assertEqual(
_normalized(generate_readme.generate_contributing()),
_normalized(generate_site.generate_contributing()),
)
class CoverageReadsTheGivenProfiles(unittest.TestCase):
"""generate_site --emulators-dir changed the emulator pages but not the
platform coverage, which read ./emulators whatever was given."""
def test_the_directory_reaches_verify(self):
seen: list[str] = []
def fake_verify(_config, _db, emulators_dir, **_kwargs):
seen.append(emulators_dir)
return {"status_counts": {}, "total_files": 0, "undeclared_files": [], "details": []}
with mock.patch.object(generate_readme, "verify_platform", fake_verify), \
mock.patch.object(generate_readme, "load_emulator_profiles", return_value={}):
generate_readme.compute_coverage(
"retroarch", str(REPO_ROOT / "platforms"), {"files": {}}, emulators_dir="elsewhere"
)
self.assertEqual(seen, ["elsewhere"])
if __name__ == "__main__":
unittest.main()
+59
View File
@@ -0,0 +1,59 @@
"""The gap report reads the collection the way the resolver does.
Three ways it called held files missing, or a missing file held: system IDs
compared verbatim (sega-megacd against sega-mega-cd), no sha256 in the hash
fallback (rust_dos names its SC-55 ROMs by sha256 alone), and directory names
indexed as files (BasiliskII's required ROM read as held because cpcemu keeps
a directory called ROM).
"""
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from cross_reference import ( # noqa: E402
_build_supplemental_index,
cross_reference,
entry_source,
)
def _index(**over) -> dict:
base = {
"by_name": {}, "by_name_lower": {}, "by_path_suffix": {}, "by_md5": {},
"by_sha256": {}, "by_crc32": {}, "db_files": {}, "data_names": set(),
}
base.update(over)
return base
class GapReportSources(unittest.TestCase):
def test_a_sha256_alone_finds_the_file(self):
entry = {"name": "wave1.bin", "sha256": "AB" * 32}
self.assertEqual(entry_source(entry, _index(by_sha256={"ab" * 32: "s"})), "bios")
def test_a_directory_does_not_stand_for_a_file(self):
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
(Path(tmp) / "bios" / "cpcemu" / "ROM").mkdir(parents=True)
names = _build_supplemental_index(str(Path(tmp) / "data"), str(Path(tmp) / "bios"))
self.assertIsNone(entry_source({"name": "ROM"}, _index(data_names=names)))
directory = {"name": "ROM", "type": "directory"}
self.assertEqual(entry_source(directory, _index(data_names=names)), "data")
def test_a_system_spelled_otherwise_is_still_declared(self):
profiles = {"gpgx": {"emulator": "gpgx", "systems": ["sega-megacd"],
"files": [{"name": "bios_CD_U.bin"}]}}
declared = {"megacd": {"bios_CD_U.bin"}} # sega-mega-cd, normalized
db = {"files": {}, "indexes": {}}
report = cross_reference(profiles, declared, db)
self.assertEqual(report["gpgx"]["gaps"], 0)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -201,7 +201,7 @@ class PackCarriesADirectory(DirectoryFixture):
out.mkdir() out.mkdir()
zip_path = builder.generate_pack( zip_path = builder.generate_pack(
"demo", str(self.platforms), self._db(), str(self.bios), str(out), "demo", str(self.platforms), self._db(), str(self.bios), str(out),
include_extras=True, emulators_dir=str(self.emulators), emulators_dir=str(self.emulators),
emu_profiles=profiles, offline=True, emu_profiles=profiles, offline=True,
) )
with zipfile.ZipFile(zip_path) as archive: with zipfile.ZipFile(zip_path) as archive:
+19 -2
View File
@@ -5562,11 +5562,13 @@ struct BurnDriver BurnDrvneogeo = {
with open(os.path.join(plat_dir, f"{name}.yml"), "w") as f: with open(os.path.join(plat_dir, f"{name}.yml"), "w") as f:
yaml.dump(cfg, f) yaml.dump(cfg, f)
# Keyed by the normalized system ID, as profiles spell systems
# differently from platforms.
everything, _ = load_platform_files(plat_dir) everything, _ = load_platform_files(plat_dir)
self.assertEqual(everything["shared-system"], {"a.bin", "b.bin"}) self.assertEqual(everything["sharedsystem"], {"a.bin", "b.bin"})
only_alpha, _ = load_platform_files(plat_dir, ["alpha"]) only_alpha, _ = load_platform_files(plat_dir, ["alpha"])
self.assertEqual(only_alpha["shared-system"], {"a.bin"}) self.assertEqual(only_alpha["sharedsystem"], {"a.bin"})
def test_227_manifest_injected_compressed(self): def test_227_manifest_injected_compressed(self):
@@ -6004,6 +6006,21 @@ struct BurnDriver BurnDrvneogeo = {
"refuse"), "refuse"),
(["--list-systems"], ["--region", "us"], "refuse"), (["--list-systems"], ["--region", "us"], "refuse"),
(["--list-emulators"], ["--target", "switch"], "refuse"), (["--list-emulators"], ["--target", "switch"], "refuse"),
# Only the platform path refreshes data directories.
(["--emulator", "dolphin"], ["--refresh-data"], "refuse"),
(["--system", "sony-playstation"], ["--refresh-data"], "refuse"),
(["--from-md5", "d8f1"], ["--refresh-data"], "refuse"),
(["--platform", "retroarch", "--offline"], ["--refresh-data"], "refuse"),
# Only --all chooses among platforms, as verify.py already says.
(["--platform", "retroarch"], ["--include-archived"], "refuse"),
(["--emulator", "handy"], ["--include-archived"], "refuse"),
(["--system", "atari-lynx"], ["--include-archived"], "refuse"),
(["--from-md5", "d8f1"], ["--include-archived"], "refuse"),
(["--platform", "retroarch", "--verify-packs"], ["--include-archived"],
"refuse"),
# Documented as adding core files, read by nothing.
(["--emulator", "handy"], ["--include-extras"], "refuse"),
(["--system", "atari-lynx"], ["--include-extras"], "refuse"),
] ]
for mode, flag, expected in matrix: for mode, flag, expected in matrix:
with self.subTest(mode=mode, flag=flag): with self.subTest(mode=mode, flag=flag):
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff. Show more