diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 183deaba..7a7a5de7 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -43,6 +43,7 @@ on: - "scripts/restore_large_files.py" - "scripts/safeparse.py" - "scripts/sect233r1.py" + - "scripts/sitediagrams.py" - "scripts/siteexports.py" - "scripts/siterender.py" - "scripts/sitesources.py" diff --git a/docs_assets/images-needed.md b/docs_assets/images-needed.md new file mode 100644 index 00000000..4bc29a92 --- /dev/null +++ b/docs_assets/images-needed.md @@ -0,0 +1,71 @@ +# Images that need a real device + +These pictures show a real screen, so they are captured on a real install and +never drawn as a mock-up. Each entry says what the capture must show, where it +goes, and the alt text it carries. Folder layouts that follow from data are +drawn by `scripts/sitediagrams.py` instead (the MinUI card tree is +``). + +Captures: PNG or WebP, 1200 px wide at most, cropped tight, under 150 KB, +personal paths and user names removed. Menu labels are checked against the +current release of the software before the capture is used. Put each file in +`docs_assets/screens/` and note the software version in the commit message. + +## RetroArch System/BIOS directory + +Settings, Directory in RetroArch (Ozone menu), with the System/BIOS row +highlighted and its path visible. For the RetroArch pages on Windows, macOS, +Linux and Android. + +**Alt text**: RetroArch Settings, Directory screen, with the System/BIOS line highlighted showing the folder path. + +## RetroArch firmware check + +Main Menu, Information, Core Information after loading a PlayStation core, with +the firmware lines reading Present. For RetroArch and Lakka pages, "Done when". + +**Alt text**: RetroArch Core Information for a PlayStation core, each firmware line marked Present. + +## Steam Deck Desktop Mode and Konsole + +Two panels: the Steam Deck power menu with Switch to Desktop selected, then the +desktop with Konsole open and the install command pasted. For the EmuDeck and +RetroDECK pages. + +**Alt text**: Steam Deck power menu with Switch to Desktop selected, then the Konsole terminal open with the install command pasted. + +## Installer run, annotated + +A real installer run in a terminal with three marks: the detected BIOS folder +line, the confirmation prompt, and the Done line. For First install and every +setup page. + +**Alt text**: Installer output in a terminal, with marks on the detected folder, the install choice and the Done line. + +## Batocera network share from Windows + +Windows File Explorer with `\\BATOCERA\share\bios` in the address bar and the +BIOS files listed. For the Batocera and KNULLI pages. + +**Alt text**: Windows File Explorer showing the Batocera bios folder opened over the network. + +## Batocera missing BIOS screen + +Batocera's main menu path to the missing BIOS check, and the result screen with +no missing file for the consoles in use. For the Batocera page, "Done when". + +**Alt text**: Batocera missing BIOS check listing no missing files. + +## Recalbox BIOS manager + +Recalbox's BIOS checking screen with green, yellow and red entries visible. For +the Recalbox page. + +**Alt text**: Recalbox BIOS manager listing files marked present, present with another version, and missing. + +## A multi-part pack extracted + +A file manager showing two downloaded parts and the single folder both were +extracted into. For Get your files. + +**Alt text**: Two downloaded pack parts and the one folder both were extracted into. diff --git a/docs_assets/logos/amberelec.png b/docs_assets/logos/amberelec.png new file mode 100644 index 00000000..ddc713f4 Binary files /dev/null and b/docs_assets/logos/amberelec.png differ diff --git a/docs_assets/logos/batocera.svg b/docs_assets/logos/batocera.svg new file mode 100644 index 00000000..de36c9b4 --- /dev/null +++ b/docs_assets/logos/batocera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs_assets/logos/bizhawk.png b/docs_assets/logos/bizhawk.png new file mode 100644 index 00000000..511597b2 Binary files /dev/null and b/docs_assets/logos/bizhawk.png differ diff --git a/docs_assets/logos/emudeck.png b/docs_assets/logos/emudeck.png new file mode 100644 index 00000000..408a9415 Binary files /dev/null and b/docs_assets/logos/emudeck.png differ diff --git a/docs_assets/logos/emuelec.png b/docs_assets/logos/emuelec.png new file mode 100644 index 00000000..1c432fc5 Binary files /dev/null and b/docs_assets/logos/emuelec.png differ diff --git a/docs_assets/logos/knulli.png b/docs_assets/logos/knulli.png new file mode 100644 index 00000000..fc8885f1 Binary files /dev/null and b/docs_assets/logos/knulli.png differ diff --git a/docs_assets/logos/lakka.svg b/docs_assets/logos/lakka.svg new file mode 100644 index 00000000..fc89ee3c --- /dev/null +++ b/docs_assets/logos/lakka.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs_assets/logos/minui.png b/docs_assets/logos/minui.png new file mode 100644 index 00000000..cc59063a Binary files /dev/null and b/docs_assets/logos/minui.png differ diff --git a/docs_assets/logos/misterfpga.png b/docs_assets/logos/misterfpga.png new file mode 100644 index 00000000..35821644 Binary files /dev/null and b/docs_assets/logos/misterfpga.png differ diff --git a/docs_assets/logos/nextui.png b/docs_assets/logos/nextui.png new file mode 100644 index 00000000..dd758331 Binary files /dev/null and b/docs_assets/logos/nextui.png differ diff --git a/docs_assets/logos/recalbox.svg b/docs_assets/logos/recalbox.svg new file mode 100644 index 00000000..b96d2dd1 --- /dev/null +++ b/docs_assets/logos/recalbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs_assets/logos/retroarch.svg b/docs_assets/logos/retroarch.svg new file mode 100644 index 00000000..a35b434a --- /dev/null +++ b/docs_assets/logos/retroarch.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/docs_assets/logos/retrobat.png b/docs_assets/logos/retrobat.png new file mode 100644 index 00000000..2b4395ac Binary files /dev/null and b/docs_assets/logos/retrobat.png differ diff --git a/docs_assets/logos/retrodeck.svg b/docs_assets/logos/retrodeck.svg new file mode 100644 index 00000000..185f9d63 --- /dev/null +++ b/docs_assets/logos/retrodeck.svg @@ -0,0 +1,53 @@ + + diff --git a/docs_assets/logos/retropie.png b/docs_assets/logos/retropie.png new file mode 100644 index 00000000..e3885625 Binary files /dev/null and b/docs_assets/logos/retropie.png differ diff --git a/docs_assets/logos/rocknix.png b/docs_assets/logos/rocknix.png new file mode 100644 index 00000000..8158615d Binary files /dev/null and b/docs_assets/logos/rocknix.png differ diff --git a/docs_assets/logos/romm.png b/docs_assets/logos/romm.png new file mode 100644 index 00000000..71d38413 Binary files /dev/null and b/docs_assets/logos/romm.png differ diff --git a/docs_assets/logos/sources.json b/docs_assets/logos/sources.json new file mode 100644 index 00000000..590c551d --- /dev/null +++ b/docs_assets/logos/sources.json @@ -0,0 +1,124 @@ +{ + "note": "Each platform's own logo, owned by its project and shown only to name that platform. Copied once by scripts/fetch_site_images.py.", + "images": [ + { + "file": "amberelec.png", + "platform": "amberelec", + "source": "https://avatars.githubusercontent.com/u/102820909", + "owner": "AmberELEC", + "licence": "GitHub organisation avatar of AmberELEC" + }, + { + "file": "batocera.svg", + "platform": "batocera", + "source": "https://raw.githubusercontent.com/batocera-linux/batocera-emulationstation/master/resources/splash_batocera.svg", + "owner": "Batocera", + "licence": "repository licence MIT" + }, + { + "file": "bizhawk.png", + "platform": "bizhawk", + "source": "https://avatars.githubusercontent.com/u/11743303", + "owner": "TASVideos", + "licence": "GitHub organisation avatar of TASEmulators" + }, + { + "file": "emudeck.png", + "platform": "emudeck", + "source": "https://raw.githubusercontent.com/dragoonDorise/EmuDeck/main/icons/EmuDeck.png", + "owner": "EmuDeck", + "licence": "repository licence GPL-3.0" + }, + { + "file": "emuelec.png", + "platform": "emuelec", + "source": "https://avatars.githubusercontent.com/u/57574835", + "owner": "EmuELEC", + "licence": "GitHub organisation avatar of EmuELEC" + }, + { + "file": "knulli.png", + "platform": "knulli", + "source": "https://avatars.githubusercontent.com/u/166268572", + "owner": "KNULLI", + "licence": "GitHub organisation avatar of knulli-cfw" + }, + { + "file": "lakka.svg", + "platform": "lakka", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/src/xmb/flatui/lakka.svg", + "owner": "Lakka (libretro)", + "licence": "file from libretro/retroarch-assets, CC-BY-4.0" + }, + { + "file": "minui.png", + "platform": "minui", + "source": "https://avatars.githubusercontent.com/u/225589", + "owner": "Shaun Inman", + "licence": "GitHub avatar of shauninman, the author of MinUI" + }, + { + "file": "misterfpga.png", + "platform": "misterfpga", + "source": "https://avatars.githubusercontent.com/u/29345272", + "owner": "MiSTer FPGA", + "licence": "GitHub organisation avatar of MiSTer-devel" + }, + { + "file": "nextui.png", + "platform": "nextui", + "source": "https://avatars.githubusercontent.com/u/202386615", + "owner": "LoveRetro", + "licence": "GitHub organisation avatar of LoveRetro" + }, + { + "file": "recalbox.svg", + "platform": "recalbox", + "source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/main/svg/recalbox.svg", + "owner": "Recalbox", + "licence": "file from homarr-labs/dashboard-icons, Apache-2.0" + }, + { + "file": "retroarch.svg", + "platform": "retroarch", + "source": "https://raw.githubusercontent.com/libretro/RetroArch/master/media/retroarch-vector_invader-only.svg", + "owner": "libretro", + "licence": "repository licence GPL-3.0" + }, + { + "file": "retrobat.png", + "platform": "retrobat", + "source": "https://raw.githubusercontent.com/RetroBat-Official/retrobat/main/system/resources/retrobat_logo_notext.png", + "owner": "RetroBat", + "licence": "repository states no licence" + }, + { + "file": "retrodeck.svg", + "platform": "retrodeck", + "source": "https://raw.githubusercontent.com/RetroDECK/RetroDECK/main/res/icon.svg", + "owner": "RetroDECK", + "licence": "repository licence GPL-3.0" + }, + { + "file": "retropie.png", + "platform": "retropie", + "source": "https://avatars.githubusercontent.com/u/11378204", + "owner": "RetroPie", + "licence": "GitHub organisation avatar of RetroPie" + }, + { + "file": "rocknix.png", + "platform": "rocknix", + "source": "https://avatars.githubusercontent.com/u/163176886", + "owner": "ROCKNIX", + "licence": "GitHub organisation avatar of ROCKNIX" + }, + { + "file": "romm.png", + "platform": "romm", + "source": "https://avatars.githubusercontent.com/u/164058018", + "owner": "RomM", + "licence": "GitHub organisation avatar of rommapp" + } + ] +} diff --git a/docs_assets/og/contribute.png b/docs_assets/og/contribute.png new file mode 100644 index 00000000..88a40b47 Binary files /dev/null and b/docs_assets/og/contribute.png differ diff --git a/docs_assets/og/explore.png b/docs_assets/og/explore.png new file mode 100644 index 00000000..7e4c409d Binary files /dev/null and b/docs_assets/og/explore.png differ diff --git a/docs_assets/og/home.png b/docs_assets/og/home.png new file mode 100644 index 00000000..3d2b7e17 Binary files /dev/null and b/docs_assets/og/home.png differ diff --git a/docs_assets/og/reference.png b/docs_assets/og/reference.png new file mode 100644 index 00000000..fc688a7a Binary files /dev/null and b/docs_assets/og/reference.png differ diff --git a/docs_assets/og/setup.png b/docs_assets/og/setup.png new file mode 100644 index 00000000..84bd6f81 Binary files /dev/null and b/docs_assets/og/setup.png differ diff --git a/docs_assets/og/start.png b/docs_assets/og/start.png new file mode 100644 index 00000000..15245230 Binary files /dev/null and b/docs_assets/og/start.png differ diff --git a/docs_assets/overrides/404.html b/docs_assets/overrides/404.html new file mode 100644 index 00000000..59ad2c3d --- /dev/null +++ b/docs_assets/overrides/404.html @@ -0,0 +1,37 @@ +{% extends "main.html" %} + +{# + The page a stale link lands on. Pages have moved before (file cards + refiled between manufacturers), so it offers the search, the two pages a + visitor most often wants, and the list a moved emulator, system or + platform page belongs to. +#} +{% block content %} +

