Building 3DSnickerStream: A Native
3DS Streaming App for Mac and Windows

3DSnickerStream app icon

The Problem: Streaming a 3DS Meant Booting Into Windows

Snickerstream by RattletraPM is a genuinely good tool — it streams a 3DS's two screens to a computer over Wi-Fi using the NTR and HzMod custom-firmware protocols. The problem was where it runs: Windows only, and not a native app even there. I use a Mac day to day, and I wanted to stream my 3DS while I was on it, not after switching machines.

I didn't want a compatibility-layer workaround either. I wanted a resizable window that behaves like a Mac window, controls that respond instantly, and colors that look like what's actually on the 3DS screen instead of whatever a translation layer decides to do to them.

So instead of trying to port the existing project, I built something new: a native SwiftUI app for Apple Silicon Macs first, then a matching native app for Windows using WPF and .NET. Neither is a copy of the original code — the NTR and HzMod protocols were reverse-engineered by reading the original project's AutoIt source, then re-implemented from scratch on each platform.

"The tool existed. It just didn't run where I actually sit. So I built it twice — once for each place I sit."

What It Does

Point it at a 3DS running NTR CFW (or HzMod) with remoteplay enabled, and both screens show up in a real, resizable window within a couple of seconds.

🎮
NTR Remoteplay
The main protocol — tested and working reliably on real hardware.
🧪
HzMod (Beta)
The original's other protocol, reconstructed from source — connects and streams, top screen only.
📡
Find My 3DS
Scan the local network and pick the console instead of typing an IP. Optional scan-on-startup and auto-reconnect.
🖥️
Flexible Layouts
Stacked, side by side, or one screen at a time — with adjustable gap and per-screen scale.
🎨
Per-Screen Color
Brightness, contrast, saturation, highlights, and shadows — tuned independently for each screen.
🔍
Zoom & Clean Mode
Fit or 100–300% native zoom, plus a one-key clean mode that hides every bit of UI.
🔖
Saved Consoles
Bookmark an IP once, reconnect with a single click from then on.
🎚️
Quality Presets
Built-in quality/framerate presets, plus room to define your own.
⌨️
Remappable Shortcuts
Every action is a rebindable key, including screenshots to disk or straight to the clipboard.

A few smaller things matter more than they sound: a playback FPS cap that limits redraw without touching what the 3DS actually sends, sharp/linear/smooth scaling with optional rotation, an ambient glow that lifts colors off whatever's on screen, and — maybe the most useful one — the app doesn't just show a black screen and give up if the 3DS doesn't answer immediately. It retries.

Two Native Apps, Not One Port

The macOS app is SwiftUI, built for Apple Silicon, using ImageIO and Core Graphics for JPEG decoding and a layer-backed view to keep both screens smooth. The Windows app lives on a separate branch and is WPF on .NET, published as a self-contained build via dotnet publish -c Release -r win-x64 --self-contained (the executable ships with its x64 and tessdata folders alongside it).

Neither one started from the original project's AutoIt codebase. Both were written fresh, each in the idioms of its platform — but both had to speak the exact same wire protocols the 3DS expects, because the 3DS side of NTR and HzMod isn't something you get to redesign.

That meant the actual work wasn't "port this app" — it was "figure out what bytes the 3DS is sending and expecting, twice, in two different languages, and make sure both implementations agree with real hardware."

Reverse-Engineering NTR: 84 Bytes and a JPEG Stream

NTR's remoteplay protocol isn't publicly documented anywhere I could find — the closest thing is the original Snickerstream's include/ntr.au3 AutoIt source, which builds and sends the handshake by hand. Reading it gave the shape of the protocol: connect to the 3DS on TCP port 8000, then send an 84-byte command packet carrying a magic number, a command ID, and the priority/quality/QoS settings the user picked.

Once the 3DS accepts that command, it starts streaming JPEG frames as UDP packets to the app on port 8001 — no acknowledgment, no retransmission, just a continuous stream. Each UDP packet carries a small header (frame id, which screen it belongs to plus a last-packet flag, and a packet index) followed by a slice of JPEG data. The app buffers packets by frame id until the last-packet flag shows up, concatenates the slices in order, and decodes the result.

// NTR handshake — simplified
struct NTRCommand {
  UInt32 magic = 0x12345678
  UInt32 seq
  UInt32 type
  UInt32 cmd    = 901          // remoteplay start
  UInt32 args[16]              // priority, quality, qos, screen mask...
  // padded to 84 bytes total
}

func sendHandshake(to socket: TCPSocket, quality: Int, priority: Int) {
  var packet = NTRCommand()
  packet.args[0] = UInt32(priority)
  packet.args[1] = UInt32(quality)
  socket.write(packet.serialized())   // 3DS:8000
}

// UDP frame reassembly — simplified
func onPacket(_ data: Data) {
  let header = parseHeader(data)      // frameId, screen, isLast, packetIndex
  buffers[header.frameId, default: []].append(data.jpegSlice)
  if header.isLast {
    let jpeg = buffers[header.frameId]!.joined()
    decode(jpeg, screen: header.screen)
    buffers[header.frameId] = nil
  }
}

