diff --git a/scripts/generate_site.py b/scripts/generate_site.py
index 4e33e7b3..41ab6526 100644
--- a/scripts/generate_site.py
+++ b/scripts/generate_site.py
@@ -2659,6 +2659,8 @@ def generate_files_page() -> str:
"",
'
',
"",
+ "To try it, paste `0555c6fae8906f3f09baf5988f00e55f88e9f30b`, the SHA1 of "
+ "`scph5501.bin`, the PlayStation BIOS v3.0. "
"To look a file up by its name, use the search at the top of the page.",
"",
"## Permanent links",
@@ -2911,6 +2913,10 @@ def generate_systems_index(manufacturers: dict) -> str:
lines = [
"# Systems",
"",
+ "Every BIOS file the collection holds, by manufacturer and console. Open "
+ "a console to see each file, the emulators that read it and the setups "
+ "that list it.",
+ "",
f"{total_mfr} manufacturers, {total_consoles} consoles, "
f"{total_files:,} files in the repository.",
"",
@@ -3028,6 +3034,11 @@ def generate_provenance_page(
lines = [
"# Dump provenance",
"",
+ "Provenance says whether a file is, byte for byte, a dump that Redump, "
+ "No-Intro or TOSEC catalogues. It tells you where a dump is known from, "
+ "not whether an emulator accepts it: that is the emulator's code, read "
+ "in its profile. Example: `scph5501.bin` is Redump's `ps-30a.bin`.",
+ "",
f"**{matched_files:,}** of {total_files:,} system files match an entry "
"in a dump-preservation catalog. Those files carry a "
'catalog badge and the '
@@ -3559,8 +3570,16 @@ def system_pages(
console: _console_cards(manufacturer, consoles[console], consumers, whole_anchors)
for console in sorted(consoles)
}
+ intro = (
+ f"Every BIOS file the collection holds for {manufacturer} machines, one "
+ "card per file. A card gives the file's fingerprints, the emulators that "
+ "read it, the setups that list it and where to download it. A file can "
+ "have several versions, one per region or revision: the main one comes "
+ "first, the others under \"Variants\". To install the files of one "
+ "console, open its \"Install\" box."
+ )
lines = [
- f"# {manufacturer}", "",
+ f"# {manufacturer}", "", intro, "",
f"The files below by console, with every hash, are also published as {json_link}.",
"",
]
@@ -3591,7 +3610,7 @@ def system_pages(
pages: dict[str, tuple[str, dict[str, list]]] = {}
summary = [
- f"# {manufacturer}", "",
+ f"# {manufacturer}", "", intro, "",
f"The files below by console, with every hash, are also published as {json_link}. "
f"The largest sections have a page of their own, linked from their heading.",
"",
@@ -3696,6 +3715,11 @@ def generate_emulators_index(profiles: dict) -> str:
lines = [
"# Emulators",
"",
+ "Each page is one emulator: the files it reads, whether it starts games "
+ "without them, and the line of its source code that says so. The "
+ "classification below says how a core relates to the emulator it comes "
+ "from, which tells how far its own files can differ.",
+ "",
f"**{len(unique):,}** emulator profiles, **{total_files:,}** files total, **{len(aliases):,}** aliases.",
"",
"| Classification | Count | Description |",
@@ -3703,13 +3727,13 @@ def generate_emulators_index(profiles: dict) -> str:
]
cls_desc = {
- "official_port": "Same author maintains both standalone and libretro",
- "community_fork": "Third-party port to libretro",
- "pure_libretro": "Built for libretro, no standalone version",
+ "official_port": "The same authors keep the app and the RetroArch core",
+ "community_fork": "Ported to RetroArch by someone else",
+ "pure_libretro": "Built for RetroArch, no app of its own",
"game_engine": "Game engine reimplementation",
"enhanced_fork": "Fork with added features",
"frozen_snapshot": "Frozen at an old version",
- "embedded_hle": "All ROMs compiled into binary",
+ "embedded_hle": "Needs no BIOS file: the emulator carries its own",
"launcher": "Launches an external emulator",
"other": "Unclassified",
}
@@ -4619,6 +4643,42 @@ def generate_emulator_page(
name, profile, db, consumers, data_names, data_registry, data_dirs)[name]
+def _emulator_intro(emu_name: str, profile: dict) -> str:
+ """What the emulator needs, in plain words, from fields every profile has.
+
+ The page opened on a twelve-row table: "Type", "Version", "Checked by".
+ DuckStation needs one PlayStation BIOS of any name, and the reader had
+ to infer it from the "Files found by" row."""
+ import siterender # noqa: PLC0415
+
+ names: list[str] = []
+ for sys_id in profile.get("systems") or []:
+ page = siterender._system_page_map.get(sys_id) or siterender._page_by_norm(sys_id)
+ label = page[1] if page else str(sys_id)
+ if label not in names:
+ names.append(label)
+ files = [f for f in profile.get("files") or [] if isinstance(f, dict)]
+ required = sum(1 for f in files if f.get("required") is True)
+ sentences = []
+ if names:
+ shown = names if len(names) <= 4 else names[:3] + [f"{len(names) - 3} more systems"]
+ listed = shown[0] if len(shown) == 1 else ", ".join(shown[:-1]) + f" and {shown[-1]}"
+ sentences.append(f"{emu_name} emulates {listed}.")
+ if files:
+ sentences.append(
+ f"It reads {_count(len(files), 'file')}, {required} of them required: "
+ "without a required file it does not start games."
+ if required else
+ f"It reads {_count(len(files), 'file')}, none of them required: it starts "
+ "games without them."
+ )
+ if profile.get("found_by") == "content":
+ sentences.append("It recognises a file by its contents, so any file name works.")
+ if any(f.get("source_ref") for f in files):
+ sentences.append("Each file below cites the line of source code that reads it.")
+ return " ".join(sentences)
+
+
def emulator_pages(
name: str,
profile: dict,
@@ -4671,9 +4731,11 @@ def emulator_pages(
data_dirs = profile.get("data_directories", [])
part_pages: dict[str, str] = {}
+ intro = _emulator_intro(emu_name, profile)
lines = [
f"# {emu_name}",
"",
+ *([intro, ""] if intro else []),
'',
"",
"| Detail | Value |",
@@ -4759,7 +4821,7 @@ def emulator_pages(
if notes:
indented = _admonition_body(link_prose_citations(profile, notes))
- lines.extend(['???+ note "Technical notes"', f" {indented}", ""])
+ lines.extend(['??? note "Technical notes"', f" {indented}", ""])
if not files:
lines.append("No BIOS or firmware files required.")
@@ -5154,14 +5216,15 @@ def _platform_lists_sentence(
labels = {"game_data": "game data",
"unread": "opened by none of the platform's emulators"}
firmware = missing - sum(not_firmware.values())
- pieces = [f"{firmware:,} firmware"] + [
- f"{count:,} {labels.get(category, category)}"
+ others = [
+ f"{count:,} {'is' if count == 1 else 'are'} {labels.get(category, category)}"
for category, count in sorted(not_firmware.items())
]
- split = (
- f" Of the missing, {', '.join(pieces[:-1])} and {pieces[-1]}: "
- "only missing firmware stops a console from starting."
- )
+ others_text = others[0] if len(others) == 1 else ", ".join(others[:-1]) + f" and {others[-1]}"
+ head = (f"None of the missing is firmware: {others_text}" if not firmware
+ else f"Of the missing, {firmware:,} {'is' if firmware == 1 else 'are'} "
+ f"firmware, {others_text}")
+ split = f" {head}. Only missing firmware stops a console from starting."
return (
f"Of the {total:,} files on the platforms' own BIOS lists, {listed}.{split} "
"The files the emulators load beyond those lists are counted per "
@@ -5883,29 +5946,31 @@ def generate_wiki_data_model(db: dict, profiles: dict) -> str:
by_path = len(db.get("indexes", {}).get("by_path_suffix", {}))
by_sha256 = len(db.get("indexes", {}).get("by_sha256", {}))
+ # A real entry with real values: the PlayStation BIOS v3.0 (A).
+ sample_key = "0555c6fae8906f3f09baf5988f00e55f88e9f30b"
+ files = db.get("files", {})
+ sample = files.get(sample_key) or next(iter(files.values()), {})
lines = [
"# Data model",
"",
+ "Three kinds of file hold everything this project knows. `database.json` "
+ "lists every file the collection holds, keyed by its SHA1. A platform YAML "
+ "copies the BIOS list a setup publishes. An emulator profile records what "
+ "one emulator opens, read from its code. The sections below show the "
+ "fields of each, with a real entry, so you can read the data or build on it.",
+ "",
+ "Example: `scph5501.bin` is one entry of `database.json` (key "
+ "`0555c6fa...`), one entry of `platforms/batocera.yml` (its MD5) and one "
+ "entry of `emulators/duckstation.yml` (its MD5, region and priority).",
+ "",
"## database.json",
"",
f"Primary key: SHA1. **{files_count}** file entries.",
"",
- "Each entry:",
+ f"The entry of `{sample.get('name', '')}`:",
"",
"```json",
- "{",
- ' "path": "bios/Nintendo/GameCube/GC/USA/IPL.bin",',
- ' "name": "IPL.bin",',
- ' "size": 2097152,',
- ' "sha1": "...",',
- ' "md5": "...",',
- ' "sha256": "...",',
- ' "crc32": "...",',
- ' "adler32": "...",',
- ' "provenance": {',
- ' "redump": {"dat": "...", "name": "...", "description": "..."}',
- " }",
- "}",
+ *json.dumps(sample, indent=2, ensure_ascii=False).splitlines(),
"```",
"",
"`provenance` maps each catalog that lists the file to the DAT and entry "
@@ -5925,24 +5990,11 @@ def generate_wiki_data_model(db: dict, profiles: dict) -> str:
"",
"### File resolution order",
"",
- "`resolve_local_file` tries these steps in order:",
- "",
- "1. SHA1 exact match: every other declared hash must agree with the record",
- "2. SHA256 exact match, with the same all-declarations-must-agree rule",
- "3. CRC32 plus declared size, only when no stronger hash is present",
- "4. MD5 direct lookup (including explicitly supported truncated MD5 values)",
- "5. Path suffix lookup for regional variants. With hashes it is accepted only if those hashes match",
- "6. Name and alias lookup only when no content hash was declared",
- "7. Candidate inspection for composite ZIP MD5 or direct MD5. A named candidate with the wrong content returns `hash_mismatch`",
- "8. `zipped_file` content match via the inner-ROM MD5 index",
- "9. MAME clone fallback, only for declarations without a content hash",
- "10. Data-directory scan: declared hashes are computed over the candidate before it is accepted",
- "11. Agnostic size/path fallback, only for declarations without a content hash",
- "",
- "A filename or destination can never override a declared hash. The first "
- "evidence-compatible match wins, otherwise the resolver reports a mismatch "
- "or absence. Steps and their return codes are described in "
- "[verification modes](verification-modes.md#file-resolution-chain).",
+ "`resolve_local_file` finds the file an entry describes by its hashes "
+ "first, then by its path, then by its name, and a filename or "
+ "destination never overrides a declared hash. The steps and their "
+ "return codes are written once, in "
+ "[the file resolution chain](verification-modes.md#file-resolution-chain).",
"",
"## Platform YAML",
"",
diff --git a/tests/test_site_copy.py b/tests/test_site_copy.py
index a4518587..d44a673a 100644
--- a/tests/test_site_copy.py
+++ b/tests/test_site_copy.py
@@ -48,7 +48,8 @@ HOME_BEFORE_ACTION = 40
LIST_ITEMS = {"start": 5, "setup": 6}
HEADING_WORDS = 8
-# Words that say nothing, or talk about the page instead of the task.
+# Words that say nothing, or talk about the page instead of the task. The
+# marketing words are held on every page by test_prose_style.
FILLER = (
"just", "simply", "easy", "easily", "easiest", "basically", "actually",
"really", "very", "quite", "note that", "please note", "keep in mind",
@@ -58,7 +59,7 @@ FILLER = (
"the rest of this page", "further down", "the usual causes", "come up",
"one path", "a few minutes", "is the answer", "the normal way", "not a fault",
"does not pretend", "plainly", "for when you want", "for your own tools",
- "welcome", "robust", "seamless", "comprehensive", "leverage", "streamline",
+ "welcome",
"ensure", "utilize", "facilitate", "via", "e.g.", "i.e.", "etc.", "vs.",
"respectively", "deliberately",
)
diff --git a/wiki/adding-a-platform.md b/wiki/adding-a-platform.md
index 870fcc1f..061ea3e2 100644
--- a/wiki/adding-a-platform.md
+++ b/wiki/adding-a-platform.md
@@ -4,8 +4,17 @@ level: contribute
# Adding a platform
-How to add support for a new retrogaming platform (e.g. a frontend like Batocera,
-a manager like EmuDeck, or a firmware database like BizHawk).
+Adding a platform gives its users a pack and a one-line install with the files
+their setup checks, in the place it checks them, plus every file its emulators
+need that its own list leaves out. The work is to read how the platform
+declares and checks BIOS files, then teach this project to read that
+declaration. A platform can be a frontend like Batocera, a manager like EmuDeck
+or a firmware database like BizHawk.
+
+Example: RomM declares its BIOS files in
+`backend/models/fixtures/known_bios_files.json` and checks size plus any one
+hash (`firmware.py`, `verify_file_hashes`). Its scraper turns that JSON into
+`platforms/romm.yml`, and the pack, the installer and its site page follow.
## Prerequisites
diff --git a/wiki/adding-a-scraper.md b/wiki/adding-a-scraper.md
index 29559ce8..b5ad0e44 100644
--- a/wiki/adding-a-scraper.md
+++ b/wiki/adding-a-scraper.md
@@ -4,8 +4,15 @@ level: contribute
# Adding a scraper
-How to create or modify a scraper for fetching BIOS requirements from upstream
-platform sources.
+A scraper reads a platform's own BIOS list and writes it as
+`platforms/.yml`, the file every pack, check and page of that platform
+starts from. It copies what the platform declares, as the platform wrote it,
+and leaves the corrections to the emulator profiles.
+
+Example: `scripts/scraper/romm_scraper.py` reads RomM's
+`known_bios_files.json` and writes `platforms/romm.yml`. Adding a scraper means
+one `*_scraper.py` file with a class that extends `BaseScraper`, the base class
+every scraper extends.
## Scraper architecture
diff --git a/wiki/advanced-usage.md b/wiki/advanced-usage.md
index 7d06867b..d9734659 100644
--- a/wiki/advanced-usage.md
+++ b/wiki/advanced-usage.md
@@ -4,7 +4,9 @@ level: reference
# Build your own packs
-Fine-grained control over pack generation, hardware filtering, truth analysis, and verification.
+Fine-grained control over pack generation, hardware filtering, truth analysis (the file
+list a platform would have if it were written from the emulators' code) and
+verification.
The [latest release](https://github.com/Abdess/retrobios/releases/latest)
carries one pack for each platform, and that pack holds everything the
diff --git a/wiki/architecture.md b/wiki/architecture.md
index 3ab42b66..aa2de9aa 100644
--- a/wiki/architecture.md
+++ b/wiki/architecture.md
@@ -4,37 +4,16 @@ level: reference
# Architecture
-## Principles
+How a BIOS list on a platform's site becomes a pack on your drive. Scrapers
+copy each platform's own list. Emulator profiles record what each emulator's
+code opens. `database.json` indexes every file the collection holds by its
+contents. The pack builder joins the three and ships every file a platform's
+emulators need, and the verifier checks the result the way the platform itself
+would.
-Every part of the toolchain follows from a few rules.
-
-- **The emulator's source code is the authority.** A profile records what the
- code opens, under which names, what it checks and whether it runs without
- the file. Documentation, `.info` files and platform lists are starting
- points to verify. When they disagree with the code, the code wins.
-- **Every statement rests on a fact anyone can check.** A field, a deletion or
- a wording names what settles it: a `file:line` at a pinned revision, a hash,
- a command and its output. Where no fact settles a question, the answer is
- "not verified" until one is found. A sentence on this site once said
- Batocera matches md5 prefixes. Its script compares the full string
- (`batocera-systems:1088`).
-- **The collection serves the user.** A pack carries what the user has to
- obtain to run their emulators. When something forces the user or a
- contributor to work around the toolchain, the toolchain is changed rather
- than the workaround documented.
-- **Profiles document the emulator, not this repository.** A profile can be
- taken into another project as is. No field refers to this repository's
- folders, file names or database, nor to one platform's conventions. The
- toolchain resolves a profile against the collection.
-- **Other projects are peers.** A finding about another project says what
- changes, never who erred, and comes with its evidence. Our data can be
- wrong too, and an issue that shows it is as welcome as a finding sent
- upstream.
-- **An inconsistency found is fixed now.** A fix comes with a test that fails
- without it, so the same mistake cannot return unnoticed.
-- **Collect everything a working setup needs.** BIOS, firmware, keys, fonts,
- boot and DSP ROMs, databases, data files. Age and licence are not criteria.
- A file the collection lacks is an acquisition target, not an end state.
+Example: libretro's `System.dat` names 526 RetroArch files. The cores RetroArch
+runs load 7,650 more, and the RetroArch pack carries both (counts of
+[What the packs do not hold](../gaps.md) on 2026-10-22).
## Directory structure
@@ -130,6 +109,36 @@ for the full pipeline reference.
+## Principles
+
+Every part of the toolchain follows from a few rules.
+
+- **The emulator's source code is the authority.** A profile records what the
+ code opens, under which names, what it checks and whether it runs without
+ the file. Documentation, `.info` files and platform lists are starting
+ points to verify. When they disagree with the code, the code wins.
+- **Every statement rests on a fact anyone can check.** A field, a deletion or
+ a wording names what settles it: a `file:line` at a pinned revision, a hash,
+ a command and its output. Where no fact settles a question, the answer is
+ "not verified" until one is found.
+- **The collection serves the user.** A pack carries what the user has to
+ obtain to run their emulators. When something forces the user or a
+ contributor to work around the toolchain, the toolchain is changed rather
+ than the workaround documented.
+- **Profiles document the emulator, not this repository.** A profile can be
+ taken into another project as is. No field refers to this repository's
+ folders, file names or database, nor to one platform's conventions. The
+ toolchain resolves a profile against the collection.
+- **Other projects are peers.** A finding about another project says what
+ changes, never who erred, and comes with its evidence. Our data can be
+ wrong too, and an issue that shows it is as welcome as a finding sent
+ upstream.
+- **An inconsistency found is fixed now.** A fix comes with a test that fails
+ without it, so the same mistake cannot return unnoticed.
+- **Collect everything a working setup needs.** BIOS, firmware, keys, fonts,
+ boot and DSP ROMs, databases, data files. Age and licence are not criteria.
+ A file the collection lacks is an acquisition target, not an end state.
+
## Three layers of data
| Layer | Source | Role |
@@ -168,7 +177,7 @@ Some entries in a platform YAML are not the platform's own: files a
to a twin system (`mirror_of`) and additions the libretro scraper makes
(`curated`). `native_layer()` removes them wherever the question is what the
platform itself declares: the export, the truth diff and the site's
-comparison tables. Counted as the platform's, they were written into
+comparison tables. Counted as the platform's, they would be written into
System.dat as if libretro had declared them.
diff --git a/wiki/profiling.md b/wiki/profiling.md
index 8d60f38f..af09f234 100644
--- a/wiki/profiling.md
+++ b/wiki/profiling.md
@@ -4,7 +4,24 @@ level: contribute
# Profiling guide
-How to create an emulator profile from source code.
+A profile is a YAML file that says which files one emulator opens, under which
+names, what it checks in them and whether it runs without them. Each line
+points at the code that proves it, at a fixed revision of that code (the pin,
+`source_commit`), so anyone can check it. Packs, checks and every emulator page
+on this site are built from profiles.
+
+Example, one entry of `emulators/duckstation.yml`:
+
+```yaml
+- name: "scph5501.bin"
+ region: [north-america, latin-america]
+ required: true
+ md5: "490f666e1afb15b7362b406ed1cea246"
+ priority: 5
+ source_ref: "src/core/bios.cpp:47"
+```
+
+This guide shows how to read an emulator's code to write one.
## Approach
@@ -15,15 +32,13 @@ but are verified against the code.
### Source hierarchy
-Documentation and metadata are valuable starting points, but they can
-fall out of sync with the actual code over time. The desmume2015 .info
-file is a good illustration: it declares `firmware_count=3`, but the
-source code at the pinned version opens zero firmware files. Cross-checking
-against the source helps catch that kind of gap early.
+The desmume2015 `.info` file declares `firmware_count=3`. The source at the
+pinned revision opens no firmware file. Documentation and metadata are where a
+profile starts, and the code is where it is checked.
When sources conflict, priority follows the chain of actual execution:
-1. **Original emulator source** (ground truth, what the code actually does)
+1. **Original emulator source** (ground truth, what the code does)
2. **Libretro port** (may adapt paths, add compatibility shims, or drop features)
3. **.info metadata** (declarative, may be outdated or copied from another core)
@@ -218,7 +233,8 @@ selection, and a Hong Kong PS2 is an NTSC-J console.
### 3c. Record the search order
-When several files can fill one slot, `priority:` records the order the code
+When several files can fill one slot (one role for one system and region, such
+as the North American PlayStation BIOS), `priority:` records the order the code
looks for them, lowest first, read from the source alone. Two cores can rank
the same file differently: pcsx1 walks `scph1001`, `scph5501`, `scph7001` in
that order while DuckStation prefers `scph5501` (5) to `scph1001` (10). The
diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md
index 43bf98bb..a22e7363 100644
--- a/wiki/testing-guide.md
+++ b/wiki/testing-guide.md
@@ -4,10 +4,13 @@ level: contribute
# Testing guide
-The suite is discovered dynamically across focused modules, so its exact count
-is reported by the test runner instead of being hand-maintained here. Tests do
-not require network access. Most build synthetic fixtures under the repository's
-`tmp/`. Modules that read real artifacts skip cleanly when those inputs are absent.
+The tests hold what users and platforms rely on: a pack holds what its
+platform checks, the site states what the code does, the installer writes only
+where it should.
+
+No test needs network access. Most build synthetic fixtures under the
+repository's `tmp/`, and modules that read real artifacts skip when those are
+absent. The test runner prints the count.
## Running tests
diff --git a/wiki/verification-modes.md b/wiki/verification-modes.md
index 0b940009..ab7eb3d4 100644
--- a/wiki/verification-modes.md
+++ b/wiki/verification-modes.md
@@ -4,8 +4,22 @@ level: reference
# How each platform checks files
-Each platform verifies BIOS files differently. `verify.py` replicates the native behavior
-of each platform so that verification results match what the platform itself would report.
+Before a game starts, your setup may check its BIOS files, and each setup checks
+differently. RetroArch only asks whether a file with the right name is in the
+folder. Batocera computes the file's MD5 fingerprint and compares it with its
+list, so a renamed or wrong-region file shows as `UNTESTED`: present, but not
+the expected file. BizHawk does the same with SHA1. The way a setup checks
+decides what "OK" means there: a file RetroArch accepts can still be one
+Batocera reports.
+
+Example: `scph5501.bin`, MD5 `490f666e1afb15b7362b406ed1cea246`. Any file named
+`scph5501.bin` passes on RetroArch. On Batocera only those exact bytes pass.
+
+This project's `verify.py` reproduces each setup's own check, so its report
+says what the setup itself would say. A missing file is then ranked by
+**severity**: CRITICAL when the setup cannot start that console, WARNING when
+it runs degraded or the file's version is in doubt, INFO when nothing visible
+changes ([severity matrix](#severity-matrix)).
@@ -15,8 +29,8 @@ of each platform so that verification results match what the platform itself wou
**Source**: RetroArch `core_info.c`, function `path_is_valid()`; minarch for the MinUI family
-The most straightforward mode. A file is OK if it exists at the expected path. No hash is checked.
-Any file with the correct name passes, regardless of content.
+A file is OK if it exists at the expected path. No hash is checked. Any file
+with the correct name passes, regardless of content.
| Condition | Status | Severity (required) | Severity (optional) |
|-----------|--------|---------------------|---------------------|
@@ -24,15 +38,15 @@ Any file with the correct name passes, regardless of content.
| File missing | MISSING | WARNING | INFO |
RetroArch does not distinguish between a correct and an incorrect BIOS at the verification
-level. A corrupt or wrong-region file still shows as present. This is by design in the
-upstream code: `core_info.c` only calls `path_is_valid()` and does not open or hash the file.
+level. A corrupt or wrong-region file still shows as present: RetroArch's
+`core_info.c` only calls `path_is_valid()` and does not open or hash the file.
Lakka and RetroPie inherit this behavior through platform config inheritance
(`inherits: retroarch` in the platform YAML).
-MinUI, NextUI and MyMinUI check nothing at all: minarch returns the folder
-`Bios//` for `RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY` and never opens a
-file in it (MinUI `workspace/all/minarch/minarch.c:1889-1893` and `:2943`). The
+MinUI, NextUI and MyMinUI check nothing at all. They hand each core its own
+folder, `Bios//`, and open nothing in it: minarch answers
+`RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY` with that folder (MinUI `workspace/all/minarch/minarch.c:1889-1893` and `:2943`). The
core decides what it loads, so the files are the ones its profile documents,
placed below each folder a pak hands it (`core_system_dirs` in the platform
YAML).
@@ -56,8 +70,9 @@ hashed with MD5, and compared case-insensitively against the expected value.
| File present, hash differs | UNTESTED | WARNING | WARNING |
| File missing | MISSING | CRITICAL | WARNING |
-If the `resolve_local_file` step already confirmed the MD5 match (status `md5_exact`),
-`verify.py` skips re-hashing and returns OK directly.
+If finding the file already confirmed the MD5 match (status `md5_exact` of the
+[file resolution chain](#file-resolution-chain)), `verify.py` skips re-hashing
+and returns OK directly.
### MD5 shorter than 32 characters (Batocera)
@@ -381,8 +396,10 @@ A local file that contradicts a declared hash is handled by the platform's
own mode, never by one global rule. An existence frontend never reads the
bytes, so the file is packed and the difference reported: a hash in System.dat
that differs from the local dump must not remove a file RetroArch would have
-loaded. An md5 or sha1 frontend would reject it, so it is left out of the pack
-and counted as `unsafe excluded`, which is distinct from `missing`. The pack
+loaded. An md5 or sha1 frontend would report it as not the expected file
+(Batocera's `checkBios` marks it `UNTESTED`), so it is left out of the pack
+and counted as `unsafe excluded`, left out because the platform would not
+accept it, which is distinct from `missing`. The pack
report and the installer manifest both record it. The pack builder and the
pack verification apply the same rule.
@@ -401,22 +418,29 @@ pack, verify and the install manifest all read it, so they name the same file.
the emulator declares, which finds a dump stored under another name, then
from the files sharing the name, `.variants/` included, in a stable order.
On a md5 or sha1 platform a candidate must also carry a value the platform
- entry declares, or the frontend would refuse it.
+ entry declares, or the frontend's own check would not accept it.
4. When only a warning-level check fails (`on_mismatch: warn`), the file
itself stands: the core runs it.
5. Then a held file the core at least runs.
6. Failing all of these, the platform-verified file is kept and the
disagreement is reported (" says ..."), never as compatible.
-### Practical example
+### Worked example
-A `scph5501.bin` file passes Batocera MD5 verification (hash matches upstream
-declaration) but fails an emulator profile's size check because the profile
-was verified against a different revision. The search looks for a held file
-carrying a Batocera-accepted md5 that also passes the size check, under that
-name or any name the emulator's hashes reach. If found, it ships. If not, the
-Batocera-verified file ships and the discrepancy stays visible in verification
-and gap reporting.
+RetroBat lists `dragon/d64_1.rom` with MD5 `5f0bee59710e55f5880e74890912ed78`
+(`platforms/retrobat.yml`). The collection holds that dump, CRC32 `60a4634c`,
+and RetroBat's check passes. XRoar, which RetroBat runs for the Dragon, checks
+the CRC32 of the same file and accepts `84f68bf9` only. The collection holds a
+dump with that CRC32 too (`bios/Dragon/Dragon/d64_1.rom`), but its MD5 is
+`be9bc86ee5eb401d0a40d0377f65fefa`, not the one RetroBat lists, so RetroBat
+would not accept it at that path (step 3). The RetroBat file ships, and the
+[gaps page](../gaps.md) lists the discrepancy with the dump XRoar accepts.
+
+## Reading this project's own reports
+
+The sections below are about `verify.py` run on a clone of the repository.
+Your own BIOS folder is checked with the installer's `--check`
+([When a game does not start](troubleshooting.md)).
## Hash mismatch / UNTESTED
@@ -449,8 +473,8 @@ dumps), the primary version lives in the main directory and alternatives live in
`.variants/`. `verify.py` checks the primary file first, then falls back to
variants when resolving by hash.
-If your file matches a variant hash but not the primary, it's a valid BIOS --
-just not the preferred version. Some cores accept multiple versions.
+A file that matches a variant hash but not the primary works. It is not the
+version the emulator tries first, and some cores accept several versions.
## Pack is missing files