Skip to content
NewAgent3Public
forked from pound-emu/pound

About

Open source emulator for the Nintedo Switch 1 and 2. Highly work in progress (MADE WITH AI!)

Resources

Stars

1 star

Watchers

0 watching

Forks

 
 

Latest commit

 

History

85 Commits

Folders and files

Repository files navigation





Pound

“i think of getting pounded when i see that [name]” – Satisfied Customer

Overview

Pound is an early-stage Nintendo Switch emulator project targeting both:

  • Switch 1 — NVIDIA Maxwell, SM53
  • Switch 2 — NVIDIA Ampere, SM86

The project is currently focused on building small, testable native C11/C++17 foundations. It is not yet a commercial-game or firmware emulator. The current runnable milestone is a Switch 1 NRO/NSO loader plus a deliberately small AArch64 interpreter that can execute synthetic and compatible subset guest code and print observable execution events in the terminal.

Development progress

Overall emulator progress is intentionally conservative: this tracks implemented foundations, not commercial-game compatibility.

Switch 1 bounded NRO/NSO loader    [####################] 100%
Switch 1 ARM64 CPU core           [########------------] 40%
Switch 1 dynamic linking          [##########----------] 50%
Switch 1 guest runtime boundary   [######--------------] 30%
Switch 1 Horizon/kernel runtime   [##------------------] 10%
Switch 1 GPU/display/input        [##------------------] 10%
Switch 1 firmware boot             [--------------------]  0%
Switch 2 SM86 decoder             [######--------------] 30%
Switch 2 IR/SPIR-V lowering       [##############------] 70%
Switch 2 CPU/GPU/runtime           [##------------------] 10%
Commercial game compatibility      [--------------------]  0%

These are engineering milestones rather than benchmark claims; percentages will move as validated subsystems land. A 100% bar means the narrowly named, bounded contract is implemented and tested—not that every production format or compatibility layer is complete.

The first completed bounded milestone is the conservative NRO/NSO loader: container validation, segment mapping, BSS zero-fill, supported raw-LZ4 segment decoding, entrypoint tracking, and malformed-input rejection are covered. The next dynamic-linking milestone now validates same-image local/global/weak symbol lookup and transactional ABS64/GLOB_DAT/JUMP_SLOT relocation application; DT_NEEDED dependency loading and external symbol resolution remain separate work.

Join the Pound Discord Server!

Current phase

Switch 1

The Switch 1 path now includes:

  • A separate lossless SM53/Maxwell instruction frontend for 64-bit instruction words.

  • NRO0 and conservative NSO0 loaders that validate images, map text/rodata/data segments, zero-fill BSS, and track the guest entrypoint. Uncompressed and raw-LZ4-compressed NSO segments are supported with bounded decoding and malformed-input rejection. This bounded loader contract is complete and covered by the test suite. - A small, bounds-checked AArch64 interpreter supporting:

    • NOP
    • B and BL
    • BR and RET
    • CBZ and CBNZ
    • ADR and ADRP
    • MOVZ and MOVK
    • 32-bit and 64-bit ADD/SUB immediate and shifted-register forms, including stack-pointer updates and NZCV flags
    • shifted-register AND/ORR/EOR plus TST flag-setting behavior
    • CSEL/CSINC conditional data selection
    • conditional branches (B.cond) driven by arithmetic flags
    • PC-relative 32-bit/64-bit LDR literal loads
    • byte-safe unsigned-immediate LDR and STR
    • 32-bit and 64-bit STP/LDP pair transfers for common stack prologues/epilogues
    • SVC event dispatch
  • A bounded 2 MiB guest stack reserved after BSS, allowing basic function prologues and local memory traffic.

  • A conservative MOD0 metadata inspector that validates dynamic, BSS, unwind-header, and runtime-module offsets and reports their guest addresses without applying relocations or starting a dynamic loader.

  • A bounded Elf64_Dyn planner that validates dynamic-table termination, string/symbol/RELA addresses, dependency counts, and relocation sizing while explicitly rejecting unsupported dynamic formats.

  • A bounded dynamic-linking plan with transactional relocation application: R_AARCH64_RELATIVE (load_bias + signed_addend) and same-image defined R_AARCH64_ABS64, R_AARCH64_GLOB_DAT, and R_AARCH64_JUMP_SLOT are supported for local/global/weak symbols; undefined or external symbols remain explicit unresolved results, and unsupported/malformed batches leave memory untouched.

  • switch1_runner, a command-line tool that loads an NRO or NSO, reports valid MOD0 and dynamic-plan metadata, executes the supported subset through the runtime boundary, prints SVC events and register state, and reports the exact stop reason.

  • A bounded guest-runtime boundary with virtual-address-checked memory helpers, modeled text/rodata/data/BSS/stack permissions, structured unmapped/permission fault reporting, a BSS-backed heap that cannot grow into the active stack, explicit bring-up heap-allocation and exit services, and a deterministic cooperative thread table with create/start/yield/exit operations.

  • A bounded manual-reset event subsystem with deterministic sync IDs, blocked/waiting thread state, signal-based wakeup, and SVC plumbing. These service IDs and handles are Pound-internal scaffolding, not Horizon ABI compatibility.

  • A deterministic I/O boundary with a guest-owned 1280x720 RGBA8 framebuffer, clear operation, explicit input state, and monotonic emulated time. These are testable bring-up services only: no host window, controller backend, or Horizon ABI is implied.

This is an execution and bring-up milestone, not a claim of game compatibility. A real game will still stop when it reaches unsupported instructions, dynamic loading, Horizon kernel services, filesystem services, GPU submission, or other unavailable runtime functionality.

Switch 2

The Switch 2 path currently contains an SM86 decoder, generated opcode metadata, an explicit target-aware SoA IR boundary, and a compact SPIR-V module emitter/validator for the first integer subset. The emitter lowers register moves and three-input integer adds into validated SSA-style IDs, materializes immediate constants, accepts explicit u32 live-in GPR values while preserving R255 as architectural zero, and lowers straight-line predicated register-producing operations with predicate live-ins and OpSelect (including negated predicates and @!PT). It also emits a deliberately narrow u32 StorageBuffer ABI for unpredicated LD/ST (byte addresses are converted to u32 element indices with a fixed binding 0 / descriptor set 0). The next structured-control-flow slice now lowers one forward SM86 BRA to SPIR-V OpBranch or OpBranchConditional with OpSelectionMerge, limited to an early-exit/skip shape that has a single terminal target and a store-only fallthrough. Invalid, backward, misaligned, and multi-branch shapes remain rejected. This is a useful compiler bring-up milestone, not a general shader compiler: the SM86 path is not yet a complete CPU, kernel, GPU, or game runtime.

Build

Pound uses CMake, a C11 library, and C++17 GoogleTest tests. From the repository root:

cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure

The build produces:

  • libPound.a — the emulator-core library
  • test_engine — the native test suite
  • switch1_runner — the Switch 1 NRO execution probe

The project intentionally enables strict compiler warnings and treats them as errors for the core library.

Running the Switch 1 execution probe

Build the project, then pass a valid Switch 1 NRO0 or supported uncompressed NSO0 image to the runner:

./build/switch1_runner path/to/program.nro
./build/switch1_runner path/to/program.nso

The runner identifies the container by magic, maps it at a modeled guest base address, starts at the image's text entrypoint, and prints output similar to:

Switch 1 NRO loaded: ... entrypoint=0x...
Running the supported ARM64 subset; max steps=1000000
[guest] SVC #...  x0=0x...
Guest stopped: status=... pc=0x... x0=0x...

A stop caused by an unsupported instruction, memory permission fault, unsupported service, or the step limit is reported explicitly. SVC instructions are currently exposed as trace callbacks rather than real Horizon services. The runner still does not create a host window or display game video. The runtime now exposes a guest-owned framebuffer plus input/time state for synthetic guest bring-up tests; a host presentation backend and real controller input are still absent.

Readiness assessment

This is the measured state of the current implementation, not a compatibility claim:

  • Firmware boot: effectively 0%. Firmware is not parsed, installed, or executed; there is no Horizon boot chain, kernel, secure-monitor boundary, or system-module loader.
  • A small synthetic NRO/NSO program: runnable through the supported ARM64 subset and bring-up services. This validates the loader/interpreter plumbing only.
  • Real homebrew: not yet a supported target. It will generally stop at missing instructions, dynamic dependencies, or missing Horizon services.
  • Commercial game: 0% boot readiness. The blockers are kernel/syscalls, threads and synchronization, filesystem/content loading, GPU/MMIO, display, input, audio, timing, and substantially broader ARM64 coverage.

In practical terms, the project is at the executable-loader/interpreter bring-up stage—not at firmware boot or game boot. A deterministic thread scheduler and manual-reset event boundary now exist as testable kernel-like scaffolding, but they are not Horizon-compatible. The next meaningful target is syscall/object-handle coverage, filesystem/content services, and an MMIO boundary, followed by controlled homebrew validation.

What is not implemented yet

The following pieces are still required before a commercial Switch 1 game can boot and display gameplay:

  • A substantially broader ARM64 interpreter or integration with the Ballistic recompiler.
  • A real Switch 1 memory map and MMIO models.
  • Horizon kernel, syscall/object-handle, synchronization, and service emulation; the current runtime service IDs, cooperative thread table, and manual-reset events are only an explicit bring-up boundary.
  • DT_NEEDED dependency loading, cross-module symbol resolution, and executable runtime linking beyond the bounded same-image relocation subset.
  • Cartridge, content, filesystem, and loader support.
  • GPU command submission, Maxwell execution, and a host display/framebuffer backend.
  • Audio, real controller input, host presentation, application lifecycle, and other system services. The current framebuffer/input/time APIs are deterministic internal scaffolding only.
  • Compatibility testing against homebrew and commercial titles.

Switch 2 additionally needs its own CPU/runtime bring-up and a verified Ampere execution path; the existing SM86 work should not be treated as a finished emulator backend.

Development roadmap

The near-term order is intentionally incremental:

  1. Expand the AArch64 core with more instruction decoding, stack behavior, and memory access tests. Current milestone: shifted-register ALU, conditional select, PC-relative literal loads, and CPU-side read/write/execute permission checks are covered.
  2. Extend the SM86 IR/SPIR-V emitter with predicates, structured control flow, memory operations, and verified SPIR-V toolchain output. Current milestone: target-aware SoA IR, explicit u32 live-in GPR and predicate mapping with R255/PT semantics, SSA-style register mapping, immediate constants, integer move/add lowering, straight-line predicated MOV/IADD via OpSelect, a narrow validated u32 StorageBuffer LD/ST path, and one forward early-exit/skip BRA shape lowered with structured SPIR-V selection. General loops, joins with SSA merges, predicated stores, and multi-block control flow remain.
  3. Add a real guest memory map, MMIO regions, and basic exception/fault reporting. Current milestone: modeled image-region permissions, CPU-enforced memory access, bounded runtime access, structured fault records, BSS-backed heap, and explicit service dispatch boundary.
  4. Replace the bring-up service boundary with a minimal Switch 1 kernel/runtime layer around the NRO entrypoint. Current milestone: bounded cooperative thread create/start/yield/exit state, deterministic context switching, and manual-reset event wait/signal behavior.
  5. Extend validated MOD0 relocation support, add dependency-aware symbol resolution, and load simple homebrew dependencies. Current milestone: same-image local/global/weak symbol lookup plus transactional ABS64/GLOB_DAT/JUMP_SLOT application are covered; DT_NEEDED loading remains.
  6. Add a host-visible framebuffer and input loop. Current milestone: guest-owned RGBA8 framebuffer clear, explicit input state, monotonic emulated time, and SVC plumbing are covered; host presentation remains.
  7. Build out Maxwell command processing and graphics translation.
  8. Integrate the Ballistic ARM recompiler when its interface is ready.
  9. Begin compatibility work with controlled homebrew before attempting commercial titles.

Firmware, title content, and key material are runtime inputs owned and supplied by the user; they are not embedded, logged, or committed by Pound. Keep them outside this repository (for example in local firmware/, keys/, roms/, or titles/ directories, which are ignored by Git) and provide only paths or configuration at runtime. Do not upload or commit firmware, prod.keys, ROMs, NCA/NSP/XCI files, or other copyrighted/proprietary title data. Use only material you are legally entitled to use. This repository currently does not implement NCA decryption, key ingestion, firmware installation, or a Horizon boot environment.

Contributions that improve instruction documentation, Switch reverse engineering, memory modeling, testing, or compiler/recompiler development are welcome.

About

Open source emulator for the Nintedo Switch 1 and 2. Highly work in progress (MADE WITH AI!)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages