Co-authored-by: Cursor <cursoragent@cursor.com>
25 KiB
Nintendo Switch development (love-nx)
Status: work in progress — experimental fused NRO on Releases.
Tracks experimental support for issue #531. Expect rough edges, manual console copy, and host-specific contributor tooling.
Canonical install / build / transfer docs (start here unless you need hardware depth):
- Players → switch-install.md
- Builders → switch-build.md (
scripts/build_switch.sh --fetchdownloads the pinned love-nx pair) - Transfer (MTP / SD / FTP on macOS, Linux, Windows) → switch-transfer.md
This document covers what landed so far, known limitations, how hardware was tested, vendor layout, build/deploy, and the contributor transfer loop (detail lives in the transfer runbook).
Current status (honest)
| Area | State |
|---|---|
| Feature completeness | In development — playable P0 path on one console; not finished or release-gated |
| Runtime | Pinned love-nx 11.5-nx1 |
| Product artifact goal | Single fused gen1recomp.nro (game in romfs); loose nro+game.love for iteration |
| Hardware validated | Nintendo Switch OLED only (title override / full memory). Original Switch, Lite, docked mode, and other hosts are untested |
| Deploy / install | Releases publish fused NRO; console copy is still manual (MTP / SD / FTP — 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 |
| Network features on NX | Self-update / remote mod download disabled (networkValidated == false) |
| Community help | Welcome — especially from people familiar with HOS / love-nx / Switch homebrew packaging |
What this branch already does
- Detect
NXviasrc/core/Platform.luawithout reusing Android flags - Writable ROM inbox under
getSaveDirectory()/imports/+ “Procurar novamente” - Joy-Con / gamepad mapping shared by launcher and gameplay (Nintendo A/B UX on NX)
- Focus loss / joystick reconnect recovery; opt-in
switch-debug.txtdiagnostics - Loose assemble + fused NRO build scripts (
scripts/build_switch.sh,scripts/switch/*) - Payload gates so ROM / generated cache / saves never enter
game.love - Community mod zip inbox at
imports/mods/(rescan installs; FIND MODS stays network-gated) - Select+face display chords (COLORS / TILT / pipelines) on Joy-Con
- Hardware evidence for Phase 0 probe, ROM import, naming A/B, save/suspend, fused NRO — see
docs/switch-hardware-evidence.md
What is still unfinished / out of this draft
- Docked vs handheld soak, long-play soak, non-OLED hardware
- Pro Controller / third-party pad matrices beyond the OLED Joy-Con path already measured
- Applet Mode remains unsupported by design (title override required)
nxlink/ netloader contrib fast-loop (deferred — see switch-transfer.md)
Transfer runbooks for Linux/Windows (and SD/FTP alternatives) are in switch-transfer.md. VoxelMod OLED smoke is pass — see NXMOD-12 in switch-hardware-evidence.md.
Design references (Dusklight)
This work borrowed method — not the native stack — from the Dusklight Switch port, especially LESSONS_AND_REUSE.md:
| Dusklight lesson | How Gen1Recomp applied it |
|---|---|
| Emulators hide Tegra failures | Gate milestones on real OLED hardware, not Ryujinx/Yuzu alone |
| Prove the lower layer first | tools/switch-probe before full launcher |
| Know which binary ran | Embedded build-info.json (commit / love-nx tag) |
| Cap continuous logs | Opt-in diagnostics, ≤1 Hz flush; Lua error log rotation |
| Crash symbolization needs the exact ELF | Keep pinned love.elf with the NRO under test |
| Full memory matters | Title override; Applet Mode is not the validation path |
| Do not treat SD FS like desktop POSIX | Lua stays on love.filesystem; inbox + MTP for user files |
| Isolate platform code | Capability module instead of Android flag overload |
NVK / WSI / audren stacks |
Not copied — love-nx already supplies video/audio/input/FS |
Goal for a finished release is closer to Dusklight’s single self-contained .nro, not a permanent Mac-only contributor toolchain.
Known limitations (read before reviewing)
- Transfer is manual and multi-method. Runtime only needs files under the LÖVE save directory / NRO install folder. Use MTP, direct SD, or FTP per switch-transfer.md. macOS + OpenMTP is a documented example for OLED evidence — not “Switch requires a Mac.”
- Deploy is manual. There is no automated push to the console and no
nxlinkpath yet. Operators build locally, transfer files, then title-override launch. - OLED-only evidence. All pass rows in the P0/P1 matrix were recorded on one Switch OLED. Treat other hardware as unknown until someone re-runs the checklist.
- No ROM/save/mod zip bytes in git. Legal dumps and third-party mods stay on the console (or local untracked folders).
- AppleDouble sidecars (
._*) from some MTP clients can break zip/ROM scans — the launcher skips hidden.*names; still prefer clean copies.
How we tested
| Layer | What | Where |
|---|---|---|
| Unit / headless | Platform NX flags, RomImporter inbox, dual-path input, mod zip inbox, display chords, payload/self-tests | tests/*, scripts/test.sh |
| Probe on hardware | getOS()==NX, 1280×720, save path, Joy-Con events |
tools/switch-probe → OLED |
| Integration on hardware | MTP inbox ROM import, Play Red/Blue, naming A/B, quit/reopen save, suspend×10, reboot, fused NRO alone + NRO-only update | docs/switch-hardware-evidence.md |
| Not done yet | Docked soak, ≥30 min long-play, non-OLED, automated/nxlink deploy |
Matrix deferred / absent rows |
Operator evidence must stay in docs/switch-hardware-evidence.md. Do not invent passes for hardware not run.
love-nx 11.5-nx1 (pinned)
Tag: 11.5-nx1
Local layout (not committed):
.bazinga/love-nx/11.5-nx1/
├── love.nro # homebrew launcher binary (loose mode: copied to gen1recomp.nro)
└── love.elf # required for fused NRO builds (devkitPro nacptool/elf2nro)
Manifest: scripts/switch/love-nx-11.5-nx1.sha256 lists expected artifact names and SHA-256 checksums. Checksums are filled when binaries are fetched (TBD_* placeholders until then).
Fetch instructions
Preferred (automated checksum verify):
scripts/build_switch.sh --fetch
That downloads pinned love.nro + love.elf into .bazinga/love-nx/11.5-nx1/
and checks them against scripts/switch/love-nx-11.5-nx1.sha256. See
switch-build.md for the full mode glossary.
Manual fallback:
-
Open the 11.5-nx1 release and download
love.nroandlove.elf. -
Create the directory:
mkdir -p .bazinga/love-nx/11.5-nx1 -
Move both files into that directory.
-
Confirm checksums match the manifest:
shasum -a 256 .bazinga/love-nx/11.5-nx1/love.nro \ .bazinga/love-nx/11.5-nx1/love.elf
Never commit love-nx binaries, ROM dumps, or generated cache into git. The repo .gitignore excludes .bazinga/ (vendor cache) and /dist/ (build output).
Loose-mode dist layout
Development builds place gen1recomp.nro and game.love side by side:
dist/switch/loose/
├── gen1recomp.nro
└── game.love
Assemble with:
scripts/build_switch.sh --loose
(See scripts/switch/assemble_loose.sh for the underlying copy + checksum step.)
Transfer & deploy (current contributor loop)
Detail for macOS / Linux / Windows and MTP / SD / FTP lives in switch-transfer.md. Summary:
| 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. |
| 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.
Still avoided for routine evidence (keeps SD handling honest):
- Treating
nxlink/ netloader as the release deploy story (deferred) - DBI
MicroSD install/NAND install/ NSP-style virtual folders for the.love/.nropair
If MTP fails: check cable, USB port, DBI state, and that only one MTP client holds the device — then retry or switch to SD/FTP. Do not silently rewrite evidence using an untested path and claim parity with recorded SHA-256 round-trips.
Manual deploy checklist (today)
- Build on the contributor host (
scripts/build_switch.sh --looseor fused). - Close Gen1Recomp on the Switch; open DBI →
Run MTP responder. - Copy artifacts with your MTP client into
1: SD Card/switch/gen1recomp/(and ROMs/mods into the save-dir inboxes when needed). - Wait for the transfer queue; refresh; optionally round-trip SHA-256 on first artifacts of a type.
- Exit MTP; launch via title override (hold R on a title → hbmenu, not Applet Mode).
OpenMTP + DBI transfer (loose build, Mac evidence example)
Full multi-OS / multi-method steps: switch-transfer.md. The numbered Mac loop below reproduces the OLED evidence path.
On the Switch
- Close Gen1Recomp if it is running.
- Open DBI from hbmenu.
- Select
Run MTP responder(DBI documentsXon the main screen). - Keep DBI on that screen for the entire transfer.
- Connect the Switch to the Mac with a USB-C data cable.
On the Mac
-
Close any other MTP clients.
-
Open OpenMTP and select the DBI device.
-
In the remote pane, open
1: SD Card. -
Navigate to
switch/and creategen1recomp/if needed. -
Enter
1: SD Card/switch/gen1recomp/. -
Drag from the local pane:
dist/switch/loose/gen1recomp.nro dist/switch/loose/game.love -
Wait for the OpenMTP queue to finish completely.
-
Refresh the remote listing and confirm file sizes match the local files.
-
On the Switch, exit MTP responder normally in DBI before launching the app.
Expected layout on SD:
1: SD Card/
└── switch/
└── gen1recomp/
├── gen1recomp.nro
└── game.love
Round-trip SHA-256 verification
For the first deploy of each artifact type (loose pair, later fused NRO), verify MTP integrity:
-
Before send — record local hashes:
shasum -a 256 dist/switch/loose/gen1recomp.nro \ dist/switch/loose/game.love -
After send — in OpenMTP, copy the same files from
1: SD Card/switch/gen1recomp/back to an empty local folder, e.g.dist/switch/mtp-roundtrip/. -
Compare round-trip hashes:
shasum -a 256 dist/switch/mtp-roundtrip/gen1recomp.nro \ dist/switch/mtp-roundtrip/game.love -
Local pre-send and round-trip hashes must match. Record results in the test report template below.
Repeat whenever a cable glitch or interrupted transfer is suspected.
Title override launch (full memory)
Applet Mode is not the primary validation path. Use title override so hbmenu runs with full memory:
- Confirm the OpenMTP transfer queue finished.
- Exit MTP responder in DBI; disconnect USB if desired.
- Hold
Rwhile launching any legitimately installed title. - Keep holding until hbmenu appears.
- Confirm hbmenu does not show Applet Mode.
- Launch
gen1recomp(or the probe NRO during Phase 0).
Album / applet launches are only useful to document applet-specific limitations; P0/P1 gates use title override.
Phase 0 hardware checklist
Complete in order on OLED hardware. Operator fills evidence fields — leave blank until tested.
| Step | Action | Pass | Evidence / notes |
|---|---|---|---|
| P0-0a | Fetch love-nx 11.5-nx1; record manifest SHA-256 | yes | See scripts/switch/love-nx-11.5-nx1.sha256 |
| P0-0b | Build switch-probe.love per tools/switch-probe/README.md |
yes | |
| P0-0c | Assemble loose probe (game.love = probe) to dist/switch/loose/ |
yes | |
| P0-0d | MTP deploy to 1: SD Card/switch/gen1recomp/; round-trip SHA-256 |
yes | nro 8290ac15…5918f5; love 9f198637…fa2e34f |
| P0-0e | Title override → probe boots; getOS() shows NX |
yes | getOS()=NX, love._os=NX |
| P0-0f | Probe lists 1280×720 (or documented dims), save path, gamepad/touch log | yes | save sdmc:/switch/gen1recomp/switch-probe; Joy-Con Y→#3 X→#4 |
| P0-1a | Replace game.love with unpatched Gen1Recomp build |
yes | feat/switch-nx inbox build |
| P0-1b | MTP replace game.love only; round-trip SHA-256 |
yes | |
| P0-1c | Title override → launcher reaches import screen | yes | |
| P0-1d | Joy-Con: can navigate launcher (no touch-only) | yes | Full report: docs/switch-hardware-evidence.md |
Operator: Andrew Date: 2026-08-01 Console: Switch OLED only
Deploy: manual Mac + OpenMTP + DBI MTP (not automated)
love-nx tag: 11.5-nx1 gen1recomp commit: df7cea4
Phase 0 test report template
Copy this block into your hardware notes or PR evidence. Do not commit ROM files or ROM hashes of private dumps.
## Switch Phase 0 — hardware report
- Operator:
- Date:
- Console model:
- Atmosphère / HOS version:
- gen1recomp commit:
- love-nx tag: 11.5-nx1
- love.nro SHA-256 (local):
- game.love SHA-256 (local, pre-send):
- MTP round-trip SHA-256 (gen1recomp.nro):
- MTP round-trip SHA-256 (game.love):
- Title override used: yes / no
- Applet Mode observed: yes / no (should be no for P0)
- Probe getOS():
- Probe dimensions:
- Probe save directory shown:
- Gamepad events logged: yes / no
- Touch events logged: yes / no
- Unpatched launcher boot: pass / fail
- Joy-Con launcher navigation: pass / fail / not tested
- Notes:
Fast dev loop (loose mode)
While iterating on Lua/assets:
- Edit on Mac; run
scripts/test.sh --quick. - Rebuild
.bazinga/work/game.love(scripts/build.sh mac --no-notarizeor project pack step). - Close Gen1Recomp on Switch.
- DBI →
Run MTP responder. - OpenMTP →
1: SD Card/switch/gen1recomp/. - Replace only
game.love; wait for queue + refresh listing. - Exit MTP responder; launch via title override.
- Keep
gen1recomp.nrounchanged until the love-nx pin changes.
scripts/test.sh --quick
scripts/build.sh mac --no-notarize
scripts/build_switch.sh --loose
shasum -a 256 .bazinga/work/game.love
Controller input mapping (NX)
Measured on Switch OLED (feat/switch-nx, love-nx 11.5-nx1, 1280×720). Both joystickpressed and gamepadpressed fire for Joy-Con; prefer the gamepad path when joystick:isGamepad() is true.
| Path | Control | Mapping |
|---|---|---|
gamepadpressed |
D-pad / left stick | move |
gamepadpressed |
SDL a / b on NX |
swapped via NX_GAMEPAD_BINDINGS: physical A (east) = GB A confirm, physical B (south) = GB B cancel |
gamepadpressed |
SDL a / b on desktop |
identity (SDL south = GB A) |
gamepadpressed |
start / back |
Start / Select |
joystickpressed (raw) |
only if not isGamepad() |
face/menu fallback |
joystickpressed (raw) |
#1 / #2 on NX |
Nintendo B / A → GB B / A |
joystickpressed (raw) |
#9 / #10 |
Select / Start (− / +) |
Nintendo UX on Switch: physical A confirms, physical B cancels (explicit NX remap of SDL face labels).
Dual-path rule: love-nx emits both gamepadpressed and joystickpressed for Joy-Con. When joystick:isGamepad() is true, Input and RomImporter ignore raw face/menu so NamingScreen does not see A+B in one frame. NamingScreen also prefers A over B if both edges still fire.
Implementation: src/core/GamepadMap.lua (NX_RAW_*, ignoreRawForJoystick, displayChordDigit). Launcher and gameplay share the same converter.
Mod zip inbox (NX)
Community mods install from a separate MTP inbox (not mixed into the ROM imports/ scan):
| Item | Value |
|---|---|
| Save-relative path | imports/mods/ |
| MTP destination | 1: SD Card/<save identity>/imports/mods/ (see launcher notice for the live getSaveDirectory() path) |
| Candidates | *.zip only |
| Rescan | MODS tab → Procurar novamente (installs each zip via LauncherMods.installZip; source zips are retained on success and failure) |
| FIND MODS | Remains network-gated / hidden on NX (networkValidated == false) |
Do not commit third-party mod zip bytes into git. Drop the zip over MTP, rescan, enable in MODS, then Play.
MTP tip (esp. macOS clients): OpenMTP/Finder often creates AppleDouble sidecars named ._Something.zip / ._cart.gb. Those are not real archives or ROMs — the launcher ignores hidden .* names under both imports/ and imports/mods/. If install still fails with “could not be opened” / “not a zip file”, delete any ._* under the inbox and confirm the real zip starts with the PK magic (re-copy the release asset if unsure). This is a host-side annoyance of the current manual MTP loop, not something players should need forever.
Example zip source: DramaticShape VoxelMod releases — download a release .zip, copy into imports/mods/, rescan, enable.
Joy-Con display chords (Select + face)
PC digit hotkeys for COLORS / TILT / pipelines have Joy-Con equivalents. Hold Select (back / −) and press a face/shoulder button; the engine runs the same path as Game:keypressed for that digit (including writeOptions / Pipelines parity).
| Chord (Nintendo UX) | Engine key | Typical effect |
|---|---|---|
| Select + A | 2 |
COLORS cycle |
| Select + B | 3 |
TILT / perspective cycle |
| Select + Y | 5 |
GBC FX / V-GRID (mod pipeline) |
| Select + X | 6 |
T-SHIFT / mod pipeline |
| Select + L (left shoulder) | 7 |
V-CURVE / mod pipeline |
Without Select held, face buttons keep normal GB A/B gameplay mapping (no accidental color/tilt cycles). The Options menu remains available for the same settings — chords are optional shortcuts, not the only path.
On NX, A/B chords resolve through the Nintendo UX face remap so physical A → key 2 and physical B → key 3 match this table.
Opt-in diagnostics: create an empty switch-debug.txt in the save directory; events flush to switch.log at ≤1 Hz with build identity (no ROM/save bytes).
Hardware re-test: T16 pass @ 2699c9a (naming A=confirm / B=cancel). T19 pass (quit/reopen, suspend×10, reboot) — operator 2026-08-01.
Suspend/resume audio: after resume, chip music is stopped to avoid duplicate streams; confirm on hardware during P0-09/10 (T19).
Lua error log (save directory)
On any uncaught Lua error, Gen1Recomp appends a redacted trace to lua-error.log in the LÖVE save directory (love.filesystem.getSaveDirectory()). The on-screen error overlay includes a hint pointing at that file. Logs rotate to lua-error.log.1 when the active file exceeds 32 KiB. ROM/save bytes and non-printable data are stripped — never commit or share logs that might contain private paths without reviewing them first.
Native crash triage (love-nx / Atmosphère)
love-nx native faults land under the console’s crash_reports/ folder on SD (reachable via the same manual MTP workflow used for game deploys).
-
Collect — DBI →
Run MTP responder; copysdmc:/crash_reports/*.bin(or the dated subfolder) to the contributor host. Prefer keeping the microSD in-console for routine pulls during this draft. -
Redact — delete any attached screenshots or notes that mention ROM filenames, save paths, or private hashes before sharing logs publicly.
-
Symbolize — use the pinned
love.elffrom.bazinga/love-nx/11.5-nx1/that matchesbuild-info.json/scripts/switch/love-nx-11.5-nx1.sha256. Never use a “latest” download.# Example: aarch64-none-elf-addr2line from devkitPro aarch64-none-elf-addr2line -e .bazinga/love-nx/11.5-nx1/love.elf -f -C 0xADDRESS_FROM_CRASH_REPORT -
Correlate — compare
gitCommit/loveNxTagfrom embeddedbuild-info.jsonwith the operator’s hardware notes.
If addr2line cannot resolve an address, archive the crash .bin with the exact love.elf SHA-256 used for the build — addresses are only meaningful against that ELF.
P0 / P1 hardware matrix (ADR §9)
Operator evidence lives in docs/switch-hardware-evidence.md. Do not invent passes for rows that require hardware not yet run.
| ID | Requirement | Status | Evidence |
|---|---|---|---|
| P0-0a–f | love-nx pin, probe, MTP, title override | pass | Phase 0 checklist above; T4 |
| P0-1a–d | Unpatched launcher boot + Joy-Con nav | pass | T4 / docs/switch-hardware-evidence.md |
| P0-02 | MTP inbox import path shown | pass | T12 |
| P0-03 | Rescan imports ROM | pass | T12 |
| P0-04 | Canonical hash routes version | pass | T12 |
| P0-05 | Source dump retained in inbox | pass | T12 |
| P0-06 | Play reaches game after import | pass | T12 |
| P0-07 | Joy-Con launcher navigation | pass | T16 @ 2699c9a |
| P0-08 | Joy-Con gameplay (incl. naming A/B) | pass | T16 @ 2699c9a |
| P0-09 | Save survives quit + reopen | pass | T19 |
| P0-10 | ≥10 suspend cycles, no stuck input/dup audio | pass | T19 (operator 2026-08-01) |
| P0-12 | Fused NRO boots without adjacent game.love |
pass | T24 — docs/switch-hardware-evidence.md |
| P0-14 | Fused NRO MTP round-trip SHA-256 | pass | T24 — first artifact b019e2e8… @ 6fb5602 (redeploy after Blue fix) |
| P0-15 | Replace NRO only; saves persist | pass | T24 — operator NRO-only update keeps saves |
| P1-01 | Docked vs handheld spot-check | deferred | Not exercised on OLED dock yet |
| P1-02 | Applet Mode documented unsupported | pass | Title override required; Album path not validated |
| P1-03 | Long-play soak (≥30 min) | deferred | No soak session recorded |
| P1-04 | Reboot persistence | pass | T19 |
| P1-05 | Audio resume after suspend | pass | T19 (no dup audio reported) |
| — | Non-OLED hardware (original / Lite) | untested | OLED-only evidence so far |
| — | Automated / nxlink deploy |
absent | Manual MTP / SD / FTP only (AD-009) |
| — | Multi-OS transfer runbooks | pass | switch-transfer.md |
| — | VoxelMod OLED smoke (NXMOD-12) | pass | docs/switch-hardware-evidence.md |
Upstream contribution outline
This draft PR may still be a single large review; maintainers can split later. Suggested review slices:
Each slice should declare: WIP / not finished, no ROM/save bytes committed, love-nx pin with manifest checksums, hardware-tested rows listed (OLED only so far), Applet Mode unsupported, network/updater disabled on NX, deploy still manual (MTP / SD / FTP; no nxlink yet), OpenMTP is one example not the sole contract.
PR 1 — Platform + import (platform/import)
src/core/Platform.lua,conf.luaNX branchsrc/import/RomImporter.lua(NX flags, inbox, scan, shell/updater gates)- Tests:
tests/platform_nx_*,tests/rom_importer_nx_* - Docs: inbox/MTP import sections only
PR 2 — Input + lifecycle (input/lifecycle)
src/core/GamepadMap.lua,Input.lua,main.luafocus/joystick hookssrc/debug/SwitchDiagnostics.lua(opt-in probe + error log)- Tests: input/diagnostics suites
- Docs: controller mapping, suspend/audio notes
PR 3 — Build + docs (build/docs)
scripts/pack_love.sh,scripts/build_switch.sh,scripts/switch/*assets/switch/icon.jpg,docs/switch-development.md, hardware evidence templates- Gates:
pack_love.sh --dry-run,verify_payload.sh --self-test, fused build script (devkitPro host)
Pre-merge checklist (all PRs):
- Manifest
scripts/switch/love-nx-11.5-nx1.sha256filled; binaries not in git verify_payload.shrejects generated cache / ROM /.sav/.bak- P0 matrix rows marked pass only with linked hardware evidence
- Fused NRO P0-12/14/15 pass with T24 evidence (
docs/switch-hardware-evidence.md) - Updater / remote mod download hidden on NX (
networkValidated == false)