feat(switch): add initial Nintendo Switch homebrew host#175
Open
zerob13 wants to merge 10 commits into
Open
Conversation
zerob13
marked this pull request as ready for review
July 24, 2026 10:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.nroHostOps, andpocketjs-coreRuntime architecture
The Switch host is split across a thin libnx C shell and a linked
no_stdRust static library.The C shell owns platform integration:
The Rust library owns the PocketJS runtime:
globalThis.ui/HostOpssurfacepocketjs-coreThe existing 480x272 logical viewport is rasterized at density 2 and centered as a 960x544 image inside the Switch framebuffer. Switch A/B/X/Y map to the existing PocketJS Circle/Cross/Triangle/Square contract, while the D-pad, shoulders, Plus, Minus, and left stick preserve the established console input model.
Each NRO embeds
app.jsandapp.pakunderromfs:/pocketjs/. Manifest title and version fields are forwarded to NACP metadata.Why
ffi.rsandpak.rsmovedffi.rsandpak.rspreviously lived underhosts/vita/src, but neither module is inherently Vita-specific.ffi.rsimplements the native QuickJSHostOpsboundary: argument decoding, UI operations, textures, sprites, debug hooks, runtime identity, and optional application switching.pak.rsparses the PocketJS pak format and feeds styles, fonts, images, sprites, and tiles intopocketjs-core.Keeping these modules inside the Vita host would either make the Switch runtime depend on a sibling platform host or require a second copy of the same ABI and resource-loading logic. Both choices would allow Vita and Switch behavior to drift.
The files therefore move, with Git history preserved, to
hosts/native/. Vita and Switch include the same sources and provide their own platform hooks throughgraphics,dbg,switch, andhost_log.The move is detected as a 97% rename for both files. The remaining changes only neutralize Vita-specific names and comments, such as replacing
vita_logwithhost_log. Rendering, input, framebuffer ownership, lifecycle, and platform services remain inside their respective host directories.This makes the native QuickJS boundary and pak semantics a single source of truth while keeping platform integration isolated.
Build and developer workflow
The manifest resolver now exposes a truthful
switchtarget profile and allows integer-fit presentation with centered letterboxing. Host build inputs also carry the application title and version required by NACP packaging.The new workflows are:
bun pocket build \ --target switch \ --manifest apps/hero/pocket.json \ --project-root . \ -- \ --release bun switch hero --release bun play switch heroThe backend builds QuickJS and the Rust runtime for
aarch64-nintendo-switch-freestanding, links them through devkitA64/libnx, constructs RomFS explicitly, and emits:No
vapor/runtime/switchbackend is added. Switch is a QuickJS guest host, not an AOT Pocket Vapor target. Vue Vapor applications already use the shared framework runtime andHostOps, so a second Switch-specific Vapor runtime would duplicate platform semantics rather than add support.Validation
bun run testrepository gate passes after merging the current upstreammaingit diff --checkCurrent scope
This is an initial emulator-verified port. It does not yet claim real-hardware acceptance, native touchscreen input, right analog, motion, rumble, audio, networking, or automated Ryujinx capture.
Each NRO currently packages one guest. The Pocket Launcher multi-guest lifecycle is not implemented for Switch.
Ryujinx
Café, Chrome, Hero Vue Vapor, Now Playing, Gallery, and Zoom Lab were exercised in Ryujinx 1.3.3. These runs cover controller interaction, Solid and Vue Vapor guests, text and layout, images and sprites, clipping, transforms, and scaling. The captured runs present at approximately 60 FPS.