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