diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 01b379ce..4fa1f1b3 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -5025,6 +5025,9 @@ def generate_wiki_data_model(db: dict, profiles: dict) -> str: "`base_destination` is the prefix the pack applies to every entry. It is", "empty when the upstream destinations already carry their own root, which", "is why the RetroDECK pack ships `bios/` and `roms/` at its top level.", + "Core files then go under `core_destination` when the platform states one", + "(`bios` for AmberELEC and EmuELEC, whose lists also name `neogeo/` and", + "`ports/`), else under the root nearly every destination shares.", "", "## Emulator YAML", "", @@ -5248,6 +5251,9 @@ ZIP: extract them all into the same folder. | Android (Retroid Pocket, Odin, etc.) | RetroArch for Android | [RetroArch]({rel}) | `RetroArch/system/` on internal storage or SD card | | [ROCKNIX](https://rocknix.org/) | Linux OS for ARM and x86 handhelds, among them the RG35XX (H700) and the RG552 (RK3399) | [ROCKNIX]({rel}) | `/storage/roms/bios/`, over SSH or the network share | | [Batocera](https://batocera.org/) | Also has images for handheld chips, among them H700 and RK3326 | [Batocera]({rel}) | `/userdata/bios/` | +| [KNULLI](https://knulli.org/) | Batocera-based OS for handhelds, among them the RG35XX family (H700) and the TrimUI Smart Pro | [KNULLI]({rel}) | `/userdata/bios/` | +| [AmberELEC](https://amberelec.org/) | Linux OS for Anbernic handhelds: RG351P, RG351V, RG351MP, RG353, RG503 and RG552 | [AmberELEC]({rel}) | `/storage/roms/`, which the pack fills with `bios/` and `neogeo/` | +| [EmuELEC](https://emuelec.org/) | Linux OS for Amlogic TV boxes, the Odroid Go Advance, GameForce and RK356x devices | [EmuELEC]({rel}) | `/storage/roms/`, which the pack fills with `bios/`, `neogeo/` and `ports/` | ### FPGA @@ -5315,6 +5321,16 @@ def _link_pack_cells(page: str) -> str: raise KeyError(f"no registered platform is displayed as {label!r}") return f"| {download_links(keys[label], record, REPO_URL, label)} |" + def released(line: str) -> bool: + # A platform registered after the recorded release has no pack in + # it yet: its row would send the reader to a release without one. + found = cell.search(line) + if not found or not record.get("packs"): + return True + label = found.group(1) + return label not in keys or release_record.pack_for(keys[label], record) is not None + + page = "\n".join(line for line in page.split("\n") if released(line)) return cell.sub(link, page) @@ -5702,8 +5718,8 @@ def _generate(args: argparse.Namespace) -> None: mkdocs_static = """\ site_name: RetroBIOS site_description: Source-verified BIOS and firmware packs for RetroArch, Batocera, - Recalbox, Lakka, RetroPie, EmuDeck, RetroBat, RetroDECK, RomM, BizHawk, ROCKNIX - and MiSTer FPGA. + Recalbox, Lakka, RetroPie, EmuDeck, RetroBat, RetroDECK, RomM, BizHawk, ROCKNIX, + KNULLI, AmberELEC, EmuELEC and MiSTer FPGA. site_url: https://abdess.github.io/retrobios/ repo_url: https://github.com/Abdess/retrobios repo_name: Abdess/retrobios diff --git a/tests/test_doc_surfaces.py b/tests/test_doc_surfaces.py index c8930813..18703ec3 100644 --- a/tests/test_doc_surfaces.py +++ b/tests/test_doc_surfaces.py @@ -152,6 +152,22 @@ class DownloadLinksNameTheFile(unittest.TestCase): for asset in pack["assets"]: self.assertIn(f"/releases/download/{RECORD['tag']}/{asset})", page) + def test_a_platform_the_release_lacks_has_no_row(self): + """KNULLI, AmberELEC and EmuELEC were registered after the recorded + release: a row for them linked a release page holding no such pack.""" + if not RECORD.get("packs"): + self.skipTest("release.json is not written") + from common import list_registered_platforms, load_platform_config + from release_record import pack_for + + page = _which_pack() + for key in list_registered_platforms("platforms", include_archived=True): + if pack_for(key, RECORD) is not None: + continue + label = load_platform_config(key, "platforms").get("platform", key) + with self.subTest(platform=key): + self.assertNotIn(f"| [{label}](", page) + class SlicesLeaveWithTheirRelease(unittest.TestCase): """Parts named `.zip.001` were cut by v2026.09.04, deleted since. A page diff --git a/wiki/adding-a-platform.md b/wiki/adding-a-platform.md index ee7ed3c8..13b3b609 100644 --- a/wiki/adding-a-platform.md +++ b/wiki/adding-a-platform.md @@ -250,7 +250,10 @@ systems: `base_destination` is what the pack prefixes every entry with. Leave it empty when the upstream destinations already carry their own root, as RetroDECK does -with `bios/` and `roms/`. +with `bios/` and `roms/`. Core files then go under the root most destinations +share, or under `core_destination` when the list names other folders too: +AmberELEC and EmuELEC declare `neogeo/` and `ports/` beside `bios/`, and set +`core_destination: bios`. ### Launchers @@ -411,7 +414,7 @@ to use: | Mode | Behavior | Example platforms | |------|----------|-------------------| | `existence` | File must exist, no hash check | RetroArch, Lakka, RetroPie | -| `md5` | MD5 must match the declared hash | Batocera, Recalbox, RetroBat, EmuDeck, RetroDECK, RomM, ROCKNIX, MiSTer FPGA | +| `md5` | MD5 must match the declared hash | Batocera, Recalbox, RetroBat, EmuDeck, RetroDECK, RomM, ROCKNIX, KNULLI, AmberELEC, EmuELEC, MiSTer FPGA | | `sha1` | SHA1 must match | BizHawk | Inheriting platforms take the parent's mode: Lakka and RetroPie do not declare diff --git a/wiki/faq.md b/wiki/faq.md index eba96b3d..2086c43c 100644 --- a/wiki/faq.md +++ b/wiki/faq.md @@ -212,7 +212,8 @@ checks its DSP ROMs (`dsp_rom.bin` and `dsp_coef.bin`, hashed byte-swapped). `IPL.bin` is not one of them: Dolphin loads it without a hash check. Verification uses whichever one the platform itself uses: MD5 for Batocera, -RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX and MiSTer FPGA, SHA1 for +RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX, KNULLI, AmberELEC, EmuELEC +and MiSTer FPGA, SHA1 for BizHawk, and nothing at all for RetroArch, Lakka and RetroPie, which only check that the file exists. diff --git a/wiki/first-install.md b/wiki/first-install.md index 98b53f88..fc421357 100644 --- a/wiki/first-install.md +++ b/wiki/first-install.md @@ -26,7 +26,7 @@ touched. ## Step 1. Run the command On Linux, macOS, a Steam Deck, or a handheld running Batocera, Recalbox, -KNULLI, ROCKNIX or Lakka: +KNULLI, ROCKNIX, AmberELEC, EmuELEC or Lakka: ```bash curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh diff --git a/wiki/getting-started.md b/wiki/getting-started.md index cb2ee08a..8d808462 100644 --- a/wiki/getting-started.md +++ b/wiki/getting-started.md @@ -198,6 +198,22 @@ others) whose files only the RetroPie pack carries, laid out for Accessible via SSH or Samba, like Lakka. +### KNULLI + +``` +/userdata/bios/ +``` + +### AmberELEC and EmuELEC + +``` +/storage/roms/ +``` + +Their BIOS lists name files under `/storage/roms`: `bios/` for most, and +`neogeo/` (and `ports/` on EmuELEC) for the rest. The pack holds those +folders, so it is extracted into `/storage/roms/` itself. + ### MiSTer FPGA ``` @@ -247,7 +263,8 @@ dump shows up. The installer's `--check` hashes every file against the list on every platform, so it catches a wrong revision even where the frontend would not: RetroArch, Lakka and RetroPie only check that a file exists, while Batocera, -RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX and MiSTer FPGA compare +RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX, KNULLI, AmberELEC, +EmuELEC and MiSTer FPGA compare MD5 and BizHawk compares SHA1. For a single system, or the files a single emulator core reads (`--list-systems` diff --git a/wiki/installer.md b/wiki/installer.md index 57dae073..084e2c69 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -290,7 +290,9 @@ wins, since a machine is only one of them. | Evidence | Platform | BIOS directory | |----------|----------|----------------| | `/etc/os-release` `OS_NAME=ROCKNIX` (or `ID=rocknix` on images before 2025) | ROCKNIX | `/storage/roms/bios` | -| `/etc/os-release` `OS_NAME=knulli` or `/usr/share/knulli/knulli.version` | Batocera (Knulli) | `/userdata/bios` | +| `/etc/os-release` `OS_NAME=knulli` or `/usr/share/knulli/knulli.version` | KNULLI | `/userdata/bios` | +| `/etc/os-release` `NAME=AmberELEC` | AmberELEC | `/storage/roms` (its list names `bios/` and `neogeo/`) | +| `/etc/os-release` `ID=emuelec` | EmuELEC | `/storage/roms` (its list names `bios/`, `neogeo/` and `ports/`) | | `/etc/os-release` `ID=lakka` | Lakka | `/storage/system` | | `/usr/share/batocera/batocera.version` | Batocera | `/userdata/bios` | | `/recalbox/recalbox.version` or `/usr/bin/recalbox-settings` | Recalbox | `/recalbox/share/bios` | @@ -302,9 +304,9 @@ wins, since a machine is only one of them. Batocera, Recalbox, ROCKNIX and Lakka also name the board they were built for, and that board narrows the install unless `--target` or `--every-board` says otherwise (see the options above). A board with no core list of its own is -named and gets the files of every board. Knulli names its board in -`/usr/share/knulli/knulli.arch` but builds its own set of cores, so Batocera's -per-board lists are not applied to it and the run says so. +named and gets the files of every board. KNULLI names its board in +`/usr/share/knulli/knulli.arch` but publishes no core list per board, so the +run says so and installs the files of every board. Some platforms keep their BIOS in a fixed directory, and that directory is the evidence. They are looked for after the configurations below, for a platform @@ -388,7 +390,8 @@ When a platform named with `--platform` is not found on the machine, the destination falls back to the directory its detection looks for: `/userdata/bios` for Batocera, `/recalbox/share/bios` for Recalbox, `/storage/system` for Lakka, `~/retrodeck` for RetroDECK, `~/Emulation/bios` -for EmuDeck (`C:\Emulation\bios` on Windows), `/storage/roms/bios` for ROCKNIX, `/media/fat/games` for MiSTer, +for EmuDeck (`C:\Emulation\bios` on Windows), `/storage/roms/bios` for ROCKNIX, +`/userdata/bios` for KNULLI, `/storage/roms` for AmberELEC and EmuELEC, `/media/fat/games` for MiSTer, `~/RetroPie/BIOS` for RetroPie, `/romm/library/bios` for RomM, `C:\RetroBat\bios` for RetroBat, and RetroArch's own default system directory for RetroArch. BizHawk has no such folder: it reads `Firmware` diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index b3a50f9c..458c3a8c 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -45,7 +45,7 @@ Every module in `tests/`, by the area it guards. | Hardware targets and systems | synthetic + target files | `test_extract_targets`, `test_lakka_targets`, `test_libretro_slugs`, `test_system_ids`, `test_system_selection`, `test_target_names`, `test_target_scrapers`, `test_target_systems`, `test_target_verify` | | Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_selection` | | Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` | -| Scrapers, upstream and provenance | inline sources, synthetic | `test_check_freshness`, `test_dedup`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_profile_sync`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_scraper_contract`, `test_upstream`, `test_upstream_index` | +| Scrapers, upstream and provenance | inline sources, synthetic | `test_check_freshness`, `test_dedup`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_profile_sync`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_scraper_contract`, `test_upstream`, `test_upstream_index` | | Site, docs and repository contracts | rendered pages, sources | `test_contributing_text`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_doc_pages`, `test_doc_surfaces`, `test_optional_dependencies`, `test_prose_style`, `test_site_availability`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_platform_hashes`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_write`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` | ## Test architecture diff --git a/wiki/tools.md b/wiki/tools.md index 00bfe665..620a37f5 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -143,6 +143,9 @@ Verification modes per platform: | RetroDECK | md5 | MD5 per file via component manifests | | RomM | md5 | size + any hash (MD5/SHA1/CRC32), no ZIP inspection | | ROCKNIX | md5 | MD5 hash match, same shape as Batocera | +| KNULLI | md5 | Batocera's `checkBios` in `knulli-systems`, `checkInsideZip` and emulator/core gates included | +| AmberELEC | md5 | MD5 of each file under `/storage/roms`, `checkInsideZip` for `zippedFile` entries | +| EmuELEC | md5 | MD5 of each file under `/storage/roms`; `zippedFile` is never read | | MiSTer FPGA | md5 | MD5 of the file at its destination, no ZIP inspection | | BizHawk | sha1 | SHA1 per firmware from `FirmwareDatabase.cs` | @@ -846,6 +849,9 @@ stand alone. | `retrodeck_scraper` | component manifests | JSON per component | | `romm_scraper` | known_bios_files.json | JSON | | `rocknix_scraper` | rocknix-systems script | Python dict | +| `knulli_scraper` | knulli-systems script, es_systems.yml and configgen-defaults.yml (cores) | Python dict, YAML | +| `amberelec_scraper` | batocera-systems script at the newest prerelease's commit, es_systems.cfg (cores) | Python dict, XML | +| `emuelec_scraper` | batocera-systems script, es_systems.json (cores) | Python dict, JSON | | `retropie_scraper` | RetroPie-Setup scriptmodules (`rp_module_id`, `rp_module_help`) and `retropie_packages.sh` (version) | Bash | | `misterfpga_scraper` | BiosDB_MiSTer bios_db.json.zip | JSON in ZIP | | `coreinfo_scraper` | .info files from libretro-core-info | INI-like | @@ -991,6 +997,9 @@ Located in `scripts/exporter/`. Each inherits `BaseExporter`, defined in | `lakka_exporter` | the same `System.dat`, which Lakka reads unchanged | | `batocera_exporter` | the systems block of the `batocera-systems` script, patched entry by entry | | `rocknix_exporter` | the systems mapping of the `rocknix-systems` script, patched the same way | +| `knulli_exporter` | the systems mapping of the `knulli-systems` script, patched the same way | +| `amberelec_exporter` | AmberELEC's `batocera-systems`, paths from `/storage/roms`, additions without a runner gate | +| `emuelec_exporter` | EmuELEC's `batocera-systems`, as AmberELEC's | | `retrobat_exporter` | `batocera-systems.json` | | `recalbox_exporter` | `es_bios.xml`, valid against `es_bios.xsd` | | `emudeck_exporter` | the MD5 lists of the `checkBIOS.sh` functions, patched in place | diff --git a/wiki/troubleshooting.md b/wiki/troubleshooting.md index ce099d3b..7b8a476f 100644 --- a/wiki/troubleshooting.md +++ b/wiki/troubleshooting.md @@ -62,6 +62,8 @@ Each platform expects BIOS files in a specific base directory: - Recalbox: `/recalbox/share/bios/` - RetroPie: `~/RetroPie/BIOS/` - ROCKNIX: `/storage/roms/bios/` +- KNULLI: `/userdata/bios/` +- AmberELEC and EmuELEC: `/storage/roms/` (the pack holds `bios/` and the other folders their lists name) - MiSTer FPGA: `/media/fat/games/` - BizHawk: `Firmware/` inside the BizHawk directory - EmuDeck: `~/Emulation/bios/` diff --git a/wiki/verification-modes.md b/wiki/verification-modes.md index efaee1fe..bdc76f95 100644 --- a/wiki/verification-modes.md +++ b/wiki/verification-modes.md @@ -28,7 +28,7 @@ Lakka and RetroPie inherit this behavior through platform config inheritance ## MD5 mode -**Platforms**: Batocera, RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX, MiSTer FPGA +**Platforms**: Batocera, RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX, KNULLI, AmberELEC, EmuELEC, MiSTer FPGA All MD5-mode platforms compute a hash of the file and compare it against an expected value. The details vary by platform. @@ -137,6 +137,28 @@ shape: `md5sum()` on the file, `checkInsideZip()` for archive entries, and an platform declares is mandatory in its own data, so the platform YAML marks all 38 files `required: true` and none of them use `zippedFile`. +### KNULLI, AmberELEC and EmuELEC verification + +**Source**: `knulli-systems` (KNULLI) and `batocera-systems` (AmberELEC, +EmuELEC), function `checkBios` + +All three keep Batocera's `systems` dict and its checker, run by their +EmulationStation's BIOS screen. + +- **KNULLI** runs Batocera's `checkBios` on files under `/userdata`: `md5sum()`, + `checkInsideZip()` for `zippedFile` entries, and the `emulator`/`core` gates + read from `es_systems.cfg`. +- **AmberELEC** runs the same checker on files under `/storage/roms`. Its list + sets no gate, and names `neogeo/neogeo.zip` and `neogeo/aes.zip` beside + `bios/`, so the pack extracts into `/storage/roms`. +- **EmuELEC** hashes the whole file under `/storage/roms` and never reads + `zippedFile`: an entry that names a member inside an archive compares the + archive's MD5 with the member's and reports `UNTESTED` whatever the archive + holds. Its list also names game data under `ports/`. + +A key written twice in the dict (`naomi` and `Dinothawr` in EmuELEC, `msx1` +and `msx2+` in KNULLI) keeps its later value, as when Python runs the script. + ### MiSTer FPGA verification **Source**: `Downloader_MiSTer`, `jobs/process_db_index_worker.py`