Page not found

+

This address has no page. It may have moved since the link was written.

+

+ + + +{% endblock %} diff --git a/docs_assets/overrides/main.html b/docs_assets/overrides/main.html index 4348d78c..893d3b1e 100644 --- a/docs_assets/overrides/main.html +++ b/docs_assets/overrides/main.html @@ -9,25 +9,43 @@ taking on two image dependencies. The links get shared on Discord, Reddit and forums, where a bare URL with no preview is the difference between a card and a grey rectangle. + + One sharing card per section (docs_assets/og, made by + scripts/make_og_cards.py). The section is the page's level when the + generator gives one, otherwise it follows from the URL. #} {% block extrahead %} {% set page_title = page.meta.title if page and page.meta and page.meta.title else (page.title if page and page.title else config.site_name) %} {% set page_description = page.meta.description if page and page.meta and page.meta.description else config.site_description %} {% set page_url = page.canonical_url if page and page.canonical_url else config.site_url %} - {% set preview_image = config.site_url ~ 'assets/images/logo.png' if config.site_url else '' %} + {% set url = page.url if page and page.url else '' %} + {% set level = page.meta.level if page and page.meta and page.meta.level else '' %} + {% if not url %}{% set section = 'home' %} + {% elif level in ['start', 'setup', 'explore', 'reference', 'contribute'] %}{% set section = level %} + {% elif url.startswith('which-pack') or url.startswith('wiki/first-install') or url.startswith('wiki/faq') or url.startswith('wiki/getting-started') %}{% set section = 'start' %} + {% elif url.startswith('platforms') %}{% set section = 'setup' %} + {% elif url.startswith('systems') or url.startswith('emulators') or url.startswith('files') %}{% set section = 'explore' %} + {% elif url.startswith('contributing') or url.startswith('wiki/adding') or url.startswith('wiki/profiling') or url.startswith('wiki/testing') or url.startswith('wiki/release') %}{% set section = 'contribute' %} + {% else %}{% set section = 'reference' %}{% endif %} + {% set preview_image = config.site_url ~ 'assets/og/' ~ section ~ '.png' if config.site_url else '' %} - + {% if page_url %}{% endif %} - {% if preview_image %}{% endif %} + {% if preview_image %} + + + + + {% endif %} {# The sitemap, named from every page: a project site under /retrobios/ cannot serve the host-root robots.txt that would announce it. #} {% if config.site_url %}{% endif %} - + {% if preview_image %}{% endif %} diff --git a/docs_assets/system-icons/APF - Imagination Machine.png b/docs_assets/system-icons/APF - Imagination Machine.png new file mode 100644 index 00000000..555187b6 Binary files /dev/null and b/docs_assets/system-icons/APF - Imagination Machine.png differ diff --git a/docs_assets/system-icons/Acorn - Archimedes.png b/docs_assets/system-icons/Acorn - Archimedes.png new file mode 100644 index 00000000..2095ae00 Binary files /dev/null and b/docs_assets/system-icons/Acorn - Archimedes.png differ diff --git a/docs_assets/system-icons/Acorn - BBC Micro.png b/docs_assets/system-icons/Acorn - BBC Micro.png new file mode 100644 index 00000000..884d75b4 Binary files /dev/null and b/docs_assets/system-icons/Acorn - BBC Micro.png differ diff --git a/docs_assets/system-icons/Amstrad - CPC.png b/docs_assets/system-icons/Amstrad - CPC.png new file mode 100644 index 00000000..7f662b08 Binary files /dev/null and b/docs_assets/system-icons/Amstrad - CPC.png differ diff --git a/docs_assets/system-icons/Apple - Macintosh.png b/docs_assets/system-icons/Apple - Macintosh.png new file mode 100644 index 00000000..5ebf86fd Binary files /dev/null and b/docs_assets/system-icons/Apple - Macintosh.png differ diff --git a/docs_assets/system-icons/Aquaplus - PIECE.png b/docs_assets/system-icons/Aquaplus - PIECE.png new file mode 100644 index 00000000..8175bd5d Binary files /dev/null and b/docs_assets/system-icons/Aquaplus - PIECE.png differ diff --git a/docs_assets/system-icons/Atari - 2600.png b/docs_assets/system-icons/Atari - 2600.png new file mode 100644 index 00000000..7371a2ad Binary files /dev/null and b/docs_assets/system-icons/Atari - 2600.png differ diff --git a/docs_assets/system-icons/Atari - 5200.png b/docs_assets/system-icons/Atari - 5200.png new file mode 100644 index 00000000..ab577ecd Binary files /dev/null and b/docs_assets/system-icons/Atari - 5200.png differ diff --git a/docs_assets/system-icons/Atari - 7800.png b/docs_assets/system-icons/Atari - 7800.png new file mode 100644 index 00000000..14e55633 Binary files /dev/null and b/docs_assets/system-icons/Atari - 7800.png differ diff --git a/docs_assets/system-icons/Atari - Jaguar.png b/docs_assets/system-icons/Atari - Jaguar.png new file mode 100644 index 00000000..a97bc861 Binary files /dev/null and b/docs_assets/system-icons/Atari - Jaguar.png differ diff --git a/docs_assets/system-icons/Atari - Lynx.png b/docs_assets/system-icons/Atari - Lynx.png new file mode 100644 index 00000000..e41d17a1 Binary files /dev/null and b/docs_assets/system-icons/Atari - Lynx.png differ diff --git a/docs_assets/system-icons/Atari - ST.png b/docs_assets/system-icons/Atari - ST.png new file mode 100644 index 00000000..c1fc39ef Binary files /dev/null and b/docs_assets/system-icons/Atari - ST.png differ diff --git a/docs_assets/system-icons/Atari - XEGS.png b/docs_assets/system-icons/Atari - XEGS.png new file mode 100644 index 00000000..13142d95 Binary files /dev/null and b/docs_assets/system-icons/Atari - XEGS.png differ diff --git a/docs_assets/system-icons/Bally - Astrocade.png b/docs_assets/system-icons/Bally - Astrocade.png new file mode 100644 index 00000000..3cdfe989 Binary files /dev/null and b/docs_assets/system-icons/Bally - Astrocade.png differ diff --git a/docs_assets/system-icons/Bandai - WonderSwan.png b/docs_assets/system-icons/Bandai - WonderSwan.png new file mode 100644 index 00000000..1a6466c4 Binary files /dev/null and b/docs_assets/system-icons/Bandai - WonderSwan.png differ diff --git a/docs_assets/system-icons/Bit Corporation - Gamate.png b/docs_assets/system-icons/Bit Corporation - Gamate.png new file mode 100644 index 00000000..e2e8d9b8 Binary files /dev/null and b/docs_assets/system-icons/Bit Corporation - Gamate.png differ diff --git a/docs_assets/system-icons/Casio - Loopy.png b/docs_assets/system-icons/Casio - Loopy.png new file mode 100644 index 00000000..041e2dc6 Binary files /dev/null and b/docs_assets/system-icons/Casio - Loopy.png differ diff --git a/docs_assets/system-icons/Casio - PV-2000.png b/docs_assets/system-icons/Casio - PV-2000.png new file mode 100644 index 00000000..eb77b978 Binary files /dev/null and b/docs_assets/system-icons/Casio - PV-2000.png differ diff --git a/docs_assets/system-icons/Coleco - ColecoVision.png b/docs_assets/system-icons/Coleco - ColecoVision.png new file mode 100644 index 00000000..d09b36be Binary files /dev/null and b/docs_assets/system-icons/Coleco - ColecoVision.png differ diff --git a/docs_assets/system-icons/Commodore - Amiga.png b/docs_assets/system-icons/Commodore - Amiga.png new file mode 100644 index 00000000..c437d3b5 Binary files /dev/null and b/docs_assets/system-icons/Commodore - Amiga.png differ diff --git a/docs_assets/system-icons/Commodore - VIC-20.png b/docs_assets/system-icons/Commodore - VIC-20.png new file mode 100644 index 00000000..21b31423 Binary files /dev/null and b/docs_assets/system-icons/Commodore - VIC-20.png differ diff --git a/docs_assets/system-icons/Entex - Adventure Vision.png b/docs_assets/system-icons/Entex - Adventure Vision.png new file mode 100644 index 00000000..667ef134 Binary files /dev/null and b/docs_assets/system-icons/Entex - Adventure Vision.png differ diff --git a/docs_assets/system-icons/Epoch - Super Cassette Vision.png b/docs_assets/system-icons/Epoch - Super Cassette Vision.png new file mode 100644 index 00000000..c1587518 Binary files /dev/null and b/docs_assets/system-icons/Epoch - Super Cassette Vision.png differ diff --git a/docs_assets/system-icons/Fairchild - Channel F.png b/docs_assets/system-icons/Fairchild - Channel F.png new file mode 100644 index 00000000..cba04c4d Binary files /dev/null and b/docs_assets/system-icons/Fairchild - Channel F.png differ diff --git a/docs_assets/system-icons/Fujitsu - FM Towns.png b/docs_assets/system-icons/Fujitsu - FM Towns.png new file mode 100644 index 00000000..69ab318b Binary files /dev/null and b/docs_assets/system-icons/Fujitsu - FM Towns.png differ diff --git a/docs_assets/system-icons/Fujitsu - FM-7.png b/docs_assets/system-icons/Fujitsu - FM-7.png new file mode 100644 index 00000000..455a850d Binary files /dev/null and b/docs_assets/system-icons/Fujitsu - FM-7.png differ diff --git a/docs_assets/system-icons/GCE - Vectrex.png b/docs_assets/system-icons/GCE - Vectrex.png new file mode 100644 index 00000000..56b2b9da Binary files /dev/null and b/docs_assets/system-icons/GCE - Vectrex.png differ diff --git a/docs_assets/system-icons/GamePark - GP32.png b/docs_assets/system-icons/GamePark - GP32.png new file mode 100644 index 00000000..218bf89c Binary files /dev/null and b/docs_assets/system-icons/GamePark - GP32.png differ diff --git a/docs_assets/system-icons/Hartung - Game Master.png b/docs_assets/system-icons/Hartung - Game Master.png new file mode 100644 index 00000000..95546412 Binary files /dev/null and b/docs_assets/system-icons/Hartung - Game Master.png differ diff --git a/docs_assets/system-icons/Infocom - Z-Machine.png b/docs_assets/system-icons/Infocom - Z-Machine.png new file mode 100644 index 00000000..e3b45c71 Binary files /dev/null and b/docs_assets/system-icons/Infocom - Z-Machine.png differ diff --git a/docs_assets/system-icons/Magnavox - Odyssey2.png b/docs_assets/system-icons/Magnavox - Odyssey2.png new file mode 100644 index 00000000..550cb1cd Binary files /dev/null and b/docs_assets/system-icons/Magnavox - Odyssey2.png differ diff --git a/docs_assets/system-icons/Mattel - Intellivision.png b/docs_assets/system-icons/Mattel - Intellivision.png new file mode 100644 index 00000000..8f0b3c11 Binary files /dev/null and b/docs_assets/system-icons/Mattel - Intellivision.png differ diff --git a/docs_assets/system-icons/Microsoft - MSX.png b/docs_assets/system-icons/Microsoft - MSX.png new file mode 100644 index 00000000..02767eb8 Binary files /dev/null and b/docs_assets/system-icons/Microsoft - MSX.png differ diff --git a/docs_assets/system-icons/Microsoft - Xbox.png b/docs_assets/system-icons/Microsoft - Xbox.png new file mode 100644 index 00000000..8a43ff96 Binary files /dev/null and b/docs_assets/system-icons/Microsoft - Xbox.png differ diff --git a/docs_assets/system-icons/NEC - PC-88.png b/docs_assets/system-icons/NEC - PC-88.png new file mode 100644 index 00000000..3d25da4d Binary files /dev/null and b/docs_assets/system-icons/NEC - PC-88.png differ diff --git a/docs_assets/system-icons/NEC - PC-98.png b/docs_assets/system-icons/NEC - PC-98.png new file mode 100644 index 00000000..baf2062f Binary files /dev/null and b/docs_assets/system-icons/NEC - PC-98.png differ diff --git a/docs_assets/system-icons/NEC - PC-FX.png b/docs_assets/system-icons/NEC - PC-FX.png new file mode 100644 index 00000000..2669847e Binary files /dev/null and b/docs_assets/system-icons/NEC - PC-FX.png differ diff --git a/docs_assets/system-icons/Nintendo - Game Boy Advance.png b/docs_assets/system-icons/Nintendo - Game Boy Advance.png new file mode 100644 index 00000000..a475840c Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Game Boy Advance.png differ diff --git a/docs_assets/system-icons/Nintendo - Game Boy Color.png b/docs_assets/system-icons/Nintendo - Game Boy Color.png new file mode 100644 index 00000000..aa33b387 Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Game Boy Color.png differ diff --git a/docs_assets/system-icons/Nintendo - Game Boy.png b/docs_assets/system-icons/Nintendo - Game Boy.png new file mode 100644 index 00000000..3a7277b8 Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Game Boy.png differ diff --git a/docs_assets/system-icons/Nintendo - GameCube.png b/docs_assets/system-icons/Nintendo - GameCube.png new file mode 100644 index 00000000..0ff194ea Binary files /dev/null and b/docs_assets/system-icons/Nintendo - GameCube.png differ diff --git a/docs_assets/system-icons/Nintendo - Nintendo 64.png b/docs_assets/system-icons/Nintendo - Nintendo 64.png new file mode 100644 index 00000000..472e841d Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Nintendo 64.png differ diff --git a/docs_assets/system-icons/Nintendo - Nintendo 64DD.png b/docs_assets/system-icons/Nintendo - Nintendo 64DD.png new file mode 100644 index 00000000..15e472ce Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Nintendo 64DD.png differ diff --git a/docs_assets/system-icons/Nintendo - Nintendo DS.png b/docs_assets/system-icons/Nintendo - Nintendo DS.png new file mode 100644 index 00000000..e1a6c595 Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Nintendo DS.png differ diff --git a/docs_assets/system-icons/Nintendo - Pokemon Mini.png b/docs_assets/system-icons/Nintendo - Pokemon Mini.png new file mode 100644 index 00000000..e3fc20ed Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Pokemon Mini.png differ diff --git a/docs_assets/system-icons/Nintendo - Satellaview.png b/docs_assets/system-icons/Nintendo - Satellaview.png new file mode 100644 index 00000000..372bda66 Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Satellaview.png differ diff --git a/docs_assets/system-icons/Nintendo - Switch.png b/docs_assets/system-icons/Nintendo - Switch.png new file mode 100644 index 00000000..b893522d Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Switch.png differ diff --git a/docs_assets/system-icons/Nintendo - Wii U.png b/docs_assets/system-icons/Nintendo - Wii U.png new file mode 100644 index 00000000..c9edbf32 Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Wii U.png differ diff --git a/docs_assets/system-icons/Nintendo - Wii.png b/docs_assets/system-icons/Nintendo - Wii.png new file mode 100644 index 00000000..672080d8 Binary files /dev/null and b/docs_assets/system-icons/Nintendo - Wii.png differ diff --git a/docs_assets/system-icons/Nokia - N-Gage.png b/docs_assets/system-icons/Nokia - N-Gage.png new file mode 100644 index 00000000..7786f38b Binary files /dev/null and b/docs_assets/system-icons/Nokia - N-Gage.png differ diff --git a/docs_assets/system-icons/Philips - CD-i.png b/docs_assets/system-icons/Philips - CD-i.png new file mode 100644 index 00000000..bc0811ad Binary files /dev/null and b/docs_assets/system-icons/Philips - CD-i.png differ diff --git a/docs_assets/system-icons/Philips - Videopac+.png b/docs_assets/system-icons/Philips - Videopac+.png new file mode 100644 index 00000000..38e03916 Binary files /dev/null and b/docs_assets/system-icons/Philips - Videopac+.png differ diff --git a/docs_assets/system-icons/Pioneer - LaserActive.png b/docs_assets/system-icons/Pioneer - LaserActive.png new file mode 100644 index 00000000..5bcb3345 Binary files /dev/null and b/docs_assets/system-icons/Pioneer - LaserActive.png differ diff --git a/docs_assets/system-icons/RCA - Studio II.png b/docs_assets/system-icons/RCA - Studio II.png new file mode 100644 index 00000000..47c0914a Binary files /dev/null and b/docs_assets/system-icons/RCA - Studio II.png differ diff --git a/docs_assets/system-icons/SNK - Neo Geo CD.png b/docs_assets/system-icons/SNK - Neo Geo CD.png new file mode 100644 index 00000000..e575a324 Binary files /dev/null and b/docs_assets/system-icons/SNK - Neo Geo CD.png differ diff --git a/docs_assets/system-icons/SNK - Neo Geo Pocket.png b/docs_assets/system-icons/SNK - Neo Geo Pocket.png new file mode 100644 index 00000000..ffa25ff5 Binary files /dev/null and b/docs_assets/system-icons/SNK - Neo Geo Pocket.png differ diff --git a/docs_assets/system-icons/SNK - Neo Geo.png b/docs_assets/system-icons/SNK - Neo Geo.png new file mode 100644 index 00000000..f7e28b61 Binary files /dev/null and b/docs_assets/system-icons/SNK - Neo Geo.png differ diff --git a/docs_assets/system-icons/Sega - 32X.png b/docs_assets/system-icons/Sega - 32X.png new file mode 100644 index 00000000..292b22dd Binary files /dev/null and b/docs_assets/system-icons/Sega - 32X.png differ diff --git a/docs_assets/system-icons/Sega - Beena.png b/docs_assets/system-icons/Sega - Beena.png new file mode 100644 index 00000000..3deee872 Binary files /dev/null and b/docs_assets/system-icons/Sega - Beena.png differ diff --git a/docs_assets/system-icons/Sega - Chihiro.png b/docs_assets/system-icons/Sega - Chihiro.png new file mode 100644 index 00000000..6eab6438 Binary files /dev/null and b/docs_assets/system-icons/Sega - Chihiro.png differ diff --git a/docs_assets/system-icons/Sega - Dreamcast.png b/docs_assets/system-icons/Sega - Dreamcast.png new file mode 100644 index 00000000..739918a1 Binary files /dev/null and b/docs_assets/system-icons/Sega - Dreamcast.png differ diff --git a/docs_assets/system-icons/Sega - Game Gear.png b/docs_assets/system-icons/Sega - Game Gear.png new file mode 100644 index 00000000..169bbaa1 Binary files /dev/null and b/docs_assets/system-icons/Sega - Game Gear.png differ diff --git a/docs_assets/system-icons/Sega - Saturn.png b/docs_assets/system-icons/Sega - Saturn.png new file mode 100644 index 00000000..3c3a03c9 Binary files /dev/null and b/docs_assets/system-icons/Sega - Saturn.png differ diff --git a/docs_assets/system-icons/Sharp - X1.png b/docs_assets/system-icons/Sharp - X1.png new file mode 100644 index 00000000..055c9b95 Binary files /dev/null and b/docs_assets/system-icons/Sharp - X1.png differ diff --git a/docs_assets/system-icons/Sharp - X68000.png b/docs_assets/system-icons/Sharp - X68000.png new file mode 100644 index 00000000..fdc8380c Binary files /dev/null and b/docs_assets/system-icons/Sharp - X68000.png differ diff --git a/docs_assets/system-icons/Sinclair - ZX 81.png b/docs_assets/system-icons/Sinclair - ZX 81.png new file mode 100644 index 00000000..db75644c Binary files /dev/null and b/docs_assets/system-icons/Sinclair - ZX 81.png differ diff --git a/docs_assets/system-icons/Sinclair - ZX Spectrum.png b/docs_assets/system-icons/Sinclair - ZX Spectrum.png new file mode 100644 index 00000000..465ba9d5 Binary files /dev/null and b/docs_assets/system-icons/Sinclair - ZX Spectrum.png differ diff --git a/docs_assets/system-icons/Sony - PlayStation 2.png b/docs_assets/system-icons/Sony - PlayStation 2.png new file mode 100644 index 00000000..f32ab5ea Binary files /dev/null and b/docs_assets/system-icons/Sony - PlayStation 2.png differ diff --git a/docs_assets/system-icons/Sony - PlayStation 4.png b/docs_assets/system-icons/Sony - PlayStation 4.png new file mode 100644 index 00000000..ac2577ff Binary files /dev/null and b/docs_assets/system-icons/Sony - PlayStation 4.png differ diff --git a/docs_assets/system-icons/Sony - PlayStation Portable.png b/docs_assets/system-icons/Sony - PlayStation Portable.png new file mode 100644 index 00000000..6516ef4b Binary files /dev/null and b/docs_assets/system-icons/Sony - PlayStation Portable.png differ diff --git a/docs_assets/system-icons/Sony - PlayStation Vita.png b/docs_assets/system-icons/Sony - PlayStation Vita.png new file mode 100644 index 00000000..81c5710d Binary files /dev/null and b/docs_assets/system-icons/Sony - PlayStation Vita.png differ diff --git a/docs_assets/system-icons/Sony - PlayStation.png b/docs_assets/system-icons/Sony - PlayStation.png new file mode 100644 index 00000000..cb5cbc3c Binary files /dev/null and b/docs_assets/system-icons/Sony - PlayStation.png differ diff --git a/docs_assets/system-icons/Texas Instruments - TI-83.png b/docs_assets/system-icons/Texas Instruments - TI-83.png new file mode 100644 index 00000000..7419784f Binary files /dev/null and b/docs_assets/system-icons/Texas Instruments - TI-83.png differ diff --git a/docs_assets/system-icons/Tiger - Game.com.png b/docs_assets/system-icons/Tiger - Game.com.png new file mode 100644 index 00000000..3accb44a Binary files /dev/null and b/docs_assets/system-icons/Tiger - Game.com.png differ diff --git a/docs_assets/system-icons/Tomy - Tutor.png b/docs_assets/system-icons/Tomy - Tutor.png new file mode 100644 index 00000000..81677a2d Binary files /dev/null and b/docs_assets/system-icons/Tomy - Tutor.png differ diff --git a/docs_assets/system-icons/VTech - CreatiVision.png b/docs_assets/system-icons/VTech - CreatiVision.png new file mode 100644 index 00000000..9635d68a Binary files /dev/null and b/docs_assets/system-icons/VTech - CreatiVision.png differ diff --git a/docs_assets/system-icons/VTech - Socrates.png b/docs_assets/system-icons/VTech - Socrates.png new file mode 100644 index 00000000..eb6c98ad Binary files /dev/null and b/docs_assets/system-icons/VTech - Socrates.png differ diff --git a/docs_assets/system-icons/VTech - V.Smile.png b/docs_assets/system-icons/VTech - V.Smile.png new file mode 100644 index 00000000..c89421e9 Binary files /dev/null and b/docs_assets/system-icons/VTech - V.Smile.png differ diff --git a/docs_assets/system-icons/Videoton - TVC.png b/docs_assets/system-icons/Videoton - TVC.png new file mode 100644 index 00000000..77be9b45 Binary files /dev/null and b/docs_assets/system-icons/Videoton - TVC.png differ diff --git a/docs_assets/system-icons/sources.json b/docs_assets/system-icons/sources.json new file mode 100644 index 00000000..9e65327f --- /dev/null +++ b/docs_assets/system-icons/sources.json @@ -0,0 +1,321 @@ +{ + "note": "System icons from libretro/retroarch-assets (xmb/systematic), licensed CC-BY-4.0 by the libretro team. Resized to 48 px.", + "images": [ + { + "file": "APF - Imagination Machine.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/APF%20-%20Imagination%20Machine.png" + }, + { + "file": "Acorn - Archimedes.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Acorn%20-%20Archimedes.png" + }, + { + "file": "Acorn - BBC Micro.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Acorn%20-%20BBC%20Micro.png" + }, + { + "file": "Amstrad - CPC.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Amstrad%20-%20CPC.png" + }, + { + "file": "Apple - Macintosh.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Apple%20-%20Macintosh.png" + }, + { + "file": "Aquaplus - PIECE.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Aquaplus%20-%20PIECE.png" + }, + { + "file": "Atari - 2600.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%202600.png" + }, + { + "file": "Atari - 5200.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%205200.png" + }, + { + "file": "Atari - 7800.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%207800.png" + }, + { + "file": "Atari - Jaguar.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%20Jaguar.png" + }, + { + "file": "Atari - Lynx.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%20Lynx.png" + }, + { + "file": "Atari - ST.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%20ST.png" + }, + { + "file": "Atari - XEGS.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Atari%20-%20XEGS.png" + }, + { + "file": "Bally - Astrocade.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Bally%20-%20Astrocade.png" + }, + { + "file": "Bandai - WonderSwan.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Bandai%20-%20WonderSwan.png" + }, + { + "file": "Bit Corporation - Gamate.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Bit%20Corporation%20-%20Gamate.png" + }, + { + "file": "Casio - Loopy.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Casio%20-%20Loopy.png" + }, + { + "file": "Casio - PV-2000.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Casio%20-%20PV-2000.png" + }, + { + "file": "Coleco - ColecoVision.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Coleco%20-%20ColecoVision.png" + }, + { + "file": "Commodore - Amiga.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Commodore%20-%20Amiga.png" + }, + { + "file": "Commodore - VIC-20.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Commodore%20-%20VIC-20.png" + }, + { + "file": "Entex - Adventure Vision.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Entex%20-%20Adventure%20Vision.png" + }, + { + "file": "Epoch - Super Cassette Vision.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Epoch%20-%20Super%20Cassette%20Vision.png" + }, + { + "file": "Fairchild - Channel F.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Fairchild%20-%20Channel%20F.png" + }, + { + "file": "Fujitsu - FM Towns.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Fujitsu%20-%20FM%20Towns.png" + }, + { + "file": "Fujitsu - FM-7.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Fujitsu%20-%20FM-7.png" + }, + { + "file": "GCE - Vectrex.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/GCE%20-%20Vectrex.png" + }, + { + "file": "GamePark - GP32.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/GamePark%20-%20GP32.png" + }, + { + "file": "Hartung - Game Master.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Hartung%20-%20Game%20Master.png" + }, + { + "file": "Infocom - Z-Machine.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Infocom%20-%20Z-Machine.png" + }, + { + "file": "Magnavox - Odyssey2.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Magnavox%20-%20Odyssey2.png" + }, + { + "file": "Mattel - Intellivision.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Mattel%20-%20Intellivision.png" + }, + { + "file": "Microsoft - MSX.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Microsoft%20-%20MSX.png" + }, + { + "file": "Microsoft - Xbox.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Microsoft%20-%20Xbox.png" + }, + { + "file": "NEC - PC-88.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/NEC%20-%20PC-88.png" + }, + { + "file": "NEC - PC-98.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/NEC%20-%20PC-98.png" + }, + { + "file": "NEC - PC-FX.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/NEC%20-%20PC-FX.png" + }, + { + "file": "Nintendo - Game Boy Advance.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Game%20Boy%20Advance.png" + }, + { + "file": "Nintendo - Game Boy Color.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Game%20Boy%20Color.png" + }, + { + "file": "Nintendo - Game Boy.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Game%20Boy.png" + }, + { + "file": "Nintendo - GameCube.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20GameCube.png" + }, + { + "file": "Nintendo - Nintendo 64.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Nintendo%2064.png" + }, + { + "file": "Nintendo - Nintendo 64DD.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Nintendo%2064DD.png" + }, + { + "file": "Nintendo - Nintendo DS.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Nintendo%20DS.png" + }, + { + "file": "Nintendo - Pokemon Mini.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Pokemon%20Mini.png" + }, + { + "file": "Nintendo - Satellaview.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Satellaview.png" + }, + { + "file": "Nintendo - Switch.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Switch.png" + }, + { + "file": "Nintendo - Wii U.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Wii%20U.png" + }, + { + "file": "Nintendo - Wii.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nintendo%20-%20Wii.png" + }, + { + "file": "Nokia - N-Gage.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Nokia%20-%20N-Gage.png" + }, + { + "file": "Philips - CD-i.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Philips%20-%20CD-i.png" + }, + { + "file": "Philips - Videopac+.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Philips%20-%20Videopac%2B.png" + }, + { + "file": "Pioneer - LaserActive.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Pioneer%20-%20LaserActive.png" + }, + { + "file": "RCA - Studio II.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/RCA%20-%20Studio%20II.png" + }, + { + "file": "SNK - Neo Geo CD.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/SNK%20-%20Neo%20Geo%20CD.png" + }, + { + "file": "SNK - Neo Geo Pocket.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/SNK%20-%20Neo%20Geo%20Pocket.png" + }, + { + "file": "SNK - Neo Geo.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/SNK%20-%20Neo%20Geo.png" + }, + { + "file": "Sega - 32X.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sega%20-%2032X.png" + }, + { + "file": "Sega - Beena.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sega%20-%20Beena.png" + }, + { + "file": "Sega - Chihiro.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sega%20-%20Chihiro.png" + }, + { + "file": "Sega - Dreamcast.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sega%20-%20Dreamcast.png" + }, + { + "file": "Sega - Game Gear.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sega%20-%20Game%20Gear.png" + }, + { + "file": "Sega - Saturn.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sega%20-%20Saturn.png" + }, + { + "file": "Sharp - X1.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sharp%20-%20X1.png" + }, + { + "file": "Sharp - X68000.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sharp%20-%20X68000.png" + }, + { + "file": "Sinclair - ZX 81.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sinclair%20-%20ZX%2081.png" + }, + { + "file": "Sinclair - ZX Spectrum.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sinclair%20-%20ZX%20Spectrum.png" + }, + { + "file": "Sony - PlayStation 2.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sony%20-%20PlayStation%202.png" + }, + { + "file": "Sony - PlayStation 4.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sony%20-%20PlayStation%204.png" + }, + { + "file": "Sony - PlayStation Portable.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sony%20-%20PlayStation%20Portable.png" + }, + { + "file": "Sony - PlayStation Vita.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sony%20-%20PlayStation%20Vita.png" + }, + { + "file": "Sony - PlayStation.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Sony%20-%20PlayStation.png" + }, + { + "file": "Texas Instruments - TI-83.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Texas%20Instruments%20-%20TI-83.png" + }, + { + "file": "Tiger - Game.com.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Tiger%20-%20Game.com.png" + }, + { + "file": "Tomy - Tutor.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Tomy%20-%20Tutor.png" + }, + { + "file": "VTech - CreatiVision.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/VTech%20-%20CreatiVision.png" + }, + { + "file": "VTech - Socrates.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/VTech%20-%20Socrates.png" + }, + { + "file": "VTech - V.Smile.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/VTech%20-%20V.Smile.png" + }, + { + "file": "Videoton - TVC.png", + "source": "https://raw.githubusercontent.com/libretro/retroarch-assets/master/xmb/systematic/png/Videoton%20-%20TVC.png" + } + ] +} diff --git a/platforms/_registry.yml b/platforms/_registry.yml index dcc12fd8..f10c54cb 100644 --- a/platforms/_registry.yml +++ b/platforms/_registry.yml @@ -665,7 +665,7 @@ platforms: config: romm.yml status: active extract_to: library/bios/ - logo: https://avatars.githubusercontent.com/u/168586850 + logo: https://avatars.githubusercontent.com/u/164058018 scraper: romm source_url: https://raw.githubusercontent.com/rommapp/romm/master/backend/models/fixtures/known_bios_files.json source_format: json @@ -727,7 +727,7 @@ platforms: config: rocknix.yml status: active extract_to: /storage/roms/bios/ - logo: https://avatars.githubusercontent.com/u/152618023 + logo: https://avatars.githubusercontent.com/u/163176886 scraper: rocknix schedule: weekly target_scraper: rocknix_targets @@ -897,7 +897,7 @@ platforms: config: misterfpga.yml status: active extract_to: /media/fat/games/ - logo: https://avatars.githubusercontent.com/u/47264183 + logo: https://avatars.githubusercontent.com/u/29345272 scraper: misterfpga source_url: https://raw.githubusercontent.com/ajgowans/BiosDB_MiSTer/db/bios_db.json.zip source_format: mister_downloader_db @@ -980,7 +980,6 @@ platforms: # minarch hands each core SDCARD_PATH/Bios/ # (workspace/all/minarch/minarch.c:3532). extract_to: / - logo: https://avatars.githubusercontent.com/u/10894566 scraper: myminui # minarch hands each core a folder and names no file (workspace/all/minarch/minarch.c:3532): # there is no BIOS list of its own to export corrected. diff --git a/scripts/fetch_site_images.py b/scripts/fetch_site_images.py new file mode 100644 index 00000000..410b0214 --- /dev/null +++ b/scripts/fetch_site_images.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Download the platform logos and system icons the site shows, once. + +The site served these from other hosts: GitHub avatars and files on the +``master`` branch of upstream repositories. A rename upstream broke an image +in silence, and every page view hit third-party hosts. This tool copies them +into ``docs_assets/logos/`` and ``docs_assets/system-icons/``, which are +committed, and records where each came from in a ``sources.json`` beside +them. The site build only reads those copies (``sitediagrams.localize_images``). + +Raster images are normalised to a square PNG (96 px for logos, 48 px for +system icons) so every tile has the same weight; SVG is kept as published. +Normalising needs Pillow. This is a maintenance tool run by hand when a +logo changes, not a build step: the build stays on the standard library +and PyYAML. + + python scripts/fetch_site_images.py # logos and icons + python scripts/fetch_site_images.py --only logos +""" + +from __future__ import annotations + +import argparse +import io +import json +import sys +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "scripts")) + +LOGO_DIR = ROOT / "docs_assets" / "logos" +ICON_DIR = ROOT / "docs_assets" / "system-icons" +LOGO_SIZE = 96 +ICON_SIZE = 48 +AVATAR_HOST = "avatars.githubusercontent.com" +USER_AGENT = "retrobios-site-images" + +# Who owns each mark. Logos are shown to name the platform they belong to; +# the note says so, and gives the licence of the repository the file was +# taken from when that repository states one. +OWNERS = { + "retroarch": ("libretro", "repository licence GPL-3.0"), + "lakka": ("Lakka (libretro)", "file from libretro/retroarch-assets, CC-BY-4.0"), + "batocera": ("Batocera", "repository licence MIT"), + "recalbox": ("Recalbox", "file from homarr-labs/dashboard-icons, Apache-2.0"), + "retrobat": ("RetroBat", "repository states no licence"), + "emudeck": ("EmuDeck", "repository licence GPL-3.0"), + "retrodeck": ("RetroDECK", "repository licence GPL-3.0"), + "romm": ("RomM", "GitHub organisation avatar of rommapp"), + "rocknix": ("ROCKNIX", "GitHub organisation avatar of ROCKNIX"), + "amberelec": ("AmberELEC", "GitHub organisation avatar of AmberELEC"), + "emuelec": ("EmuELEC", "GitHub organisation avatar of EmuELEC"), + "knulli": ("KNULLI", "GitHub organisation avatar of knulli-cfw"), + "retropie": ("RetroPie", "GitHub organisation avatar of RetroPie"), + "bizhawk": ("TASVideos", "GitHub organisation avatar of TASEmulators"), + "misterfpga": ("MiSTer FPGA", "GitHub organisation avatar of MiSTer-devel"), + "minui": ("Shaun Inman", "GitHub avatar of shauninman, the author of MinUI"), + "nextui": ("LoveRetro", "GitHub organisation avatar of LoveRetro"), +} + + +def fetch(url: str) -> bytes: + request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(request, timeout=60) as response: + return response.read() + + +def square_png(data: bytes, size: int) -> bytes: + try: + from PIL import Image + except ImportError as exc: + raise SystemExit("Pillow is needed to normalise raster images: pip install Pillow") from exc + image = Image.open(io.BytesIO(data)).convert("RGBA") + image.thumbnail((size, size), Image.LANCZOS) + tile = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + tile.paste(image, ((size - image.width) // 2, (size - image.height) // 2), image) + out = io.BytesIO() + tile.save(out, "PNG", optimize=True) + return out.getvalue() + + +def is_svg(data: bytes) -> bool: + head = data[:512].lstrip().lower() + return head.startswith(b" bytes: + lowered = data.lower() + for token in (b" None: + entries.sort(key=lambda entry: entry["file"]) + document = {"note": note, "images": entries} + (directory / "sources.json").write_text( + json.dumps(document, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + +def fetch_logos() -> None: + with open(ROOT / "platforms" / "_registry.yml", encoding="utf-8") as handle: + platforms = yaml.safe_load(handle)["platforms"] + LOGO_DIR.mkdir(parents=True, exist_ok=True) + entries = [] + for name, entry in sorted(platforms.items()): + url = entry.get("logo") + if not url: + continue + download = f"{url}?s={LOGO_SIZE * 2}" if AVATAR_HOST in url else url + data = fetch(download) + if is_svg(data): + file_name, payload = f"{name}.svg", safe_svg(data, url) + else: + file_name, payload = f"{name}.png", square_png(data, LOGO_SIZE) + for stale in LOGO_DIR.glob(f"{name}.*"): + if stale.name != file_name: + stale.unlink() + (LOGO_DIR / file_name).write_bytes(payload) + owner, licence = OWNERS.get(name, (name, "ownership not recorded")) + entries.append({ + "file": file_name, "platform": name, "source": url, + "owner": owner, "licence": licence, + }) + print(f" {file_name} from {url}") + write_manifest( + LOGO_DIR, + "Each platform's own logo, owned by its project and shown only to name that " + "platform. Copied once by scripts/fetch_site_images.py.", + entries, + ) + + +def fetch_icons() -> None: + from generate_site import _group_by_manufacturer, load_database + from siterender import SYSTEM_ICON_BASE, _icon_name + + db = load_database(str(ROOT / "database.json")) + names = sorted({ + _icon_name(manufacturer, console) + for manufacturer, consoles in _group_by_manufacturer(db).items() + for console in consoles + }) + ICON_DIR.mkdir(parents=True, exist_ok=True) + entries = [] + for name in names: + url = f"{SYSTEM_ICON_BASE}/{urllib.parse.quote(name)}.png" + try: + data = fetch(url) + except urllib.error.HTTPError as exc: + if exc.code == 404: + continue + raise + file_name = f"{name}.png" + (ICON_DIR / file_name).write_bytes(square_png(data, ICON_SIZE)) + entries.append({"file": file_name, "source": url}) + print(f" {file_name}") + write_manifest( + ICON_DIR, + "System icons from libretro/retroarch-assets (xmb/systematic), " + "licensed CC-BY-4.0 by the libretro team. Resized to 48 px.", + entries, + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + parser.add_argument("--only", choices=("logos", "icons"), help="fetch one kind only") + args = parser.parse_args() + if args.only in (None, "logos"): + print("Platform logos:") + fetch_logos() + if args.only in (None, "icons"): + print("System icons:") + fetch_icons() + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 55573f1e..02872ee3 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -1810,11 +1810,16 @@ def decorate_markdown_pages(docs: Path) -> None: relative = path.relative_to(docs) if any(part in LOCAL_NOTE_DIRS for part in relative.parts[:-1]): continue - markdown = path.read_text(encoding="utf-8") + original = path.read_text(encoding="utf-8") + markdown = prepare_page(original, relative) + for image_id in unresolved_images(markdown): + print(f" WARNING: {relative}: no diagram named {image_id!r}", file=sys.stderr) stripped = _undecorated(markdown) if stripped is not markdown: markdown = stripped elif markdown.startswith("---\n"): + if markdown != original: + write_decorated(path, markdown) continue title = _page_title(markdown, relative) @@ -5647,6 +5652,12 @@ def _generate(args: argparse.Namespace) -> None: images_dest = docs / "assets" / "images" images_dest.mkdir(parents=True, exist_ok=True) + # Logos, system icons and sharing cards are served from the site itself + # (docs_assets/*/sources.json records where each came from). + for folder in ("logos", "system-icons", "og"): + source = Path("docs_assets") / folder + if source.is_dir(): + shutil.copytree(source, docs / "assets" / folder, dirs_exist_ok=True) assets_src = Path(".github") / "assets" for name, dest_name in [("logo.png", "logo.png"), ("favicon.png", "favicon.png")]: src = assets_src / name @@ -6003,6 +6014,7 @@ validation: if stale: print(f"Removed {stale} page(s) nothing produced this run") +from sitediagrams import prepare_page, unresolved_images # noqa: E402,F401 from sitewrite import ( # noqa: E402,F401 _produced, _record, @@ -6042,7 +6054,7 @@ from siterender import ( # noqa: E402,F401 prime_system_icons, _icon_available, SYSTEM_ICON_BASE, - ICON_CACHE_PATH, + ICON_DIR, ) if __name__ == "__main__": diff --git a/scripts/make_og_cards.py b/scripts/make_og_cards.py new file mode 100644 index 00000000..ec472fb5 --- /dev/null +++ b/scripts/make_og_cards.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Render the sharing cards (Open Graph images) to PNG, once. + +Each card is an SVG written by ``sitediagrams.og_card_svg`` from the +registry, rasterised by a headless Chromium through Playwright, then +committed under ``docs_assets/og/``. The site build copies the PNG files and +never runs this script, so it adds nothing to the build's dependencies. +Run it again when a section's wording or the platform count changes. + + python scripts/make_og_cards.py +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "scripts")) + +from sitediagrams import OG_SECTIONS, og_card_svg # noqa: E402 + +OUT = ROOT / "docs_assets" / "og" +LOGO = ROOT / ".github" / "assets" / "logo.png" + + +def main() -> None: + try: + from playwright.sync_api import sync_playwright + except ImportError as exc: + raise SystemExit("Playwright with Chromium is needed: pip install playwright") from exc + OUT.mkdir(parents=True, exist_ok=True) + logo = LOGO.read_bytes() + with sync_playwright() as playwright: + browser = playwright.chromium.launch() + page = browser.new_page(viewport={"width": 1200, "height": 630}) + for section in OG_SECTIONS: + page.set_content( + '' + og_card_svg(section, logo) + "" + ) + path = OUT / f"{section}.png" + page.screenshot(path=str(path), clip={"x": 0, "y": 0, "width": 1200, "height": 630}) + print(path.relative_to(ROOT)) + browser.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/sitediagrams.py b/scripts/sitediagrams.py new file mode 100644 index 00000000..1117657c --- /dev/null +++ b/scripts/sitediagrams.py @@ -0,0 +1,755 @@ +"""Diagrams for the site, written as SVG from the repository's own data. + +Each diagram is a function of the registry, the platform files, the +pipeline's step list or the install manifests, so it changes when the data +does and never needs redrawing by hand. Output is deterministic: sorted +inputs, fixed number formatting, ids derived from the diagram's name. + +A page asks for a diagram with an HTML comment on a line of its own:: + + + +``resolve_images`` replaces the comment with the figure. The SVG is inline +so its text takes the page font and its colours come from the theme +variables in ``docs_assets/extra.css``: it follows the palette toggle, which +an ```` cannot. Presentation attributes give the same drawing a +readable default when the SVG is opened on its own. + +Every figure carries two layouts, wide and narrow, and the stylesheet shows +one: a 720-unit drawing scaled to a phone would print 9 px text. +""" + +from __future__ import annotations + +import ast +import json +import re +import sys +import urllib.parse +from collections import Counter +from collections.abc import Callable +from dataclasses import dataclass, field +from functools import lru_cache +from pathlib import Path +from xml.sax.saxutils import escape + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "scripts")) + +PLACEHOLDER = re.compile(r"(?m)^([ \t]*)[ \t]*$") +PLACEHOLDER_ANYWHERE = re.compile(r"") + +FONT = "system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif" +TITLE_SIZE = 14 +TEXT_SIZE = 12.5 +LINE = 17 +PAD = 10 +WIDE = 720 +NARROW = 340 + +# Default colours for an SVG read outside the page; the stylesheet replaces +# them on the site through the rb-d-* classes. +INK = "#1f2233" +FILL = "#f5f6fa" +STROKE = "#8c92ac" +ACCENT = "#3b3f7a" +MUTED = "#546e7a" + + +def _width(text: str, size: float, bold: bool = False) -> float: + """Rough advance of a sans-serif string: 0.56 em a character, 0.6 bold.""" + return len(text) * size * (0.6 if bold else 0.56) + + +def wrap(text: str, width: float, size: float, bold: bool = False) -> list[str]: + """Greedy word wrap against the estimated advance.""" + lines: list[str] = [] + current = "" + for word in text.split(): + candidate = f"{current} {word}".strip() + if current and _width(candidate, size, bold) > width: + lines.append(current) + current = word + else: + current = candidate + if current: + lines.append(current) + return lines + + +def _n(value: float) -> str: + text = f"{value:.1f}" + return text[:-2] if text.endswith(".0") else text + + +@dataclass +class Box: + title: str + lines: list[str] = field(default_factory=list) + accent: bool = False + number: str = "" + + +@dataclass +class Figure: + """One diagram: what it shows, said twice (alt and caption), and drawn twice.""" + + image_id: str + title: str + desc: str + caption: str + wide: Callable[[], tuple[float, float, list[str]]] + narrow: Callable[[], tuple[float, float, list[str]]] + + +# Drawing primitives + + +def _text(x: float, y: float, value: str, size: float = TEXT_SIZE, + bold: bool = False, anchor: str = "start", cls: str = "") -> str: + weight = ' font-weight="600"' if bold else "" + klass = f' class="{cls}"' if cls else "" + fill = MUTED if cls == "rb-d-muted" else ("#ffffff" if cls == "rb-d-numtext" else INK) + return ( + f'{escape(value)}' + ) + + +def _rect(x: float, y: float, w: float, h: float, accent: bool = False) -> str: + cls = "rb-d-box rb-d-accent" if accent else "rb-d-box" + stroke = ACCENT if accent else STROKE + width = 2 if accent else 1 + return ( + f'' + ) + + +def _badge_side(number: str) -> float: + return max(20.0, _width(number, 11, True) + 10) + + +def _badge(x: float, y: float, number: str) -> list[str]: + side = _badge_side(number) + return [ + f'', + _text(x + side / 2, y + 14, number, 11, True, "middle", "rb-d-numtext"), + ] + + +def _line(points: list[tuple[float, float]], head: bool = True) -> list[str]: + """A connector through *points*, with a drawn arrowhead at the end. + + The head is a small triangle, never a text glyph. + """ + path = " ".join( + f"{'M' if index == 0 else 'L'}{_n(x)} {_n(y)}" for index, (x, y) in enumerate(points) + ) + out = [ + f'' + ] + if head and len(points) >= 2: + (x1, y1), (x2, y2) = points[-2], points[-1] + if x1 == x2: + sign = 1 if y2 > y1 else -1 + tri = [(x2, y2), (x2 - 5, y2 - 8 * sign), (x2 + 5, y2 - 8 * sign)] + else: + sign = 1 if x2 > x1 else -1 + tri = [(x2, y2), (x2 - 8 * sign, y2 - 5), (x2 - 8 * sign, y2 + 5)] + out.append( + '' + ) + return out + + +def _box_height(box: Box, width: float) -> tuple[list[str], list[str], float]: + inner = width - 2 * PAD + title_lines = wrap(box.title, inner - (_badge_side(box.number) + 8 if box.number else 0), + TITLE_SIZE, True) + body: list[str] = [] + for line in box.lines: + body.extend(wrap(line, inner, TEXT_SIZE)) + height = PAD + len(title_lines) * LINE + (6 if body else 0) + len(body) * LINE + PAD + if box.number: + height = max(height, PAD * 2 + 20) + return title_lines, body, height + 2 + + +def _draw_box(box: Box, x: float, y: float, width: float, height: float, + title_lines: list[str], body: list[str]) -> list[str]: + out = [_rect(x, y, width, height, box.accent)] + tx = x + PAD + if box.number: + out.extend(_badge(x + PAD, y + PAD, box.number)) + tx += _badge_side(box.number) + 8 + cy = y + PAD + 13 + for line in title_lines: + out.append(_text(tx, cy, line, TITLE_SIZE, True)) + cy += LINE + cy += 6 + for line in body: + out.append(_text(x + PAD, cy, line, TEXT_SIZE, cls="rb-d-muted")) + cy += LINE + return out + + +def flow(boxes: list[Box], vertical: bool, total: float) -> tuple[float, float, list[str]]: + """Boxes joined in order: a row on a wide screen, a column on a narrow one.""" + out: list[str] = [] + if vertical: + width = total + y = 1.0 + for index, box in enumerate(boxes): + title_lines, body, height = _box_height(box, width - 2) + out.extend(_draw_box(box, 1, y, width - 2, height, title_lines, body)) + y += height + if index < len(boxes) - 1: + out.extend(_line([(width / 2, y), (width / 2, y + 22)])) + y += 24 + return width, y + 1, out + gap = 26.0 + width = (total - gap * (len(boxes) - 1) - 2) / len(boxes) + measured = [_box_height(box, width) for box in boxes] + height = max(m[2] for m in measured) + for index, (box, (title_lines, body, _)) in enumerate(zip(boxes, measured)): + x = 1 + index * (width + gap) + out.extend(_draw_box(box, x, 1, width, height, title_lines, body)) + if index < len(boxes) - 1: + mid = 1 + height / 2 + out.extend(_line([(x + width, mid), (x + width + gap - 1, mid)])) + return total, height + 2, out + + +def columns(boxes: list[Box], vertical: bool, total: float) -> tuple[float, float, list[str]]: + """Side-by-side boxes with no order between them.""" + out: list[str] = [] + if vertical: + y = 1.0 + for box in boxes: + title_lines, body, height = _box_height(box, total - 2) + out.extend(_draw_box(box, 1, y, total - 2, height, title_lines, body)) + y += height + 12 + return total, y - 11, out + gap = 16.0 + width = (total - gap * (len(boxes) - 1) - 2) / len(boxes) + measured = [_box_height(box, width) for box in boxes] + height = max(m[2] for m in measured) + for index, (box, (title_lines, body, _)) in enumerate(zip(boxes, measured)): + out.extend(_draw_box(box, 1 + index * (width + gap), 1, width, height, title_lines, body)) + return total, height + 2, out + + +def grid(boxes: list[Box], per_row: int, total: float) -> tuple[float, float, list[str]]: + """Ordered boxes in rows, each row joined to the next by an elbow line.""" + out: list[str] = [] + gap_x, gap_y = 18.0, 30.0 + width = (total - gap_x * (per_row - 1) - 2) / per_row + rows = [boxes[i:i + per_row] for i in range(0, len(boxes), per_row)] + y = 1.0 + previous_end: tuple[float, float] | None = None + for row in rows: + measured = [_box_height(box, width) for box in row] + height = max(m[2] for m in measured) + for index, (box, (title_lines, body, _)) in enumerate(zip(row, measured)): + x = 1 + index * (width + gap_x) + if previous_end is not None and index == 0: + px, py = previous_end + out.extend(_line([(px, py), (px, py + gap_y / 2), (x + width / 2, py + gap_y / 2), + (x + width / 2, y)])) + out.extend(_draw_box(box, x, y, width, height, title_lines, body)) + if index < len(row) - 1: + out.extend(_line([(x + width, y + height / 2), (x + width + gap_x - 1, y + height / 2)])) + previous_end = (x + width / 2, y + height) + y += height + gap_y + return total, y - gap_y + 1, out + + +def svg(figure: Figure, layout: str, suffix: str) -> str: + """One layout of *figure* as a standalone, accessible SVG element.""" + width, height, body = (figure.wide if layout == "wide" else figure.narrow)() + base = f"rb-fig-{figure.image_id}{suffix}-{layout}" + return ( + f'' + f'{escape(figure.title)}' + f'{escape(figure.desc)}' + + "".join(body) + + "" + ) + + +# Data + + +@lru_cache(maxsize=None) +def registry() -> dict: + import yaml + + with open(ROOT / "platforms" / "_registry.yml", encoding="utf-8") as handle: + return yaml.safe_load(handle).get("platforms", {}) + + +@lru_cache(maxsize=None) +def platform_modes() -> dict[str, tuple[str, str, str | None]]: + """name -> (display name, verification mode, hash rule) for every platform.""" + from common import list_registered_platforms, load_platform_config + + out: dict[str, tuple[str, str, str | None]] = {} + for name in sorted(list_registered_platforms(str(ROOT / "platforms"), include_archived=True)): + config = load_platform_config(name, str(ROOT / "platforms")) + rule = registry().get(name, {}).get("hash_rule") or config.get("hash_rule") + out[name] = (config.get("platform", name), config.get("verification_mode", ""), rule) + return out + + +def _display(name: str) -> str: + return platform_modes()[name][0] + + +def _folder(name: str) -> str: + return str(registry()[name].get("extract_to", "")).strip() + + +# Frontends and emulators shown in the explainer. Each pair is one a user +# meets together; tests check that the platform runs that emulator. +CHAIN_EXAMPLES = (("batocera", "duckstation"), ("emudeck", "pcsx2"), ("retroarch", "beetle_psx")) + + +@lru_cache(maxsize=None) +def emulator_names() -> dict[str, str]: + import yaml + + names: dict[str, str] = {} + for _, emulator in CHAIN_EXAMPLES: + with open(ROOT / "emulators" / f"{emulator}.yml", encoding="utf-8") as handle: + label = str(yaml.safe_load(handle).get("emulator", emulator)) + names[emulator] = re.sub(r"\s*\(.*\)$", "", label) + return names + + +def _bios_chain_boxes() -> list[Box]: + active = sum(1 for entry in registry().values() if entry.get("status") == "active") + frontends = [_display(platform) for platform, _ in CHAIN_EXAMPLES] + emulators = [emulator_names()[emulator] for _, emulator in CHAIN_EXAMPLES] + folders = [f"{_display(platform)}: {_folder(platform)}" for platform, _ in CHAIN_EXAMPLES] + return [ + Box("Your device", ["A PC, a Steam Deck, a handheld or a TV box"], number="1"), + Box("Frontend", [ + "The menu you pick a game from:", + f"{', '.join(frontends)} and {active - len(frontends)} more", + ], number="2"), + Box("Emulator", [ + "Starts the game and reads its BIOS:", + ", ".join(emulators), + ], number="3"), + Box("BIOS folder", folders + ["The pack or the installer fills it"], + accent=True, number="4"), + ] + + +def _install_boxes() -> list[Box]: + detected = sum(1 for entry in registry().values() if entry.get("install", {}).get("detect")) + return [ + Box("Run one command", ["The same line on Linux, macOS, Windows and handhelds"], number="1"), + Box("Find the setup", [f"Looks for {detected} frontends and their BIOS folders"], number="2"), + Box("Read the folder", ["Hashes the files already there"], number="3"), + Box("Download the rest", ["Only what is missing or different"], number="4"), + Box("Check each file", ["Its SHA-1 must match before it is saved"], accent=True, number="5"), + ] + + +MODE_TEXT = { + "existence": ("Name only", "Only the file's name and place count. Its content is not read."), + "md5": ("MD5 fingerprint", "The content must match an MD5 the platform lists."), + "sha1": ("SHA-1 fingerprint", "The content must match a SHA-1 the platform lists."), +} + + +def _mode_boxes() -> list[Box]: + by_mode: dict[str, list[str]] = {mode: [] for mode in MODE_TEXT} + for display, mode, rule in platform_modes().values(): + label = f"{display} (size, then any hash)" if rule == "size_and_any_hash" else display + by_mode.setdefault(mode, []).append(label) + boxes = [] + for mode, (title, text) in MODE_TEXT.items(): + names = sorted(by_mode.get(mode, []), key=str.casefold) + count = f"{len(names)} platform" + ("" if len(names) == 1 else "s") + boxes.append(Box(title, [text, f"{count}: " + ", ".join(names)])) + return boxes + + +def _mode_summary() -> str: + parts = [] + for mode, (title, _) in MODE_TEXT.items(): + names = sorted( + (display for display, m, _ in platform_modes().values() if m == mode), key=str.casefold + ) + parts.append(f"{title}: {', '.join(names)}.") + return " ".join(parts) + + +# MinUI names a BIOS folder after the tag in parentheses of the ROM folder. +# These are three of the tags its launch scripts read. +MINUI_EXAMPLES = (("Sony PlayStation", "PS"), ("Game Boy Advance", "GBA"), ("Sega Genesis", "MD")) + + +@lru_cache(maxsize=None) +def minui_files() -> dict[str, list[str]]: + manifest = json.loads((ROOT / "install" / "minui.json").read_text(encoding="utf-8")) + by_tag: dict[str, list[str]] = {} + for entry in manifest.get("files", []): + parts = str(entry.get("dest", "")).split("/") + if len(parts) == 3 and parts[0] == "Bios": + by_tag.setdefault(parts[1], []).append(parts[2]) + return {tag: sorted(names, key=str.casefold) for tag, names in by_tag.items()} + + +def _minui_wide() -> tuple[float, float, list[str]]: + out: list[str] = [] + left_x, right_x, row = 1.0, 430.0, 22.0 + out.append(_text(left_x, 16, "Roms/", TITLE_SIZE, True)) + out.append(_text(right_x, 16, "Bios/", TITLE_SIZE, True)) + y = 30.0 + for name, tag in MINUI_EXAMPLES: + files = minui_files().get(tag, []) + shown = files[:2] + ([f"and {len(files) - 2} more"] if len(files) > 2 else []) + height = max(row + 8, 14 + row * (1 + len(shown))) + label = f"{name} ({tag})/" + out.append(_rect(left_x + 14, y, 250, row + 8)) + out.append(_text(left_x + 24, y + 19, label, TEXT_SIZE, True)) + out.append(_rect(right_x + 14, y, 250, height)) + out.append(_text(right_x + 24, y + 19, f"{tag}/", TEXT_SIZE, True)) + for index, file_name in enumerate(shown): + out.append(_text(right_x + 40, y + 19 + row * (index + 1), file_name, TEXT_SIZE, + cls="rb-d-muted")) + out.extend(_line([(left_x + 264, y + (row + 8) / 2), (right_x + 13, y + (row + 8) / 2)])) + out.append(_text((left_x + 264 + right_x + 14) / 2, y + (row + 8) / 2 - 6, + f"tag {tag}", 11, anchor="middle", cls="rb-d-muted")) + y += height + 14 + return WIDE, y - 12, out + + +def _minui_narrow() -> tuple[float, float, list[str]]: + out: list[str] = [] + y = 1.0 + for name, tag in MINUI_EXAMPLES: + files = minui_files().get(tag, []) + out.append(_rect(1, y, NARROW - 2, 30)) + out.append(_text(11, y + 20, f"Roms/{name} ({tag})/", TEXT_SIZE, True)) + out.extend(_line([(NARROW / 2, y + 30), (NARROW / 2, y + 52)])) + out.append(_text(NARROW / 2 + 10, y + 45, f"tag {tag}", 11, cls="rb-d-muted")) + y += 54 + detail = "" + if len(files) == 1: + detail = files[0] + elif files: + detail = f"{len(files)} files, such as {files[0]}" + height = 30 + (LINE if detail else 0) + out.append(_rect(1, y, NARROW - 2, height, accent=True)) + out.append(_text(11, y + 20, f"Bios/{tag}/", TEXT_SIZE, True)) + if detail: + out.append(_text(11, y + 20 + LINE, detail, TEXT_SIZE, cls="rb-d-muted")) + y += height + 18 + return NARROW, y - 17, out + + +def _pipeline_boxes() -> list[Box]: + """The steps pipeline.py runs, read from its STEP_LABELS literal.""" + tree = ast.parse((ROOT / "scripts" / "pipeline.py").read_text(encoding="utf-8")) + for node in ast.walk(tree): + target = getattr(node, "target", None) or (getattr(node, "targets", [None]) or [None])[0] + if isinstance(target, ast.Name) and target.id == "STEP_LABELS": + labels = ast.literal_eval(node.value) + break + else: + raise RuntimeError("STEP_LABELS not found in scripts/pipeline.py") + boxes = [] + for label in labels.values(): + number, title = label.split(" ", 1) + title = title.replace("mame", "MAME").replace("fbneo", "FBNeo") + boxes.append(Box(title[0].upper() + title[1:], number=number)) + return boxes + + +def _layers(vertical: bool) -> tuple[float, float, list[str]]: + shared = Box("_shared.yml", ["Curated groups shared between platforms"]) + platform = Box("Platform files", ["Scraped from each platform"]) + profiles = Box("Emulator profiles", ["Read from emulator source code"]) + pack = Box("Pack generation", ["Platform baseline plus what its cores load"], accent=True) + zip_box = Box("One ZIP pack per platform") + out: list[str] = [] + + def place(box: Box, x: float, y: float, w: float) -> tuple[float, float, float, float]: + title_lines, body, h = _box_height(box, w) + out.extend(_draw_box(box, x, y, w, h, title_lines, body)) + return x, y, w, h + + if not vertical: + px, py, pw, ph = place(platform, 1, 1, 200) + sx, sy, sw, sh = place(shared, 1, py + ph + 26, 200) + ex, ey, ew, eh = place(profiles, 1, sy + sh + 26, 200) + out.extend(_line([(sx + sw / 2, sy), (px + pw / 2, py + ph)])) + out.extend(_line([(sx + sw / 2, sy + sh), (ex + ew / 2, ey)])) + _, _, pack_h = _box_height(pack, 200) + gx, gy, gw, gh = place(pack, 270, sy + (sh - pack_h) / 2, 200) + out.extend(_line([(px + pw, py + ph / 2), (240, py + ph / 2), (240, gy + 20), (gx - 1, gy + 20)])) + out.extend(_line([(ex + ew, ey + eh / 2), (240, ey + eh / 2), (240, gy + gh - 20), + (gx - 1, gy + gh - 20)])) + zx, zy, zw, zh = place(zip_box, 518, gy + (gh - 42) / 2, 200) + out.extend(_line([(gx + gw, gy + gh / 2), (zx - 1, gy + gh / 2)])) + return WIDE, max(ey + eh, gy + gh) + 2, out + + w = NARROW - 2 + half = (w - 14) / 2 + sx, sy, sw, sh = place(shared, 1, 1, w) + px, py, pw, ph = place(platform, 1, sy + sh + 26, half) + ex, ey, ew, eh = place(profiles, 15 + half, py, half) + out.extend(_line([(px + pw / 2, sy + sh), (px + pw / 2, py)])) + out.extend(_line([(ex + ew / 2, sy + sh), (ex + ew / 2, ey)])) + bottom = max(py + ph, ey + eh) + gx, gy, gw, gh = place(pack, 1, bottom + 26, w) + out.extend(_line([(px + pw / 2, py + ph), (px + pw / 2, gy)])) + out.extend(_line([(ex + ew / 2, ey + eh), (ex + ew / 2, gy)])) + zx, zy, zw, zh = place(zip_box, 1, gy + gh + 26, w) + out.extend(_line([(NARROW / 2, gy + gh), (NARROW / 2, zy)])) + return NARROW, zy + zh + 2, out + + +def _figures() -> dict[str, Figure]: + chain = _bios_chain_boxes + install = _install_boxes + return { + "bios-chain": Figure( + "bios-chain", + "How a BIOS file is used", + "A device runs a frontend, the frontend starts an emulator, and the emulator " + "reads BIOS files from one folder. The pack or the installer fills that folder.", + "The frontend is the menu, the emulator runs the game, and the BIOS folder is the " + "one place both look. Filling that folder is all a pack does.", + lambda: flow(chain(), False, WIDE), + lambda: flow(chain(), True, NARROW), + ), + "install-flow": Figure( + "install-flow", + "What the installer does", + "Install flow: run the command, the installer finds your setup, reads the BIOS " + "folder, downloads what is missing or different, then checks each file before " + "saving it.", + "Running the command again repeats the same steps and downloads only what changed.", + lambda: flow(install(), False, WIDE), + lambda: flow(install(), True, NARROW), + ), + "verification-modes": Figure( + "verification-modes", + "How each platform checks a file", + "Three ways platforms check a BIOS file. " + _mode_summary(), + "Each platform's own check, read from its source code. A file that passes on one " + "platform can be refused on another.", + lambda: columns(_mode_boxes(), False, WIDE), + lambda: columns(_mode_boxes(), True, NARROW), + ), + "minui-card": Figure( + "minui-card", + "MinUI card layout", + "SD card folders: the tag in parentheses of each Roms folder, such as PS, names " + "the matching folder under Bios.", + "The folder under Bios takes the tag in parentheses of the ROM folder, not its " + "full name. Files listed are the ones the MinUI pack puts there.", + _minui_wide, + _minui_narrow, + ), + "pipeline": Figure( + "pipeline", + "Pipeline steps", + "The pipeline runs its steps in order: " + + ", ".join(f"{b.number} {b.title}" for b in _pipeline_boxes()) + ".", + "Read from the step list in scripts/pipeline.py, numbered as the run prints them.", + lambda: grid(_pipeline_boxes(), 4, WIDE), + lambda: grid(_pipeline_boxes(), 2, NARROW), + ), + "data-layers": Figure( + "data-layers", + "Three layers of data", + "Platform files and emulator profiles both feed pack generation, _shared.yml " + "adds curated groups to both, and pack generation writes one ZIP per platform.", + "The platform's list is the baseline, the profiles add what the cores load.", + lambda: _layers(False), + lambda: _layers(True), + ), + } + + +@lru_cache(maxsize=None) +def figures() -> dict[str, Figure]: + return _figures() + + +IMAGE_IDS = ("bios-chain", "install-flow", "verification-modes", "minui-card", "pipeline", "data-layers") + +# Entities for the characters Markdown reads as syntax, for a figure that +# lands indented (in a list, a tab or an admonition), where it is parsed as +# inline text rather than kept as a raw HTML block. +_INLINE_ESCAPES = str.maketrans({"_": "_", "*": "*", "`": "`", "[": "[", "]": "]"}) + + +def figure_html(image_id: str, occurrence: int = 1, inline: bool = False) -> str: + figure = figures()[image_id] + suffix = "" if occurrence == 1 else f"-{occurrence}" + drawings = svg(figure, "wide", suffix) + svg(figure, "narrow", suffix) + caption = escape(figure.caption) + anchor = f"rb-fig-{image_id}{suffix}" + if inline: + html = ( + f'' + f'{drawings}{caption}' + ) + return html.translate(_INLINE_ESCAPES) + return ( + f'
\n' + f"{drawings}\n" + f"
{caption}
\n" + "
" + ) + + +def resolve_images(markdown: str) -> str: + """Replace each image placeholder line with its figure. + + An unknown id is left in place: validate_site reports the comment in the + built page, and the test of placeholders names it. + """ + if "\n\ntext\n\n\n" + ) + ids = re.findall(r'\bid="([^"]+)"', page) + self.assertEqual(len(ids), len(set(ids))) + + +class PlaceholdersResolve(unittest.TestCase): + """A page asks for a diagram by id. A name no function answers stays a + comment, which validate_site reports in the built page.""" + + def test_every_placeholder_in_the_sources_names_a_diagram(self): + sources = [*sorted((ROOT / "wiki").glob("*.md")), *sorted((ROOT / "scripts").glob("*.py"))] + found = 0 + for path in sources: + text = path.read_text(encoding="utf-8") + for image_id in sitediagrams.PLACEHOLDER_ANYWHERE.findall(text): + found += 1 + with self.subTest(page=path.name, image=image_id): + self.assertIn(image_id, sitediagrams.IMAGE_IDS) + self.assertGreater(found, 0) + + def test_a_block_placeholder_becomes_a_figure(self): + page = sitediagrams.resolve_images("Intro\n\n\n\nMore\n") + self.assertIn('
\n") + line = page.splitlines()[2] + self.assertTrue(line.startswith(" list[dict]: + path = ROOT / "docs_assets" / folder / "sources.json" + return json.loads(path.read_text(encoding="utf-8"))["images"] + + def test_every_registry_logo_has_a_local_copy(self): + import yaml + + registry = yaml.safe_load((ROOT / "platforms" / "_registry.yml").read_text())["platforms"] + copies = {entry["platform"]: entry for entry in self._manifest("logos")} + for name, entry in registry.items(): + if not entry.get("logo"): + continue + with self.subTest(platform=name): + self.assertIn(name, copies, "run scripts/fetch_site_images.py --only logos") + self.assertEqual(copies[name]["source"], entry["logo"]) + self.assertTrue((ROOT / "docs_assets" / "logos" / copies[name]["file"]).is_file()) + self.assertTrue(copies[name]["owner"] and copies[name]["licence"]) + + def test_every_recorded_icon_exists(self): + for entry in self._manifest("system-icons"): + with self.subTest(icon=entry["file"]): + self.assertTrue((ROOT / "docs_assets" / "system-icons" / entry["file"]).is_file()) + + def test_remote_links_with_a_copy_are_rewritten(self): + logo = self._manifest("logos")[0] + page = f"| ![X]({logo['source']}){{ width=20 }} | |" + local = sitediagrams.localize_images(page, Path("platforms/x.md")) + self.assertNotIn(logo["source"], local) + self.assertEqual(local.count(f"../assets/logos/{logo['file']}"), 2) + + def test_icon_paths_are_link_safe(self): + icon = self._manifest("system-icons")[0] + local = sitediagrams.localize_images(f"![a]({icon['source']})", Path("systems/x.md")) + self.assertNotIn(" ", local) + + def test_the_validator_refuses_a_remote_image(self): + from validate_site import _parse_page + + with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp: + path = Path(tmp) / "index.html" + path.write_text( + 'x' + 'y' + ) + page = _parse_page(path) + self.assertEqual(page.remote_images, ["https://example.org/a.png"]) + self.assertEqual(page.placeholders, ["nope"]) + + +class SharingCards(unittest.TestCase): + def test_one_card_per_section_at_the_open_graph_size(self): + import struct + + for section in sitediagrams.OG_SECTIONS: + with self.subTest(section=section): + data = (ROOT / "docs_assets" / "og" / f"{section}.png").read_bytes() + self.assertEqual(data[:8], b"\x89PNG\r\n\x1a\n") + self.assertEqual(struct.unpack(">II", data[16:24]), (1200, 630)) + ET.fromstring(sitediagrams.og_card_svg(section, b"png")) + + def test_the_template_names_every_section(self): + template = (ROOT / "docs_assets" / "overrides" / "main.html").read_text(encoding="utf-8") + named = set(re.findall(r"section = '([a-z]+)'", template)) + self.assertEqual(named, set(sitediagrams.OG_SECTIONS)) + + +class ImagesStillNeeded(unittest.TestCase): + """Screens that need a real device are listed, never drawn as a mock-up.""" + + def test_the_list_gives_each_capture_its_alt_text(self): + text = (ROOT / "docs_assets" / "images-needed.md").read_text(encoding="utf-8") + entries = re.findall(r"(?m)^## ", text) + self.assertGreater(len(entries), 3) + self.assertEqual(len(entries), len(re.findall(r"(?m)^\*\*Alt text\*\*", text))) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_site_render.py b/tests/test_site_render.py index 8f7dd3b1..78d9aeb5 100644 --- a/tests/test_site_render.py +++ b/tests/test_site_render.py @@ -1,15 +1,10 @@ -"""Renderings shared by the site pages, and the icon cache they read. - -No network: icon checks are answered by a stub. -""" +"""Renderings shared by the site pages, and the icon copies they read.""" from __future__ import annotations -import json import sys import tempfile import unittest -import urllib.error from pathlib import Path from unittest import mock @@ -19,65 +14,32 @@ sys.path.insert(0, str(REPO_ROOT / "scripts")) import siterender # noqa: E402 -class _Served: - status = 200 - - def __enter__(self) -> "_Served": - return self - - def __exit__(self, *exc: object) -> None: - return None - - -class IconCacheIsReplacedWhole(unittest.TestCase): - """The cache was truncated then dumped in place: a concurrent reader saw - half a file, took it for empty, and an offline run then wrote ``{}`` - over every recorded icon.""" +class IconsComeFromTheLocalCopies(unittest.TestCase): + """Icons were checked against upstream with a HEAD request per system on + every build, and linked from a branch tip. The build now reads the copies + in docs_assets/system-icons and makes no request.""" def setUp(self) -> None: - tmp = tempfile.TemporaryDirectory() - self.addCleanup(tmp.cleanup) - self.path = Path(tmp.name) / "system_icons.json" - patcher = mock.patch.object(siterender, "ICON_CACHE_PATH", self.path) - patcher.start() - self.addCleanup(patcher.stop) saved = dict(siterender._icon_available) self.addCleanup(lambda: (siterender._icon_available.clear(), siterender._icon_available.update(saved))) - def test_an_offline_run_leaves_an_unreadable_cache_untouched(self): - self.path.write_text('{"Sega - Mega Drive": tr') - offline = mock.patch.object( - siterender.urllib.request, "urlopen", - side_effect=urllib.error.URLError("offline")) - with offline, mock.patch("sys.stderr"), mock.patch("builtins.print"): - siterender.prime_system_icons({"Sega - Mega Drive"}) - self.assertEqual(self.path.read_text(), '{"Sega - Mega Drive": tr') - - def test_names_another_run_recorded_meanwhile_are_kept(self): - self.path.write_text(json.dumps({"A - One": True})) - - def answer(*_args: object, **_kwargs: object) -> _Served: - # A second build records its own check while this one runs. - self.path.write_text(json.dumps({"A - One": True, "C - Three": False})) - return _Served() - - with mock.patch.object(siterender.urllib.request, "urlopen", side_effect=answer), \ + def test_availability_is_the_folder(self): + tmp = tempfile.TemporaryDirectory() + self.addCleanup(tmp.cleanup) + folder = Path(tmp.name) / "docs_assets" / "system-icons" + folder.mkdir(parents=True) + (folder / "A - One.png").write_bytes(b"png") + with mock.patch.object(siterender, "ICON_DIR", folder), \ mock.patch("builtins.print"): siterender.prime_system_icons({"A - One", "B - Two"}) - self.assertEqual(json.loads(self.path.read_text()), - {"A - One": True, "B - Two": True, "C - Three": False}) - self.assertEqual(sorted(p.name for p in self.path.parent.iterdir()), - ["system_icons.json"]) + self.assertTrue(siterender._icon_available["A - One"]) + self.assertFalse(siterender._icon_available["B - Two"]) + self.assertEqual(siterender.system_icon_markdown("B", "Two"), "") + self.assertIn("A%20-%20One.png", siterender.system_icon_markdown("A", "One")) - def test_the_write_goes_through_a_scratch_file(self): - with mock.patch.object(siterender.urllib.request, "urlopen", - return_value=_Served()), \ - mock.patch.object(siterender, "write_text_atomic", - wraps=siterender.write_text_atomic) as atomic, \ - mock.patch("builtins.print"): - siterender.prime_system_icons({"B - Two"}) - atomic.assert_called_once() + def test_no_request_is_made(self): + self.assertNotIn("urllib.request", (REPO_ROOT / "scripts" / "siterender.py").read_text()) class EveryPageAddressesPlatformsAsPeers(unittest.TestCase): diff --git a/wiki/architecture.md b/wiki/architecture.md index dc301edf..afc88564 100644 --- a/wiki/architecture.md +++ b/wiki/architecture.md @@ -106,26 +106,7 @@ MAME/FBNeo hashes, verify, packs, install manifests, target manifests, consistency check, pack integrity, README, site. See [tools](tools.md) for the full pipeline reference. -```mermaid -graph LR - A[generate_db] --> A2[provenance report] - A2 --> B[refresh_data_dirs] - B --> C[MAME/FBNeo hashes] - C --> D[verify --all] - D --> E[generate_pack --all] - E --> F[install manifests] - F --> G[target manifests] - G --> H[consistency check] - H --> H2[pack integrity] - H2 --> I[generate_readme] - I --> J[generate_site] - - style A fill:#2d333b,stroke:#adbac7,color:#adbac7 - style D fill:#2d333b,stroke:#adbac7,color:#adbac7 - style E fill:#2d333b,stroke:#adbac7,color:#adbac7 - style H2 fill:#2d333b,stroke:#adbac7,color:#adbac7 - style J fill:#2d333b,stroke:#adbac7,color:#adbac7 -``` + ## Three layers of data @@ -168,20 +149,7 @@ platform itself declares: the export, the truth diff and the site's comparison tables. Counted as the platform's, they were written into System.dat as if libretro had declared them. -```mermaid -graph TD - PY[Platform YAML
scraped from upstream] --> PG[Pack generation] - EP[Emulator profiles
source-verified] --> PG - SH[_shared.yml
curated shared files] --> PY - SH --> EP - PG --> ZIP[ZIP pack per platform] - - style PY fill:#2d333b,stroke:#adbac7,color:#adbac7 - style EP fill:#2d333b,stroke:#adbac7,color:#adbac7 - style SH fill:#2d333b,stroke:#adbac7,color:#adbac7 - style PG fill:#2d333b,stroke:#adbac7,color:#adbac7 - style ZIP fill:#2d333b,stroke:#adbac7,color:#adbac7 -``` + ## The inverted model diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index fcb8793f..00774d9b 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -47,7 +47,7 @@ Every module in `tests/`, by the area it guards. | Handheld port | stubbed PortMaster dialogs, fake installer | `test_port` | | 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_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` | +| 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_images`, `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/verification-modes.md b/wiki/verification-modes.md index 112cc9ba..19f39e42 100644 --- a/wiki/verification-modes.md +++ b/wiki/verification-modes.md @@ -3,6 +3,7 @@ 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. + ## Existence mode