chore: merge round 2 fixes (docs)

# Conflicts:
#	CONTRIBUTING.md
#	scripts/check_release_assets.py
#	scripts/generate_readme.py
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:31:04 +02:00
commit 6409757c39
22 files changed
+654 -115

No files matched your search

+38 -18
View File
@@ -250,6 +250,9 @@ def format_size(size: int) -> str:
SITE_URL = "https://abdess.github.io/retrobios/"
REPO_PATH = "../.."
REPO = "Abdess/retrobios"
# GitHub's "Add file > Upload files" page for bios/: a browser upload by
# someone without write access lands on a fork and opens a pull request.
UPLOAD_URL = f"https://github.com/{REPO}/upload/main/bios"
def _existing_contributor_block(readme_path: str = "README.md") -> list[str]:
@@ -329,7 +332,6 @@ def _catalog_matched_line(db: dict) -> list[str]:
def generate_readme(db: dict, platforms_dir: str) -> str:
total_files = db.get("total_files", 0)
total_size = db.get("total_size", 0)
size_mb = total_size / (1024 * 1024)
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
platform_names = list_registered_platforms(platforms_dir, include_archived=True)
@@ -397,9 +399,13 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
"# SD card mounted on a PC: --dest is the BIOS folder on the card",
"# (RetroArch on Switch: retroarch/cores/system, on Android: RetroArch/system)",
"curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s -- --platform retroarch --dest /path/to/sdcard/retroarch/cores/system",
"",
"# Options in PowerShell: anything after | iex goes to iex, so the script runs as a block",
"&([scriptblock]::Create((irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1))) --check",
"```",
"",
"The script auto-detects your platform, downloads only missing files, and verifies checksums.",
f"Every option, and what the installer detects and writes, is on the [installer page]({SITE_URL}wiki/installer/#passing-options).",
"",
"## Download BIOS packs",
"",
@@ -462,16 +468,17 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
"",
"Each file is checked the way your platform checks it. Most compare"
" a checksum, the fingerprint of a file's contents, which catches a"
" corrupt or unexpected copy. RetroArch, Lakka and RetroPie only"
" look for the filename, because that is all their code does: the"
" corrupt or unexpected copy. RetroArch, Lakka and RetroPie check"
" that the file is present under its name without reading its"
" contents (`path_is_valid` in RetroArch's `core_info.c`): the"
" Coverage table says which applies to you. Independently of that,"
" the collection records five fingerprints per file, and wherever"
" an emulator's code states an expected size or hash, that value is"
" read from its source and rechecked here.",
"",
(
f"- **{acquirable_total} files** the platforms' emulators load"
f" are still to be found, and {unsourceable_total} more cannot"
f"- **{acquirable_total:,} files** the platforms' emulators load"
f" are still to be found, and {unsourceable_total:,} more cannot"
f" be sourced at all (per-user keys, user-filled slots, dumps"
f" nobody has made). Both are named in the"
f" [gap analysis]({SITE_URL}gaps/)"
@@ -484,7 +491,7 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
f"- **{len(system_ids)} systems** handled by those emulators (NES, SNES, PlayStation, Saturn, Dreamcast, ...)",
collection_line(total_files, comp),
*_catalog_matched_line(db),
f"- **{size_mb:.0f} MB** total collection size",
f"- **{format_size(total_size)}** for the whole collection",
"",
"## Supported systems",
"",
@@ -621,10 +628,10 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
"",
"## How it works",
"",
"Documentation and metadata can drift from what emulators actually load.",
"To keep packs accurate, platform lists are checked against emulator"
" source code, file by file where a profile exists. When the two"
" disagree, the code wins.",
"A platform's BIOS list and an emulator's code are maintained"
" separately. Each list is compared with the emulator source code,"
" file by file where a profile exists, and where the two differ the"
" code decides.",
"",
"Hashes document what emulator code loads and accepts, not dump"
" provenance. That boundary, and how it relates to preservation"
@@ -710,12 +717,21 @@ def contributing_text(
## Add a BIOS file
1. Fork this repository
2. Place the file in `bios/Manufacturer/Console/filename`.
`python scripts/validate_pr.py path/to/file` names the folder its system
is kept in, and whether a platform or an emulator profile declares it
3. Variants (alternate hashes for the same file): `bios/Manufacturer/Console/.variants/`
4. Open a pull request. Its hashes are checked automatically and reported in a comment
One file needs no clone and no local setup:
1. [Upload it into `bios/`]({UPLOAD_URL})
from the browser, 25 MB at most per file. GitHub writes it to a fork and
opens the pull request.
2. CI hashes it and looks it up in every platform list and emulator profile.
For a file it recognises, it names the folder holding the other files of its
system. The report is in the summary of the pull request's Validation run.
3. The file can be moved to that folder in the same pull request. A file whose
hash differs from the one already held under its name goes to that folder's
`.variants/`.
From a clone, the file goes to `bios/Manufacturer/Console/filename`, and
`python scripts/validate_pr.py path/to/file` names that folder for a file still
outside `bios/`. A file over the browser's 25 MB limit goes in that way.
The [dump provenance]({link("provenance")}) page lists catalogued dumps still
missing from the collection, with their hashes. A file matching one of those is
@@ -753,7 +769,9 @@ Full walkthrough: [profiling guide]({link("wiki/profiling")}).
## Before opening a PR
The checks CI runs on a pull request (Python 3.12 or newer):
A pull request that only adds files under `bios/` needs none of this: the
validation above is its check. For code, profiles or platform files, these are
the checks CI runs on a pull request (Python 3.12 or newer):
```bash
pip install pyyaml "jsonschema[format-nongpl]" markdown \\
@@ -772,7 +790,9 @@ in `dist/`, which is why maintainers run it before a release.
CI computes SHA1/MD5/CRC32 for every new file, checks them against the platform
configs, validates the YAML against the schemas, confronts each added profile
with its upstream source, runs the test suite, and posts a report on the PR.
with its upstream source and runs the test suite. The BIOS report goes to the
summary of the Validation run, and also to a comment when the branch lives in
this repository: a fork's pull request runs with a read-only token.
Contributors who add platform support are credited in the README,
{site_words}, and in the BIOS packs.
+1 -1
View File
@@ -2697,7 +2697,7 @@ def generate_emulators_index(profiles: dict) -> str:
lines = [
"# Emulators",
"",
f"**{len(unique)}** emulator profiles, **{total_files}** files total, **{len(aliases)}** aliases.",
f"**{len(unique):,}** emulator profiles, **{total_files:,}** files total, **{len(aliases):,}** aliases.",
"",
"| Classification | Count | Description |",
"|---------------|-------|-------------|",
+2 -2
View File
@@ -314,8 +314,8 @@ def _hash_matches(declared: str, actual: str) -> bool:
"""Compare a declared hash value against an actual hex digest.
Handles comma-separated multi-hash lists and uppercase (Recalbox)
and truncated MD5s (Batocera 29-char): a declared value shorter
than the digest matches by prefix.
and MD5s shorter than 32 characters: a declared value shorter than
the digest matches by prefix.
"""
actual = actual.lower()
for cand in declared.split(","):
+6 -1
View File
@@ -793,7 +793,12 @@ def digest_verdict(
actual = hashes()[algorithm].lower()
if algorithm != "md5":
return actual in declared
if any(actual == d or (len(d) < 32 and actual.startswith(d)) for d in declared):
# Recalbox hashes a .zip by its members only (Bios.cpp:64-68), so the
# container's own md5 answers nothing there.
composite_only = file_entry.get("zip_digest") == "composite"
if not composite_only and any(
actual == d or (len(d) < 32 and actual.startswith(d)) for d in declared
):
return True
member_digest = composite()
return bool(member_digest) and member_digest.lower() in declared
+1 -1
View File
@@ -1476,7 +1476,7 @@ def _format_ground_truth_aggregate(ground_truth: list[dict]) -> str:
def _format_ground_truth_verbose(ground_truth: list[dict]) -> list[str]:
"""Format ground truth as one line per core with expected values and source ref.
Example: handy validates size=512,crc32=0d973c9d [rom.h:48-49]
Example: handy validates crc32=0d973c9d,size=512 [lynx/rom.h:48-49]
"""
lines = []
for gt in ground_truth: