From 685945771e28aca83bb95e4dcbcf715d24aab735 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 18:54:01 +0200 Subject: [PATCH] feat: network route and offline line on setups A device setup with no ZIP and a cited network folder gets the command run from a computer into that folder. Every setup with a ZIP links the offline install, the Linux handhelds guide opens with a jump to its download, home and the setup index drop the stray Reference labels, the index lists the handheld tiles, and Get your files answers iPhone and iPad with each app's own steps. --- scripts/generate_site.py | 122 ++++++++++++++++++++++++++++++++++----- scripts/sitemarkup.py | 5 +- 2 files changed, 111 insertions(+), 16 deletions(-) diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 446e91a3..7a73ffe1 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -531,7 +531,7 @@ def generate_home( "", "- BIOS missing, even with the file there? " "[When a game does not start](wiki/troubleshooting.md)", - "- Reference: [Check your file](files/index.md)", + "- Have a file? [Check it](files/index.md)", "", "## Do I need it?", "", @@ -585,9 +585,9 @@ def generate_explore_index(db: dict, coverages: dict, profiles: dict) -> str: "[All installer options](wiki/installer.md)"]), ("How files are checked", [ "Each setup checks its files its own way: by name, by MD5 or by SHA1.", "", - "Reference: [How each platform checks files](wiki/verification-modes.md)"]), + "[How each platform checks files](wiki/verification-modes.md)"]), ("Data and API", ["Every file and hash as JSON, CSV and SQLite.", "", - "Reference: [Data and API](data.md)"]), + "[Data and API](data.md)"]), ]), "## The collection", "", @@ -2699,21 +2699,25 @@ def generate_platform_index(coverages: dict, registry: dict | None = None) -> st lines = [ "# Your setup", "", - "Each setup's page gives the folder, the command and the ZIP.", + "Each setup's page gives the folder and the command.", "", ] - for group, (label, description) in DEVICE_GROUPS.items(): + for group, (label, _description) in DEVICE_GROUPS.items(): members = _group_members(coverages, registry, group) if not members: continue - # One setup: its card says what the group description would. - lines += [f"## {label}", "", *([description, ""] if len(members) > 1 else [])] + # The group label names the devices, and each card carries its + # setup's own sentence. The kinds of device that run a setup + # follow, as on the home tiles. + lines += [f"## {label}", ""] lines += markup.card_grid([ - (f"[{coverages[name]['platform']}]({name}.md)", [GUIDES[name].summary]) - for name in members + *((f"[{coverages[name]['platform']}]({name}.md)", [GUIDES[name].summary]) + for name in members), + *((f"[{tile.name}](../{tile.link})", [tile.text]) + for tile in GROUP_TILES.get(group, ())), ]) lines += [ - f"Reference: [Every setup side by side](../{SETUP_TABLE_LINK})", + f"Comparing setups? [Every setup side by side](../{SETUP_TABLE_LINK})", "", ] return "\n".join(lines) + "\n" @@ -3312,6 +3316,26 @@ SAFE_LINK = "[Is it safe?](../index.md#is-it-safe)" OFFLINE_LINE = ("No internet on the device? " "[Install from the ZIP](../wiki/installer.md#without-a-network-offline-install).") + +def _network_share(name: str, guide: Guide) -> str: + """The network folder a computer fills, for a setup that runs on its own + device and has no ZIP: the command runs on the computer and writes + there. Empty when the page cites no such folder.""" + if guide.run != ("device",) or _computer_first(name, guide): + return "" + for label, path in guide.folders: + if label.startswith("From Windows") and path.startswith("`\\\\"): + return path.strip("`").rstrip("\\") + return "" + + +def _offline_line(name: str) -> list[str]: + """The offline line, on a setup whose ZIP a release publishes: the + installer reads that ZIP with no network (`--from-pack`).""" + pack = release_record.pack_for(name, release_record.load_advertised()) + return [OFFLINE_LINE, ""] if pack and pack.get("assets") else [] + + # Shown in the "On the device" tab of a guide that has no `terminal:` of its own. DEVICE_TERMINAL_FALLBACK = "Or fill the folder from your computer, with the ZIP." @@ -3422,13 +3446,26 @@ def devices_meta(devices: tuple[str, ...]) -> str: HANDHELD_ROW_DEVICES = 6 +# Where a handheld row with no cited folder sends the reader: the setting +# that shows RetroArch's BIOS folder, the words of the RetroArch page. +RETROARCH_FOLDER_SETTING = ("In RetroArch, open **Settings > Directory > System/BIOS**. " + "It shows the folder.") +# After a folder on a part of the card only Linux reads. +LINUX_ONLY_HINT = ("Copy from a Linux computer, or check the folder in RetroArch's " + "**Settings > Directory > System/BIOS**.") + + def _handheld_row(system: HandheldSystem) -> str: shown = ", ".join(system.devices[:HANDHELD_ROW_DEVICES]) more = len(system.devices) - HANDHELD_ROW_DEVICES devices = (f"{shown}{f' and {more} more' if more > 0 else ''} " f"([list]({system.devices_source}))" if system.devices else "") - folder = (f"{system.folder} ([source]({system.folder_source}))" if system.folder - else "Not written by the system. The command finds it on the device.") + if not system.folder: + folder = RETROARCH_FOLDER_SETTING + else: + folder = f"{system.folder} ([source]({system.folder_source}))" + if system.linux_only: + folder += f". {LINUX_ONLY_HINT}" return f"| {system.name} | {devices} | {folder} |" @@ -3452,6 +3489,8 @@ def generate_handheld_guide(guide: HandheldGuide) -> str: "", devices_meta(guide.devices), "", + "[Download](#download)", + "", "## Where the files go { #where-the-files-go }", "", "Put the SD card in a computer. Find your system below.", @@ -3461,6 +3500,7 @@ def generate_handheld_guide(guide: HandheldGuide) -> str: *(_handheld_row(system) for system in guide.systems), "", *_guide_download(guide.uses, display, uses, None, "## Download { #download }"), + *_offline_line(guide.uses), "## Fill the card from a computer { #install }", "", "On Windows, note the card's letter, like **E:**. Put its BIOS folder after " @@ -3506,18 +3546,31 @@ def _platform_guide(name: str, display: str, cov: dict, registry: dict, no_list: ]) if guide.on_card: lines += ["", ""] + share = _network_share(name, guide) if _computer_first(name, guide): lines += [ *_guide_download(name, display, guide, extract_to, "## From your computer { #download }"), - OFFLINE_LINE, + *_offline_line(name), + *_guide_install(name, guide, "## On the device { #install }"), + ] + elif share: + lines += [ + "## From your computer { #from-your-computer }", + "", + "Needs the device on your network. Run the command on the computer.", + "", + *terminal_tabs(("windows",), {"windows": f"--platform {name} --dest {share}"}), + SAFE_LINK, "", *_guide_install(name, guide, "## On the device { #install }"), + *_guide_download(name, display, guide, extract_to, "## Download"), ] else: lines += [ *_guide_install(name, guide, "## Install"), *_guide_download(name, display, guide, extract_to, "## Download"), + *_offline_line(name), ] check_args = {key: f"{args} --check" for key, args in platform_args(name, guide).items()} lines += [ @@ -8559,6 +8612,44 @@ def _folder_tables(registry: dict, record: dict, display: dict[str, str]) -> lis return lines +# iPhone and iPad: no terminal, so each app is filled its own way. Every +# step is the app's own: RetroArch's iOS guide (Files app, On My iPhone > +# RetroArch) and platform_darwin.m (system under Documents/RetroArch), the +# DS BIOS rows of Delta's Settings.storyboard, Provenance's import guide. +IOS_SOURCES = { + "retroarch_docs": "https://github.com/libretro/docs/blob/" + "cfa4c1a17e586c01d3a48e9eda12b6c989f8954f/docs/guides/install-ios.md?plain=1#L94", + "retroarch_code": "https://github.com/libretro/RetroArch/blob/" + "5333a7c5a59e268470834a96f86047d3cb6139a9/frontend/drivers/platform_darwin.m#L438-L459", + "delta": "https://github.com/rileytestut/Delta/blob/" + "c1d3d068e019e6493eed45654569db3cc5beb86a/Delta/Base.lproj/Settings.storyboard#L2448-L2500", + "provenance": "https://wiki.provenance-emu.com/using-provenance/importing-roms", +} +IOS_DEVICES = ("iPhone", "iPad", "iOS", "Delta", "Provenance") + + +def ios_section() -> list[str]: + """The section the iPhone and iPad tile opens: the consoles picked on a + computer, then each app's own way in.""" + return [ + "## iPhone and iPad { #iphone-and-ipad }", + "", + devices_meta(IOS_DEVICES), + "", + "[Pick the consoles](which-pack.md?platform=retroarch#only-some-consoles) " + "on a computer. Copy them to the app.", + "", + "- **RetroArch**: Files app, **On My iPhone > RetroArch**, then " + f"`RetroArch/system/` ([docs]({IOS_SOURCES['retroarch_docs']}), " + f"[code]({IOS_SOURCES['retroarch_code']})).", + "- **Delta**, for DS games: import `bios7.bin`, `bios9.bin` and `firmware.bin` " + f"in **Settings**, under **Nintendo DS** ([code]({IOS_SOURCES['delta']})).", + "- **Provenance**: share the file, then **Copy to Provenance** " + f"([docs]({IOS_SOURCES['provenance']})).", + "", + ] + + def generate_which_pack() -> str: """The device picker: every registered platform as a tile under the device it runs on, the ZIP advice with every folder and ZIP in one closed @@ -8573,7 +8664,7 @@ def generate_which_pack() -> str: lines = [ "# Get your files", "", - "Pick your setup. Its page gives the folder, the command and the ZIP.", + "Pick your setup. Its page gives the folder and the command.", "", "## Pick your setup", "", @@ -8607,6 +8698,7 @@ def generate_which_pack() -> str: "", "", "", + *ios_section(), "## Not in the list? One command finds it { #quick-install }", "", "A **terminal** is a window where you type commands.", @@ -8617,7 +8709,7 @@ def generate_which_pack() -> str: "", "The command asks what to install. Press **Enter** for everything.", "", - "```text title=\"What it asks\"", + "```text title=\"What it asks\" frame=\"terminal\"", " Enter) install everything", " i) only what the installed cores read", " c) choose systems, cores and regions", diff --git a/scripts/sitemarkup.py b/scripts/sitemarkup.py index f6ae72a8..a2a24f2d 100644 --- a/scripts/sitemarkup.py +++ b/scripts/sitemarkup.py @@ -185,7 +185,10 @@ def card_grid(cards: list[tuple[str, list[str]]]) -> list[str]: def initials(name: str) -> str: - """Up to two capitals of a name, for a setup that has no logo: MyMinUI is MM.""" + """Up to two capitals of a name, for a setup that has no logo: MyMinUI is MM. + A name written from a lowercase prefix keeps it: iPhone is iP, not PP.""" + if len(name) > 1 and name[0].islower() and name[1].isupper(): + return name[:2] capitals = [c for c in name if c.isupper()] return "".join(capitals[:2]) or name[:2].upper()