Ship Switch releases as an SD-ready zip only.

Players extract one zip at the microSD root for install and update; saves under pokemon-love2d/ survive merge. Drop the bare .nro from GitHub Release assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Andrew Quenehen
2026-08-03 09:04:25 -03:00
parent eeb8c89d3d
commit d7581dbece
11 changed files with 328 additions and 61 deletions
+7 -12
View File
@@ -1,7 +1,7 @@
name: Release name: Release
# Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS # Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS
# IPA, a Nintendo Switch fused NRO (experimental), and the Anbernic RG34XXSP # IPA, a Nintendo Switch SD-ready zip (experimental), and the Anbernic RG34XXSP
# (Stock OS 64-bit MOD / PortMaster) port on the self-hosted Mac runner, and # (Stock OS 64-bit MOD / PortMaster) port on the self-hosted Mac runner, and
# publishes them as a GitHub Release. # publishes them as a GitHub Release.
# #
@@ -270,13 +270,11 @@ jobs:
[ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; } [ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; }
cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa" cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa"
nro="dist/switch/gen1recomp-${v}-switch.nro" swzip="dist/switch/gen1recomp-${v}-switch.zip"
[ -f "$nro" ] || { echo "::error::$nro not found (expected from scripts/build_switch.sh --fused)"; exit 1; } [ -f "$swzip" ] || { echo "::error::$swzip not found (expected from scripts/build_switch.sh --fused → pack_sd_zip.sh)"; exit 1; }
cp "$nro" "$outdir/gen1recomp-${v}-switch.nro" cp "$swzip" "$outdir/gen1recomp-${v}-switch.zip"
# Sidecar written by build_fused.sh when the fused NRO succeeds. # Local fused .nro stays under dist/switch/ for PR CI / debug; release
if [ -f "${nro}.sha256" ]; then # publishes the SD-ready zip only.
cp "${nro}.sha256" "$outdir/gen1recomp-${v}-switch.nro.sha256"
fi
# Anbernic handheld port (suffix names the CFW it targets, so a # Anbernic handheld port (suffix names the CFW it targets, so a
# future RG35XX/other-CFW pack can ship alongside it). # future RG35XX/other-CFW pack can ship alongside it).
@@ -395,14 +393,11 @@ jobs:
"dist/release/gen1recomp-${v}-linux.zip" "dist/release/gen1recomp-${v}-linux.zip"
"dist/release/gen1recomp-${v}-android.apk" "dist/release/gen1recomp-${v}-android.apk"
"dist/release/gen1recomp-${v}-ios.ipa" "dist/release/gen1recomp-${v}-ios.ipa"
"dist/release/gen1recomp-${v}-switch.nro" "dist/release/gen1recomp-${v}-switch.zip"
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip" "dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip"
"dist/release/gen1recomp-${v}.love" "dist/release/gen1recomp-${v}.love"
"dist/release/sha256sums.txt" "dist/release/sha256sums.txt"
) )
if [ -f "dist/release/gen1recomp-${v}-switch.nro.sha256" ]; then
release_files+=("dist/release/gen1recomp-${v}-switch.nro.sha256")
fi
gh release create "$tag" \ gh release create "$tag" \
--target "$GITHUB_SHA" \ --target "$GITHUB_SHA" \
+3 -3
View File
@@ -231,15 +231,15 @@ Install steps, controls, and troubleshooting live in
## Nintendo Switch ## Nintendo Switch
Releases ship a fused `gen1recomp-*-switch.nro` (issue Releases ship an SD-ready `gen1recomp-*-switch.zip` (issue
[#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Runtime target [#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Runtime target
is pinned [love-nx](https://github.com/retronx-team/love-nx) `11.5-nx1`. is pinned [love-nx](https://github.com/retronx-team/love-nx) `11.5-nx1`.
Requires a console that can run Switch homebrew. Hardware evidence: **OLED** Requires a console that can run Switch homebrew. Hardware evidence: **OLED**
(author) and **V1 / Erista** boot (community). (author) and **V1 / Erista** boot (community).
- Players: [docs/switch-install.md](docs/switch-install.md) — download the - Players: [docs/switch-install.md](docs/switch-install.md) — download the
NRO, copy to the SD, title-override launch, import your own legal ROM, zip, extract at the microSD root (install or update), title-override
Joy-Con controls and shortcuts. launch, import your own legal ROM, Joy-Con controls and shortcuts.
- Builders: [docs/switch-build.md](docs/switch-build.md) — `--fetch` / - Builders: [docs/switch-build.md](docs/switch-build.md) — `--fetch` /
`--loose` / `--fused`, toolchain, Docker fallback, and **CI vs release** `--loose` / `--fused`, toolchain, Docker fallback, and **CI vs release**
(path-gated ubuntu selftest, canonical fused PR artifact, release hard-fail). (path-gated ubuntu selftest, canonical fused PR artifact, release hard-fail).
+11 -8
View File
@@ -1,14 +1,14 @@
# Build the Nintendo Switch NRO — contributor guide # Build the Nintendo Switch NRO — contributor guide
Want to play a release build instead? Download the fused NRO and copy it to Want to play a release build instead? Download the SD-ready zip and extract it
your console — see [switch-install.md](switch-install.md). at your microSD root — see [switch-install.md](switch-install.md).
This guide is for contributors who build Gen1Recomp for Switch from source. This guide is for contributors who build Gen1Recomp for Switch from source.
Hardware evidence, MTP operator loops, and deeper notes live in Hardware evidence, MTP operator loops, and deeper notes live in
[switch-development.md](switch-development.md). [switch-development.md](switch-development.md).
> Releases ship a fused `gen1recomp-*-switch.nro` (issue > Releases ship `gen1recomp-*-switch.zip` (SD tree under `switch/gen1recomp/`;
> [#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Hardware > issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Hardware
> evidence: **OLED** (author) and **V1 boot** (community). See > evidence: **OLED** (author) and **V1 boot** (community). See
> [switch-development.md](switch-development.md) for known limitations. > [switch-development.md](switch-development.md) for known limitations.
@@ -64,7 +64,7 @@ builds can fall back to the pinned image when native tools are missing.
| ---- | ------------ | | ---- | ------------ |
| `--fetch` | Downloads pinned **love.nro** + **love.elf** into `.bazinga/love-nx/11.5-nx1/` and verifies SHA-256 against `scripts/switch/love-nx-11.5-nx1.sha256`. | | `--fetch` | Downloads pinned **love.nro** + **love.elf** into `.bazinga/love-nx/11.5-nx1/` and verifies SHA-256 against `scripts/switch/love-nx-11.5-nx1.sha256`. |
| `--loose` | Packs `game.love`, copies pinned `love.nro` → `dist/switch/loose/` as `gen1recomp.nro` + `game.love` side by side. Needs the pin. | | `--loose` | Packs `game.love`, copies pinned `love.nro` → `dist/switch/loose/` as `gen1recomp.nro` + `game.love` side by side. Needs the pin. |
| `--fused` | Builds a single `dist/switch/gen1recomp-<ver>-switch.nro` (game in romfs) via `nacptool` + `elf2nro`. Needs the pin + toolchain (native or Docker). | | `--fused` | Builds `dist/switch/gen1recomp-<ver>-switch.nro` (game in romfs) via `nacptool` + `elf2nro`, then packs `dist/switch/gen1recomp-<ver>-switch.zip` (SD-ready tree). Needs the pin + toolchain (native or Docker). GitHub Releases publish the **zip only**. |
Rules: Rules:
@@ -109,12 +109,13 @@ scripts/build_switch.sh --fetch
# Loose pair for iteration (fetch + assemble) # Loose pair for iteration (fetch + assemble)
scripts/build_switch.sh --fetch --loose scripts/build_switch.sh --fetch --loose
# Single fused NRO for a release-like artifact # Single fused NRO + SD-ready zip for a release-like artifact
scripts/build_switch.sh --fetch --fused --version 0.2.0 scripts/build_switch.sh --fetch --fused --version 0.2.0
``` ```
Outputs land under `dist/switch/` (and `dist/switch/loose/` for loose mode). Outputs land under `dist/switch/` (and `dist/switch/loose/` for loose mode).
The fused path also writes `gen1recomp-<ver>-switch.nro.sha256`. The fused path also writes `gen1recomp-<ver>-switch.nro.sha256` and
`gen1recomp-<ver>-switch.zip` (+ `.sha256` sidecar for the zip).
Offline packaging smoke (no network, no nacptool required): Offline packaging smoke (no network, no nacptool required):
@@ -164,7 +165,9 @@ other platforms — this is a **hard gate** (no `continue-on-error`):
scripts/build_switch.sh --fetch --fused --version "<release version>" scripts/build_switch.sh --fetch --fused --version "<release version>"
``` ```
A Switch packaging failure fails the entire release job. A Switch packaging failure fails the entire release job. The release asset is
`gen1recomp-<ver>-switch.zip` (SD-ready); the versioned `.nro` stays under
`dist/switch/` for the packer and for PR CI artifacts.
### Runner provisioning ### Runner provisioning
+6 -5
View File
@@ -1,8 +1,9 @@
# Nintendo Switch development (love-nx) # Nintendo Switch development (love-nx)
> Fused NRO support for issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531). > Fused NRO support for issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531).
> Releases ship `gen1recomp-*-switch.nro`. Console copy is manual; title override > Releases ship `gen1recomp-*-switch.zip` (SD-ready tree). Console copy is
> required. See [Known limitations](#known-limitations-read-before-reviewing). > extract/merge at microSD root; title override required. See
> [Known limitations](#known-limitations-read-before-reviewing).
**Canonical install / build / transfer docs** (start here unless you need hardware depth): **Canonical install / build / transfer docs** (start here unless you need hardware depth):
@@ -27,9 +28,9 @@ the transfer runbook).
| ---- | ----- | | ---- | ----- |
| Feature | **Available** — playable fused NRO path (issue #531) | | Feature | **Available** — playable fused NRO path (issue #531) |
| Runtime | Pinned love-nx **`11.5-nx1`** | | Runtime | Pinned love-nx **`11.5-nx1`** |
| Product artifact | Single fused `gen1recomp.nro` (game in romfs); loose `nro`+`game.love` for iteration | | Product artifact | Releases: SD-ready `gen1recomp-*-switch.zip`; local/PR: fused `.nro`; loose `nro`+`game.love` for iteration |
| Hardware | **OLED** validated (author, title override); **V1 / Erista** boot confirmed (community). Lite, docked soak, and Pro Controller matrices welcome | | Hardware | **OLED** validated (author, title override); **V1 / Erista** boot confirmed (community). Lite, docked soak, and Pro Controller matrices welcome |
| Deploy / install | Releases publish fused NRO; **console copy is manual** (MTP / SD / FTP — [switch-transfer.md](switch-transfer.md)); no `nxlink` path yet | | Deploy / install | Releases publish SD-ready zip; **extract/merge at microSD root** (MTP / SD / FTP — [switch-transfer.md](switch-transfer.md)); no `nxlink` path yet |
| Contributor transfer | Documented for **macOS, Linux, and Windows**; OpenMTP on Mac is one example, not the only contract | | Contributor transfer | Documented for **macOS, Linux, and Windows**; OpenMTP on Mac is one example, not the only contract |
| Network features on NX | Self-update / remote mod download **disabled** (`networkValidated == false`) | | Network features on NX | Self-update / remote mod download **disabled** (`networkValidated == false`) |
| Community help | Welcome — especially HOS / love-nx packaging and broader hardware coverage | | Community help | Welcome — especially HOS / love-nx packaging and broader hardware coverage |
@@ -165,7 +166,7 @@ Detail for **macOS / Linux / Windows** and **MTP / SD / FTP** lives in
| Layer | Intent | | Layer | Intent |
| ----- | ------ | | ----- | ------ |
| **Runtime / players** | Put the NRO under `sdmc:/switch/gen1recomp/` and land ROMs/mods under the save-dir inboxes. The game does not hard-depend on OpenMTP or macOS. | | **Runtime / players** | Extract the release zip at microSD root (`switch/gen1recomp/`) and land ROMs/mods under the save-dir inboxes. The game does not hard-depend on OpenMTP or macOS. |
| **Contributor loop** | Manual copy via MTP (DBI responder), direct SD (Hekate UMS / reader), or FTP. Fully manual — no CI deploy, no `nxlink` yet. | | **Contributor loop** | Manual copy via MTP (DBI responder), direct SD (Hekate UMS / reader), or FTP. Fully manual — no CI deploy, no `nxlink` yet. |
The Mac + OpenMTP steps that remain below are the **OLED evidence reproduction** path; prefer the transfer runbook for day-to-day contrib on other hosts. The Mac + OpenMTP steps that remain below are the **OLED evidence reproduction** path; prefer the transfer runbook for day-to-day contrib on other hosts.
+1 -1
View File
@@ -127,7 +127,7 @@ Operator smoke for the switch-build-pipeline packaging CLI (closes matrix-deferr
| love-nx | `11.5-nx1` (manifest checksums match) | | love-nx | `11.5-nx1` (manifest checksums match) |
| Artifact | `dist/switch/gen1recomp-0.0.0-test-switch.nro` | | Artifact | `dist/switch/gen1recomp-0.0.0-test-switch.nro` |
| NRO SHA-256 | `210efb884a8d27443dc1c64ed8f071b0f862d8d0c9b140ad8185093c4e4027db` | | NRO SHA-256 | `210efb884a8d27443dc1c64ed8f071b0f862d8d0c9b140ad8185093c4e4027db` |
| Install doc | `docs/switch-install.md` — copy NRO under `sdmc:/switch/gen1recomp/` | | Install doc | `docs/switch-install.md` at the time of this row: copy NRO under `sdmc:/switch/gen1recomp/` (releases now ship an SD-ready zip; same folder) |
| Console | Switch OLED | | Console | Switch OLED |
| Operator | Andrew | | Operator | Andrew |
| Date | 2026-08-01 | | Date | 2026-08-01 |
+26 -20
View File
@@ -1,8 +1,9 @@
# Install Gen1Recomp on Nintendo Switch # Install Gen1Recomp on Nintendo Switch
Every GitHub Release that includes Switch support ships a fused homebrew Every GitHub Release that includes Switch support ships an SD-ready zip:
binary: `gen1recomp-*-switch.nro`. Copy it to your microSD, launch with `gen1recomp-*-switch.zip`. Extract it at the root of your microSD (install
**title override**, then import your own legal `.gb` ROM. **or** update — same steps), launch with **title override**, then import your
own legal `.gb` ROM.
> You need a console that can run Switch homebrew (custom firmware / hbmenu). > You need a console that can run Switch homebrew (custom firmware / hbmenu).
> This project does not help you set that up. Tracks issue > This project does not help you set that up. Tracks issue
@@ -16,30 +17,36 @@ Prefer building from source? See [switch-build.md](switch-build.md).
Port by [andrewqsantos](https://github.com/andrewqsantos). Community testing Port by [andrewqsantos](https://github.com/andrewqsantos). Community testing
help from [booshankles](https://github.com/booshankles). help from [booshankles](https://github.com/booshankles).
## 1. Download the NRO ## 1. Download the zip
1. Open 1. Open
[Releases](https://github.com/bryanthaboi/gen1recomp/releases). [Releases](https://github.com/bryanthaboi/gen1recomp/releases).
2. Download `gen1recomp-*-switch.nro` for the version you want. 2. Download `gen1recomp-*-switch.zip` for the version you want.
(Optional: the matching `*.nro.sha256` sidecar if you want to verify the (Optional: verify against `sha256sums.txt` in the same release.)
download.)
## 2. Copy it to the microSD ## 2. Extract onto the microSD
Put the file here on the SD card: Extract the zip at the **root** of the microSD so you get:
```text ```text
sdmc:/switch/gen1recomp/gen1recomp.nro sdmc:/switch/gen1recomp/gen1recomp.nro
sdmc:/switch/gen1recomp/pokemon-love2d/imports/
sdmc:/switch/gen1recomp/pokemon-love2d/imports/mods/
sdmc:/switch/gen1recomp/pokemon-love2d/imports/saves/...
``` ```
(or keep the versioned name under `sdmc:/switch/gen1recomp/` — hbmenu will Merge folders if your OS asks. Any method works: **MTP** (DBI → Run MTP
list it either way).
Any method that lands the file on the SD is fine: **MTP** (DBI → Run MTP
responder + a client), **direct SD** (Hekate UMS or a card reader), or **FTP**. responder + a client), **direct SD** (Hekate UMS or a card reader), or **FTP**.
Exit MTP / unmount / stop FTP cleanly before launching. Step-by-step for Exit MTP / unmount / stop FTP cleanly before launching. Step-by-step for
macOS, Linux, and Windows: [switch-transfer.md](switch-transfer.md). macOS, Linux, and Windows: [switch-transfer.md](switch-transfer.md).
### Updating
Use the **same** extract/merge. It replaces `gen1recomp.nro` (and the small
help `README.txt` / `INSTALL.txt` files). Saves, imported ROMs, mods, and
options live under `pokemon-love2d/`**do not delete that folder** when
updating, or you will lose progress.
## 3. Launch with title override ## 3. Launch with title override
**Applet Mode is not supported** for this game (not enough memory). **Applet Mode is not supported** for this game (not enough memory).
@@ -55,15 +62,12 @@ Do **not** launch from the Album applet path for normal play.
This project ships **no** game data. On first launch: This project ships **no** game data. On first launch:
1. Put your own legally obtained Pokémon Red or Blue `.gb` into the ROM 1. Put your own legally obtained Pokémon Red or Blue `.gb` into
inbox under the games save directory (`imports/` the launcher shows `switch/gen1recomp/pokemon-love2d/imports/` (the launcher also shows
the live path). the live save-dir path).
2. Use **Scan again** on the Red/Blue tab if you add the 2. Use **Scan again** on the Red/Blue tab if you add the
file after the first open. file after the first open.
Saves live in the LÖVE save directory and **persist across NRO updates**
you can replace only the `.nro` and keep your progress.
## 5. Import / Export a raw `.sav` ## 5. Import / Export a raw `.sav`
Continue a cart or PC battery save (or pull a slot off-console) via MTP / Continue a cart or PC battery save (or pull a slot off-console) via MTP /
@@ -75,6 +79,8 @@ SD / FTP — same transfer methods as ROMs. Paths are **per game**:
| Blue | `imports/saves/blue/` | `exports/blue/` | | Blue | `imports/saves/blue/` | `exports/blue/` |
| Yellow | `imports/saves/yellow/` | `exports/yellow/` | | Yellow | `imports/saves/yellow/` | `exports/yellow/` |
(Under the save dir `pokemon-love2d/` — the zip already creates these folders.)
1. Copy a Gen1 `.sav` (32 KB) into that games inbox under the save dir 1. Copy a Gen1 `.sav` (32 KB) into that games inbox under the save dir
([switch-transfer.md](switch-transfer.md)). ([switch-transfer.md](switch-transfer.md)).
2. With the games ROM already imported, open **that games tab** 2. With the games ROM already imported, open **that games tab**
@@ -164,7 +170,7 @@ and
## Prefer building it yourself? ## Prefer building it yourself?
Building the fused (or loose) NRO from source is covered in Building the fused NRO (and SD-ready zip) from source is covered in
[switch-build.md](switch-build.md). Copying artifacts and inbox files [switch-build.md](switch-build.md). Copying artifacts and inbox files
(MTP / SD / FTP on macOS, Linux, Windows): [switch-transfer.md](switch-transfer.md). (MTP / SD / FTP on macOS, Linux, Windows): [switch-transfer.md](switch-transfer.md).
Status, limitations, and how we tested: [switch-development.md](switch-development.md). Status, limitations, and how we tested: [switch-development.md](switch-development.md).
+8 -5
View File
@@ -19,7 +19,7 @@ Player install (what to download, title override) stays in
| What | Where on the console | | What | Where on the console |
| ---- | -------------------- | | ---- | -------------------- |
| Fused release NRO | `sdmc:/switch/gen1recomp/gen1recomp.nro` (or versioned name under that folder) | | SD-ready release zip | Extract at microSD **root** `sdmc:/switch/gen1recomp/gen1recomp.nro` plus `pokemon-love2d/` inbox folders. Install and update use the same merge; do **not** delete `pokemon-love2d/` |
| Loose iteration pair | `sdmc:/switch/gen1recomp/gen1recomp.nro` **and** `game.love` beside it | | Loose iteration pair | `sdmc:/switch/gen1recomp/gen1recomp.nro` **and** `game.love` beside it |
| ROM inbox | LÖVE save dir → `imports/` (launcher shows the live `getSaveDirectory()` path; under MTP often `1: SD Card/<save identity>/imports/`) | | ROM inbox | LÖVE save dir → `imports/` (launcher shows the live `getSaveDirectory()` path; under MTP often `1: SD Card/<save identity>/imports/`) |
| Mod zip inbox | Same save dir → `imports/mods/` then MODS → **Scan again** | | Mod zip inbox | Same save dir → `imports/mods/` then MODS → **Scan again** |
@@ -28,7 +28,8 @@ Player install (what to download, title override) stays in
| Opt-in diagnostics | Empty `switch-debug.txt` in the save dir → `switch.log` | | Opt-in diagnostics | Empty `switch-debug.txt` in the save dir → `switch.log` |
| Lua error log | `lua-error.log` in the save dir | | Lua error log | `lua-error.log` in the save dir |
Saves persist across **NRO-only** replacements. Never commit ROM dumps, `.sav` Saves persist across zip re-extract / NRO replacements as long as
`pokemon-love2d/` is left in place. Never commit ROM dumps, `.sav`
files, or third-party mod zips to git. files, or third-party mod zips to git.
--- ---
@@ -51,7 +52,8 @@ hardware evidence — **one contributor example**, not a Mac-only product rule.
1. Quit other MTP clients. 1. Quit other MTP clients.
2. Open OpenMTP → select the DBI device → **`1: SD Card`**. 2. Open OpenMTP → select the DBI device → **`1: SD Card`**.
3. Create `switch/gen1recomp/` if needed; copy NRO (and `game.love` for loose). 3. Create `switch/gen1recomp/` if needed; extract the release zip at SD root
(or copy NRO / `game.love` for loose).
4. For ROMs/mods/saves, open the save-dir `imports/`, `imports/mods/`, 4. For ROMs/mods/saves, open the save-dir `imports/`, `imports/mods/`,
`imports/saves/<red|blue|yellow>/`, or `exports/<red|blue|yellow>/` path the `imports/saves/<red|blue|yellow>/`, or `exports/<red|blue|yellow>/` path the
launcher prints. launcher prints.
@@ -68,7 +70,8 @@ fails to open.
desktops, or your distros KDE MTP stack). desktops, or your distros KDE MTP stack).
2. With DBI MTP active, open **Files** / **Dolphin** / **Thunar** and select 2. With DBI MTP active, open **Files** / **Dolphin** / **Thunar** and select
the Switch / DBI device → **`1: SD Card`**. the Switch / DBI device → **`1: SD Card`**.
3. Copy into `switch/gen1recomp/` and the save-dir inboxes as above. 3. Extract the release zip at SD root (merge), or copy into `switch/gen1recomp/`
and the save-dir inboxes as above.
4. Use **only one** MTP accessor at a time. If `mtp-tools` / `mtpfs` reports 4. Use **only one** MTP accessor at a time. If `mtp-tools` / `mtpfs` reports
“device is busy”, close the file managers MTP mount (or the CLI mount) “device is busy”, close the file managers MTP mount (or the CLI mount)
and retry with a single client. and retry with a single client.
@@ -81,7 +84,7 @@ card reader) or **FTP** instead — same destinations in the table above.
1. With DBI MTP active, open **This PC** / **File Explorer** and look under 1. With DBI MTP active, open **This PC** / **File Explorer** and look under
**Portable Devices** for the Switch / DBI MTP volume → **`1: SD Card`**. **Portable Devices** for the Switch / DBI MTP volume → **`1: SD Card`**.
2. Copy files into `switch\gen1recomp\` and the save-dir inboxes. 2. Copy / extract into `switch\gen1recomp\` and the save-dir inboxes.
3. Optional: [OpenMTP](https://github.com/ganeshrvel/openmtp) on Windows if 3. Optional: [OpenMTP](https://github.com/ganeshrvel/openmtp) on Windows if
Explorer is flaky. Explorer is flaky.
4. If Windows does not show an MTP device: Device Manager → find DBI / Switch 4. If Windows does not show an MTP device: Device Manager → find DBI / Switch
+10 -5
View File
@@ -19,10 +19,13 @@
# (gen1recomp.nro + game.love side by side). Requires the pin # (gen1recomp.nro + game.love side by side). Requires the pin
# (run --fetch first, or combine --fetch --loose). # (run --fetch first, or combine --fetch --loose).
# #
# --fused Build a single gen1recomp-<ver>-switch.nro via nacptool+elf2nro. # --fused Build a single gen1recomp-<ver>-switch.nro via nacptool+elf2nro,
# Uses native tools (PATH or $DEVKITPRO/tools/bin) first; else # then pack dist/switch/gen1recomp-<ver>-switch.zip (SD-ready
# Docker from scripts/switch/dkp-docker.image (override # tree under switch/gen1recomp/). Uses native tools (PATH or
# GEN1_DKP_IMAGE). Requires the pin (run --fetch or combine). # $DEVKITPRO/tools/bin) first; else Docker from
# scripts/switch/dkp-docker.image (override GEN1_DKP_IMAGE).
# Requires the pin (run --fetch or combine). GitHub Releases
# publish the zip only; the versioned .nro stays local / PR CI.
# #
# Combinable: --fetch alone, or --fetch with --loose / --fused. # Combinable: --fetch alone, or --fetch with --loose / --fused.
# XOR: --loose and --fused cannot be used together. # XOR: --loose and --fused cannot be used together.
@@ -46,7 +49,7 @@ say() { printf '\033[1;32m==>\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
usage() { usage() {
sed -n '2,32p' "$0" | sed 's/^# \{0,1\}//' sed -n '2,34p' "$0" | sed 's/^# \{0,1\}//'
} }
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@@ -128,7 +131,9 @@ fi
if [ "$FUSED" -eq 1 ]; then if [ "$FUSED" -eq 1 ]; then
GAME_LOVE="$(pack_game_love)" GAME_LOVE="$(pack_game_love)"
OUT_NRO="$DIST/gen1recomp-${VERSION}-switch.nro" OUT_NRO="$DIST/gen1recomp-${VERSION}-switch.nro"
OUT_ZIP="$DIST/gen1recomp-${VERSION}-switch.zip"
"$ROOT/scripts/switch/build_fused.sh" "$GAME_LOVE" "$VERSION" "$OUT_NRO" "$ROOT/scripts/switch/build_fused.sh" "$GAME_LOVE" "$VERSION" "$OUT_NRO"
"$ROOT/scripts/switch/pack_sd_zip.sh" "$OUT_NRO" "$VERSION" "$OUT_ZIP"
cp "$WORK/build-info.json" "$DIST/gen1recomp-${VERSION}-build-info.json" cp "$WORK/build-info.json" "$DIST/gen1recomp-${VERSION}-build-info.json"
say "done. See $DIST/" say "done. See $DIST/"
exit 0 exit 0
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Pack a Switch SD-ready zip: extract at microSD root (merge-safe update).
#
# Usage:
# scripts/switch/pack_sd_zip.sh NRO_PATH VERSION OUT_ZIP
#
# Layout inside the zip (SD root):
# switch/gen1recomp/gen1recomp.nro
# switch/gen1recomp/INSTALL.txt
# switch/gen1recomp/pokemon-love2d/imports/.../README.txt
# switch/gen1recomp/pokemon-love2d/exports/.../README.txt
#
# Does not ship ROMs, saves, or mods. Re-extracting merges over an existing
# install and only overwrites the NRO + these text placeholders — keep
# pokemon-love2d/ to preserve progress.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
. "$SCRIPT_DIR/common.sh"
NRO_PATH="${1:-}"
VERSION="${2:-}"
OUT_ZIP="${3:-}"
[ -n "$NRO_PATH" ] && [ -n "$VERSION" ] && [ -n "$OUT_ZIP" ] \
|| fail "usage: scripts/switch/pack_sd_zip.sh NRO_PATH VERSION OUT_ZIP"
[ -f "$NRO_PATH" ] || fail "missing NRO at $NRO_PATH"
command -v zip >/dev/null 2>&1 || fail "need zip on PATH"
# Absolutize before any cd — relative OUT_ZIP would otherwise land inside the
# staging dir and vanish when the EXIT trap cleans up.
NRO_PATH="$(cd "$(dirname "$NRO_PATH")" && pwd)/$(basename "$NRO_PATH")"
OUT_DIR="$(dirname "$OUT_ZIP")"
mkdir -p "$OUT_DIR"
OUT_ZIP="$(cd "$OUT_DIR" && pwd)/$(basename "$OUT_ZIP")"
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gen1recomp-sd-zip.XXXXXX")"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
APP_DIR="$STAGE/switch/gen1recomp"
SAVE_ROOT="$APP_DIR/pokemon-love2d"
mkdir -p "$APP_DIR"
cp "$NRO_PATH" "$APP_DIR/gen1recomp.nro"
cat > "$APP_DIR/INSTALL.txt" <<EOF
gen1recomp Switch (v${VERSION})
===============================
First install or update (same steps):
1. Extract this zip at the root of your microSD (merge folders if asked).
2. Do NOT delete switch/gen1recomp/pokemon-love2d/ — that folder holds
your saves, imported ROMs, mods, and options. Re-extracting only
replaces gen1recomp.nro and these help files.
3. Launch with title override (hold R on HOME, open any title → hbmenu).
4. Copy a legal Pokemon Red/Blue .gb into:
switch/gen1recomp/pokemon-love2d/imports/
then use Scan again in the launcher if needed.
Inboxes (drop files here via MTP / SD / FTP):
imports/ — ROM .gb / .gbc
imports/mods/ — community mod .zip
imports/saves/red|blue|yellow/ — raw .sav import
exports/red|blue|yellow/ — pull after Export save
Full guide: https://github.com/bryanthaboi/gen1recomp/blob/main/docs/switch-install.md
EOF
write_readme() {
local path="$1"
local body="$2"
mkdir -p "$(dirname "$path")"
printf '%s\n' "$body" > "$path"
}
write_readme "$SAVE_ROOT/imports/README.txt" \
"Put a legal Pokemon Red or Blue .gb / .gbc here, then Scan again in the launcher."
write_readme "$SAVE_ROOT/imports/mods/README.txt" \
"Put community mod .zip files here, then MODS → Scan again."
write_readme "$SAVE_ROOT/imports/saves/red/README.txt" \
"Put a Red .sav (32 KB) here, then Red tab → SAVE FILES → Import save."
write_readme "$SAVE_ROOT/imports/saves/blue/README.txt" \
"Put a Blue .sav (32 KB) here, then Blue tab → SAVE FILES → Import save."
write_readme "$SAVE_ROOT/imports/saves/yellow/README.txt" \
"Put a Yellow .sav (32 KB) here, then Yellow tab → SAVE FILES → Import save."
write_readme "$SAVE_ROOT/exports/red/README.txt" \
"After Export save (Red), copy the .sav out of this folder via MTP / SD / FTP."
write_readme "$SAVE_ROOT/exports/blue/README.txt" \
"After Export save (Blue), copy the .sav out of this folder via MTP / SD / FTP."
write_readme "$SAVE_ROOT/exports/yellow/README.txt" \
"After Export save (Yellow), copy the .sav out of this folder via MTP / SD / FTP."
rm -f "$OUT_ZIP"
(
cd "$STAGE"
zip -q -r "$OUT_ZIP" switch
)
[ -f "$OUT_ZIP" ] || fail "zip was not created at $OUT_ZIP"
[ -s "$OUT_ZIP" ] || fail "zip is empty: $OUT_ZIP"
LISTING="$(unzip -Z1 "$OUT_ZIP" 2>/dev/null || unzip -l "$OUT_ZIP")"
printf '%s\n' "$LISTING" | grep -q 'switch/gen1recomp/gen1recomp.nro' \
|| fail "zip missing switch/gen1recomp/gen1recomp.nro"
REQUIRED=(
"switch/gen1recomp/INSTALL.txt"
"switch/gen1recomp/pokemon-love2d/imports/README.txt"
"switch/gen1recomp/pokemon-love2d/imports/mods/README.txt"
"switch/gen1recomp/pokemon-love2d/imports/saves/red/README.txt"
"switch/gen1recomp/pokemon-love2d/imports/saves/blue/README.txt"
"switch/gen1recomp/pokemon-love2d/imports/saves/yellow/README.txt"
"switch/gen1recomp/pokemon-love2d/exports/red/README.txt"
"switch/gen1recomp/pokemon-love2d/exports/blue/README.txt"
"switch/gen1recomp/pokemon-love2d/exports/yellow/README.txt"
)
for rel in "${REQUIRED[@]}"; do
printf '%s\n' "$LISTING" | grep -Fq "$rel" || fail "zip missing $rel"
done
ZIP_SHA="$(sha256_file "$OUT_ZIP")"
printf '%s %s\n' "$ZIP_SHA" "$OUT_ZIP" > "${OUT_ZIP}.sha256"
say "SD-ready zip: $OUT_ZIP"
printf '%s %s\n' "$ZIP_SHA" "$OUT_ZIP"
+104 -2
View File
@@ -4,8 +4,8 @@
# Usage: scripts/switch/selftest_build_switch.sh # Usage: scripts/switch/selftest_build_switch.sh
# #
# Covers: sha256_file, --help glossary, XOR loose/fused, fail_need_fetch, # Covers: sha256_file, --help glossary, XOR loose/fused, fail_need_fetch,
# verify_love_nx mismatch, fail_fused_toolchain message. Does not download # verify_love_nx mismatch, fail_fused_toolchain message, pack_sd_zip layout.
# love-nx or invoke nacptool/elf2nro/Docker. # Does not download love-nx or invoke nacptool/elf2nro/Docker.
set -euo pipefail set -euo pipefail
@@ -209,6 +209,108 @@ else
bad "fail_fused_toolchain missing OS hints:$OS_MISSING" bad "fail_fused_toolchain missing OS hints:$OS_MISSING"
fi fi
# ---------------------------------------------------------------------------
# 6. pack_sd_zip.sh builds SD-ready tree (offline; fake NRO)
# ---------------------------------------------------------------------------
FAKE_NRO="$STAGING/fake.nro"
printf 'fake-nro-bytes\n' > "$FAKE_NRO"
FAKE_ZIP="$STAGING/gen1recomp-0.0.0-test-switch.zip"
PACK_RC=0
"$ROOT/scripts/switch/pack_sd_zip.sh" "$FAKE_NRO" "0.0.0-test" "$FAKE_ZIP" \
>"$STAGING/pack.out" 2>"$STAGING/pack.err" || PACK_RC=$?
if [ "$PACK_RC" -eq 0 ] && [ -f "$FAKE_ZIP" ] && [ -s "$FAKE_ZIP" ]; then
ok "pack_sd_zip.sh writes a non-empty zip"
else
bad "pack_sd_zip.sh failed (rc=$PACK_RC err=$(cat "$STAGING/pack.err"))"
fi
ZIP_LIST="$(unzip -Z1 "$FAKE_ZIP" 2>/dev/null || unzip -l "$FAKE_ZIP")"
PACK_MISSING=""
for rel in \
"switch/gen1recomp/gen1recomp.nro" \
"switch/gen1recomp/INSTALL.txt" \
"switch/gen1recomp/pokemon-love2d/imports/README.txt" \
"switch/gen1recomp/pokemon-love2d/imports/mods/README.txt" \
"switch/gen1recomp/pokemon-love2d/imports/saves/red/README.txt" \
"switch/gen1recomp/pokemon-love2d/imports/saves/blue/README.txt" \
"switch/gen1recomp/pokemon-love2d/imports/saves/yellow/README.txt" \
"switch/gen1recomp/pokemon-love2d/exports/red/README.txt" \
"switch/gen1recomp/pokemon-love2d/exports/blue/README.txt" \
"switch/gen1recomp/pokemon-love2d/exports/yellow/README.txt"
do
printf '%s\n' "$ZIP_LIST" | grep -Fq "$rel" || PACK_MISSING="${PACK_MISSING} ${rel}"
done
if [ -z "$PACK_MISSING" ]; then
ok "pack_sd_zip.sh zip contains SD tree + inbox READMEs"
else
bad "pack_sd_zip.sh zip missing:$PACK_MISSING"
fi
EXTRACT_DIR="$STAGING/extract-v1"
rm -rf "$EXTRACT_DIR"
mkdir -p "$EXTRACT_DIR"
unzip -q "$FAKE_ZIP" -d "$EXTRACT_DIR"
if cmp -s "$FAKE_NRO" "$EXTRACT_DIR/switch/gen1recomp/gen1recomp.nro"; then
ok "pack_sd_zip.sh NRO bytes match source"
else
bad "pack_sd_zip.sh NRO inside zip differs from source"
fi
if [ -f "${FAKE_ZIP}.sha256" ]; then
ok "pack_sd_zip.sh writes .sha256 sidecar"
else
bad "pack_sd_zip.sh should write ${FAKE_ZIP}.sha256"
fi
MISSING_NRO_RC=0
"$ROOT/scripts/switch/pack_sd_zip.sh" "$STAGING/does-not-exist.nro" "0.0.0" \
"$STAGING/should-fail.zip" >/dev/null 2>&1 || MISSING_NRO_RC=$?
if [ "$MISSING_NRO_RC" -ne 0 ]; then
ok "pack_sd_zip.sh fails when NRO is missing"
else
bad "pack_sd_zip.sh should fail on missing NRO"
fi
# Relative OUT_ZIP must survive (absolutized before cd into staging)
REL_DIR="$STAGING/rel-out"
mkdir -p "$REL_DIR"
REL_RC=0
(
cd "$REL_DIR"
"$ROOT/scripts/switch/pack_sd_zip.sh" "$FAKE_NRO" "0.0.1" "relative.zip" \
>"$STAGING/rel.out" 2>"$STAGING/rel.err"
) || REL_RC=$?
if [ "$REL_RC" -eq 0 ] && [ -f "$REL_DIR/relative.zip" ] && [ -s "$REL_DIR/relative.zip" ]; then
ok "pack_sd_zip.sh accepts relative OUT_ZIP"
else
bad "pack_sd_zip.sh relative OUT_ZIP failed (rc=$REL_RC err=$(cat "$STAGING/rel.err"))"
fi
# Merge-safe update: second extract replaces NRO, keeps user data
printf 'KEEP-SAVE' > "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/slot.sav"
printf 'KEEP-ROM' > "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/imports/red.gb"
printf 'KEEP-MOD' > "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/imports/mods/mod.zip"
printf 'KEEP-OPTS' > "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/options.lua"
FAKE_NRO2="$STAGING/fake-v2.nro"
printf 'fake-nro-bytes-v2\n' > "$FAKE_NRO2"
FAKE_ZIP2="$STAGING/gen1recomp-0.0.1-test-switch.zip"
"$ROOT/scripts/switch/pack_sd_zip.sh" "$FAKE_NRO2" "0.0.1-test" "$FAKE_ZIP2" \
>"$STAGING/pack2.out" 2>"$STAGING/pack2.err"
unzip -qo "$FAKE_ZIP2" -d "$EXTRACT_DIR"
MERGE_OK=1
cmp -s "$FAKE_NRO2" "$EXTRACT_DIR/switch/gen1recomp/gen1recomp.nro" || MERGE_OK=0
[ "$(cat "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/slot.sav")" = "KEEP-SAVE" ] || MERGE_OK=0
[ "$(cat "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/imports/red.gb")" = "KEEP-ROM" ] || MERGE_OK=0
[ "$(cat "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/imports/mods/mod.zip")" = "KEEP-MOD" ] || MERGE_OK=0
[ "$(cat "$EXTRACT_DIR/switch/gen1recomp/pokemon-love2d/options.lua")" = "KEEP-OPTS" ] || MERGE_OK=0
if [ "$MERGE_OK" -eq 1 ]; then
ok "pack_sd_zip.sh merge update preserves user data"
else
bad "pack_sd_zip.sh merge update lost user data or failed to replace NRO"
fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Summary # Summary
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+24
View File
@@ -172,4 +172,28 @@ do
mustContain(block, "Hard-fail", "release Build Switch comment") mustContain(block, "Hard-fail", "release Build Switch comment")
end end
-- Release publishes SD-ready zip only (no bare .nro / .nro.sha256 assets)
do
local start = release:find("- name: Stage release assets", 1, true)
check(start ~= nil, "release Stage release assets present")
local rest = release:sub(start)
local nextStep = rest:find("\n - name:", 2)
local block = nextStep and rest:sub(1, nextStep - 1) or rest
mustContain(block, "gen1recomp-${v}-switch.zip", "release Stage Switch zip")
mustContain(block, "pack_sd_zip.sh", "release Stage cites pack_sd_zip")
mustNotContain(block, "gen1recomp-${v}-switch.nro", "release Stage no bare NRO")
mustNotContain(block, "switch.nro.sha256", "release Stage no NRO sha256 sidecar")
end
do
local start = release:find("- name: Publish GitHub Release", 1, true)
check(start ~= nil, "release Publish GitHub Release present")
local rest = release:sub(start)
local nextStep = rest:find("\n - name:", 2)
local block = nextStep and rest:sub(1, nextStep - 1) or rest
mustContain(block, "gen1recomp-${v}-switch.zip", "release Publish Switch zip")
mustNotContain(block, "gen1recomp-${v}-switch.nro", "release Publish no bare NRO")
mustNotContain(block, "switch.nro.sha256", "release Publish no NRO sha256 sidecar")
end
T.finish("switch_ci_workflows_test") T.finish("switch_ci_workflows_test")