There's no error correction anywhere in this. If a UDP packet drops, that frame just never completes and gets discarded — the next one arrives a few milliseconds later. It's a fragile-sounding design that works fine on a local Wi-Fi network, which is presumably why the original project never needed anything sturdier.

HzMod: The Beta Protocol

HzMod is the original project's second protocol, reconstructed from its include/HzMod.au3 source the same way. It works differently from NTR: the app connects to the 3DS on TCP port 6464, sends a packet with a CPU-limit value, a quality setting, and a start flag, then reads the response stream back over the same TCP connection.

The tricky part was the frame boundaries. The header layout I could reconstruct from the AutoIt source was ambiguous enough that trusting a parsed length field felt risky. The more robust fix: don't parse a header for framing at all — just scan the incoming byte stream for a complete JPEG, marked by the standard FF D8 start-of-image and FF D9 end-of-image bytes, and decode whatever's between them.

// HzMod framing — simplified
var buffer = Data()

func onBytes(_ chunk: Data) {
  buffer.append(chunk)
  while let start = buffer.range(of: [0xFF, 0xD8]),
        let end   = buffer.range(of: [0xFF, 0xD9], from: start.upperBound) {
    let jpeg = buffer[start.lowerBound..
"HzMod connects. It streams. I couldn't test every case it might hit — so it ships labeled beta, honestly, instead of quietly."

Finding Your 3DS Without Typing an IP

Typing a 3DS's IP address every session gets old fast, especially on a home network where DHCP hands out a new one every so often. The "Find on network" button scans the local subnet for a 3DS answering on the NTR/HzMod ports and lists it for a one-click connect. There's an option to run that scan automatically on startup, too.

Saved consoles solve the rest: bookmark an IP once with the ribbon icon next to the address field, and it's available for one-click reconnect from then on — along with an optional auto-connect to the last device used.

One small design choice that mattered more than expected: if the 3DS doesn't answer right away, the app doesn't just show a black screen and call it connected. It keeps retrying the handshake until the stream actually starts, or the user cancels. A remoteplay tool that silently "succeeds" at nothing is worse than one that's honest about still trying.

Two Screens, Arranged However You Want

The 3DS has two screens with very different jobs — top for the game, bottom usually for touch UI — and different games use them differently. The layout system supports stacked, side-by-side, or a single screen at a time, with adjustable spacing between them and independent scale per screen so one can be bigger than the other.

Color adjustment is per screen too: brightness, contrast, saturation, highlights, and shadows can each be tuned separately for top and bottom. It's a small thing, but a game with a bright top screen and a dim menu on the bottom looks noticeably better once they're not forced through the same curve.

Zoom goes from a fit-to-window mode up to 300% native, and a single key (H) hides every bit of app chrome for a clean recording or screenshot — just the streamed screens, nothing else.

Building Two Codebases at Once with Claude Code

I'll say the same thing here I say about every project: I didn't hand-write this code myself. I built it together with Claude Code — describing what I wanted, reviewing what came back, and testing every change against my own 3DS until it actually worked, not just compiled.

What made this project different from the others is that there wasn't one codebase to iterate on — there were two, in two different languages, that both had to independently agree with the same 3DS hardware behavior. Getting the Swift version's frame reassembly right didn't automatically get the C# version right; each had to be tested and debugged against a real console separately.

The split in practice: Claude Code wrote and rewrote the Swift and C# implementations as I described symptoms — a torn frame, a color that looked off, a reconnect that hung — and I was the one standing in front of a 3DS confirming whether the fix actually changed what showed up on screen.

"Two languages, one protocol, one 3DS on my desk to check the truth against. That last part doesn't get automated."

Credits & License

3DSnickerStream is a GPLv3 fork of the original Snickerstream — the shipping code is a fresh native rewrite, not a copy, but it follows the same protocols and license as the original it's built on. Credits, in order:

  • RattletraPM — original Snickerstream project and the NTR/HzMod protocol groundwork this app reverse-engineers from
  • samaBR — native macOS (SwiftUI) and Windows (WPF/.NET) rewrite
  • Claude Code (Anthropic's agentic coding tool) — wrote and iterated the Swift and C# implementations from my ideas, descriptions, and hardware testing

Licensed under GPLv3, same as the original. Not affiliated with or endorsed by Nintendo.


The Result

3DSnickerStream is a free, open source, GPLv3 app that streams a Nintendo 3DS's two screens to a real, native window on macOS 13+ (Apple Silicon) or Windows 10/11. No coding knowledge needed to use it — unzip, allow it through the firewall/Gatekeeper on first launch, point it at a 3DS running NTR remoteplay, and connect.

It's the tool I wanted when I first went looking: native on the platform I'm actually using, honest about connection state instead of showing a black screen, tunable per screen instead of one-size-fits-all, and fully keyboard-driven once it's set up.

Downloads (both macOS and Windows builds ship in every release), source, and the full feature list: github.com/samaBR85/3DSnickerStream — GPLv3, free.