NESkwik is a cross-platform (Linux, Windows, macOS, Android, and the browser) NES (Nintendo Entertainment System) emulator written in Zig. It has a custom UI powered by Clay, audio output, gamepad support, P2P multiplayer on native platforms, a simple debug UI, and native support for RetroArch shaders.
Home screen |
Game running |
RetroArch shader active |
Shader in letterbox area |
![]() |
![]() |
- 6502 CPU, PPU, and APU emulation.
- SDL3-based desktop UI with Vulkan rendering.
- Keyboard and gamepad input for two players.
- P2P multiplayer for co-op games.
- Configurable controls, aspect ratio, VSync, emulation speed.
- Pause, reset, stop, fullscreen, and step/debug controls.
- RetroArch
.slangpshader preset loading - Shaders for the letterbox area (border shader)
- Mapper 0 (NROM)
- Mapper 1 (MMC1)
- Mapper 2 (UxROM)
- Mapper 3 (CNROM)
- Mapper 4 (MMC3)
That totals to around 1900 supported games of the NES library.
- Zig 0.16.0.
- Rust 1.91 or newer for native netplay, provided by
iroh-ffi. - Vulkan runtime and development headers/library for the native shader renderer.
- Emscripten SDK for the browser build (see Browser / WebAssembly).
Android builds require the Android SDK, command-line tools, platform tools, NDK, build tools, and a JDK. The build script currently expects:
- Android SDK with
ANDROID_HOMEset, or installed in a standard Android Studio location. - JDK with
JDK_HOMEorJAVA_HOMEset, or available onPATH. - Android Build Tools
36.1.0. - Android NDK
28.2.13676358. - Rust Android targets for each ABI being built.
cargo-ndk.
If the exact build tools or NDK versions are missing, install them with sdkmanager:
sdkmanager "build-tools;36.1.0" "ndk;28.2.13676358" "platform-tools" "platforms;android-35"
rustup target add aarch64-linux-android x86_64-linux-android
cargo install --locked cargo-ndk- Android 7.0/API 24 or newer.
- Vulkan support.
To build the project simply run:
zig build --release=fastThe final executable is located at zig-out/bin/neskwik.
Install and activate the Emscripten SDK so emcc, em-config, embuilder, and emrun are available on PATH, then run:
zig build --release=fast -Dtarget=wasm32-emscriptenThe build output will be at zig-out/web.
Zig 0.16's standard library does not compile for Emscripten as-is, so the build also patches a few files in the lib/std directory of the Zig installation running it.
To build and run the WASM app, use:
zig build run --release=fast -Dtarget=wasm32-emscriptenWhat differs from the native builds:
- RetroArch
.slangpshaders are supported. Shaders are compiled like on desktop and then translated to GLSL ES 3.00 for WebGL 2; the few presets that need features WebGL 2 lacks (such astextureGather) fail to load with an error message. - Settings, history, save states, battery saves, imported shaders, and the compiled shader cache are persisted in the browser's storage (IndexedDB).
- Touch devices get the mobile layout.
- Netplay is not available.
To cross-compile the x86-64 Windows GNU build, install the Rust target and cargo-zigbuild once:
rustup target add x86_64-pc-windows-gnu
cargo install --locked cargo-zigbuildThen build the Windows executable directly through Zig:
zig build -Dtarget=x86_64-windows --release=fastThe executable is written to zig-out/bin/neskwik.exe.
Other non-native desktop targets require a compatible prebuilt iroh-ffi static archive. Pass the directory containing that archive with:
zig build -Dtarget=<zig-target> -Diroh-lib-dir=/absolute/path/to/iroh/libraryBuild a universal APK containing all supported Android ABIs:
zig build -Dandroid=true --release=fastBuild a smaller APK for one ABI:
zig build -Dtarget=aarch64-linux-android --release=fastThe APK is located at zig-out/bin/neskwik.apk.
Install and start the app on a connected device:
zig build run -Dtarget=aarch64-linux-android --release=fastOpen the UI without a ROM:
zig build runStart directly with a ROM:
zig build run -- path/to/game.nesStart with the debugger visible:
zig build run -- --debug path/to/game.nesYou need to provide your own .nes ROM files.
| NES button | Key |
|---|---|
| D-pad | Arrow keys |
| A | Z |
| B | X |
| Select | Space |
| Start | Enter |
| NES button | Key |
|---|---|
| D-pad | W / A / S / D |
| A | I |
| B | O |
| Select | U |
| Start | P |
| Action | Key |
|---|---|
| Quit | Escape |
| Toggle debug / step mode | F9 |
| Pause / continue | F4 |
| Stop ROM | F5 |
| Restart ROM | F6 |
| Run one CPU tick in step mode | F10 |
| Run one frame in step mode | F11 |
| Toggle fullscreen | F |
Controls can be changed from the settings window.
NESkwik doesn't ship with the RetroArch .slangp shaders, you'll have to clone the https://github.com/libretro/slang-shaders repository and place somewhere in your system. And then, you can select the shader by going to the "Shader" tab in the settings window.
The border shaders can be selected from a couple of options in the "Shader" tab.
In the browser, shaders are imported into the browser's storage first; see Browser / WebAssembly.
Run the unit test suite:
zig build testRun the relay-free multiplayer loopback test:
NESKWIK_NETPLAY_LOOPBACK_TEST=1 zig build test -Dtest-filter="local loopback session"Run ROM-based tests:
zig build test --release=fast -Drom-tests=trueROM tests are slower. You can filter or skip them with:
zig build test --release=fast -Drom-tests=true -Dtest-filter=mmc3
zig build test --release=fast -Drom-tests=true -Dskip-rom-test=sprite_hitIt's recommended to run ROM tests in release mode.






