Skip to content

feat(switch): add initial Nintendo Switch homebrew host#175

Open
zerob13 wants to merge 10 commits into
pocket-stack:mainfrom
zerob13:main
Open

feat(switch): add initial Nintendo Switch homebrew host#175
zerob13 wants to merge 10 commits into
pocket-stack:mainfrom
zerob13:main

Conversation

@zerob13

@zerob13 zerob13 commented Jul 24, 2026

Copy link
Copy Markdown

Summary

  • add Nintendo Switch as a first-class Host ABI 4 target
  • package a manifest-resolved PocketJS guest as a homebrew .nro
  • run the existing PSP-compatible application surface through QuickJS, HostOps, and pocketjs-core
  • support Solid, Vue Vapor JSX, and Vue SFC guests without Switch-specific application code
  • add controller and left-analog input, including the framework's analog-driven virtual cursor
  • add manifest-driven build, packaging, and Ryujinx launch commands
  • document the devkitPro toolchain, packaging layout, emulator setup, and current limitations

Runtime architecture

The Switch host is split across a thin libnx C shell and a linked no_std Rust static library.

The C shell owns platform integration:

  • application startup and shutdown
  • RomFS access
  • controller polling and button mapping
  • the 1280x720 linear framebuffer
  • presentation through the libnx applet loop

The Rust library owns the PocketJS runtime:

  • QuickJS initialization and guest execution
  • the complete globalThis.ui / HostOps surface
  • pak loading and resource registration
  • pocketjs-core
  • deterministic density-2 software rasterization
  • pending JavaScript job execution and guest lifecycle cleanup

The 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.js and app.pak under romfs:/pocketjs/. Manifest title and version fields are forwarded to NACP metadata.

Why ffi.rs and pak.rs moved

ffi.rs and pak.rs previously lived under hosts/vita/src, but neither module is inherently Vita-specific.

  • ffi.rs implements the native QuickJS HostOps boundary: argument decoding, UI operations, textures, sprites, debug hooks, runtime identity, and optional application switching.
  • pak.rs parses the PocketJS pak format and feeds styles, fonts, images, sprites, and tiles into pocketjs-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 through graphics, dbg, switch, and host_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_log with host_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 switch target 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 hero

The backend builds QuickJS and the Rust runtime for aarch64-nintendo-switch-freestanding, links them through devkitA64/libnx, constructs RomFS explicitly, and emits:

dist/switch/<app.output>.nro

No vapor/runtime/switch backend is added. Switch is a QuickJS guest host, not an AOT Pocket Vapor target. Vue Vapor applications already use the shared framework runtime and HostOps, so a second Switch-specific Vapor runtime would duplicate platform semantics rather than add support.

Validation

  • full bun run test repository gate passes after merging the current upstream main
  • all 18 Switch-admitted repository demos produce release NROs
  • focused Switch contract, host-input, CLI, and npm-package suite: 39 passed, 244 assertions
  • controller interaction verified in Ryujinx
  • git diff --check

Current 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.

pocketjs-switch-ryujinx

@zerob13
zerob13 marked this pull request as ready for review July 24, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant