mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-27 14:17:25 -05:00
docs: open the deep pages on their question
Each reference and contributor page now says what it answers and gives
a real case before its rules: scph5501.bin for the check modes and the
data model, DuckStation's entry for profiles, RomM for a platform. The
verification example is the RetroBat d64_1.rom case the gaps page lists
instead of a scph5501.bin case the data cannot produce.
Emulator pages open on what the emulator reads, from the profile, and
keep their technical notes folded. System pages say what a card holds.
The data model shows a real database entry and links the one
resolution chain. Gaps tables name what they count ("Discrepancy",
"Where the files come from"), and the core inventory is titled Cores
by platform so cross-reference keeps one meaning.
This commit is contained in:
1 parent
70d761682c
commit
009413944e
9 files changed
+242
-119
No files matched your search
+97
-45
@@ -2659,6 +2659,8 @@ def generate_files_page() -> str:
|
||||
"",
|
||||
'<p id="rb-file-lookup-status" role="status"></p>',
|
||||
"",
|
||||
"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 "
|
||||
'<span class="rb-badge rb-badge-success">catalog</span> 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 []),
|
||||
'<div class="rb-meta-card" markdown>',
|
||||
"",
|
||||
"| 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",
|
||||
"",
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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/<name>.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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+40
-31
@@ -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.
|
||||
|
||||
<!-- image: pipeline -->
|
||||
|
||||
## 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.
|
||||
|
||||
<!-- image: data-layers -->
|
||||
|
||||
+24
-8
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+48
-24
@@ -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)).
|
||||
|
||||
<!-- image: verification-modes -->
|
||||
|
||||
@@ -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/<TAG>/` 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/<TAG>/`, 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 ("<core> 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
|
||||
|
||||
|
||||
Reference in new issue
Block a user