AUTHOR
Raoul Picconi
DATED
July 23, 2026
READING TIME
14 min read
SUBJECT
Nintendo DS / Reverse engineering / ESP32

I thought the problem was making the internet fast enough for the DS. The project only began to make sense when I stopped treating it as a speed problem.

01

It started with replaying the DS

A few weeks ago, I pulled my old Nintendo DS out of a drawer.

I only meant to replay a few classics. Instead, I found myself rediscovering how pleasant that console still is to use. No notifications, no updates, no accounts: turn it on, choose a game, and start playing.

What struck me most was how well the experience had aged. The only thing that was really missing was local multiplayer.

When I was a kid, there was always someone nearby with another DS: a friend at school, a cousin during the holidays, somebody ready for a Mario Kart race or a Pokémon trade.

The consoles still work exactly as they did twenty years ago. The people I used to play with simply aren’t in the same room anymore. That was the thought that started this project.

Could two Nintendo DS consoles, hundreds of kilometres apart, be made to believe they are sitting next to each other?
02

The first idea

As often happens, the first solution also seemed like the obvious one: build a kind of transparent network cable.

As a software engineer, forwarding packets is almost the default mental model.

A device next to each console would capture every Wi-Fi packet, forward it over the internet, and retransmit it at the other end.

Conceptually, it felt almost trivial. If the packets were identical, why would the game notice the difference?

Unfortunately, the Nintendo DS does not work that way.

03

Time is part of the protocol

The first packet captures revealed something important: the contents of a packet were only half of the protocol. The other half was when that packet arrived.

In Mario Kart DS and Nintendo MP traffic, ESP32 callback timestamps consistently showed command-to-reply intervals around 218–399 µs and reply-to-MP-ACK intervals around 230–412 µs. These are not perfect PHY-layer timestamps because they include ESP32 driver latency, but they are sufficiently accurate for architectural decisions.

Even an excellent fibre connection takes tens of milliseconds to cross the internet. The difference is two orders of magnitude.

At that point, the transparent proxy was effectively dead. This was not an optimisation problem. It was physically impossible for any design that required every packet to cross the internet and still meet those observed timings.

For a few days, I filed the whole idea away as impossible.

05

Changing the question

Until then, the question had been:

How do I forward every packet quickly enough?

After Celio-Link, it became:

Which packets actually need to cross the internet?
Which ones can be generated locally without changing the game’s behaviour?

That distinction changes the entire architecture.

The working hypothesis was no longer a transparent tunnel. It was a peer that understands enough of the protocol to answer immediately when necessary, while keeping only the meaningful state synchronised.

06

Passive capture

At this stage, the setup is not a bridge. It is a local impersonation probe: one ESP32 tries to make one physical DS believe that another DS is present. The current board is an original ESP32-D0WD-V3 rev 3.1 simply because I already had it at home, not because I believe it is the right platform. I will keep using it for as long as it can answer the next useful question.

Before trying to impersonate anything, I used it to observe what the consoles were already doing. The captures exposed Nintendo DS local-wireless beacons as well as Nintendo MP command, reply, MP-ACK, and acknowledgement traffic.

The callback timestamps are not a replacement for proper PHY instrumentation, but they were sufficient to reveal the shape and order of the exchange. More importantly, passive capture provided real frames that could be tested one at a time instead of guessing at the whole protocol.

Workbench with a Nintendo DS displaying the Pokémon Platinum Union Room, an ESP32 on a breadboard, and protocol code on the monitor.
FIG. 1 - The current bench during the local impersonation tests.
07

Beacon replay: the ghost player

The first active result was local beacon replay. The beacon itself had been captured from a legitimate console during a Pokémon Platinum Union Room session, then saved and transmitted again from the ESP32.

A physical DS decoded it and displayed a ghost player named Raoul in the Union Room. Nothing had travelled over the internet; this was a local replay test. Still, it proved that the DS could discover an impersonated peer from a captured beacon.

Seeing a real Nintendo DS list a player that did not exist was the first genuinely exciting milestone.

08

Discovery on 13, authentication on 1

Pokémon Platinum Union Room discovery appeared on channel 13. After selecting the ghost player, however, the DS attempted authentication on channel 1.

That creates a concrete constraint for a single radio: it must keep the ghost discoverable while also moving to the channel where the next stage of the exchange occurs. Channel behaviour is part of the state machine, not just a setup detail.

09

The raw TX wall

Once the ghost player was selected, the DS sent authentication requests. The obvious next test was to capture an Authentication Response and transmit it as another raw frame.

That is where the public ESP32 API revealed a hard limitation. Raw beacon replay worked, but esp_wifi_80211_tx() rejected the Authentication Response frame, subtype 0xb0.

This is an important boundary: the radio can replay some useful frames, but it cannot act as an unrestricted 802.11 frame injector through the supported API.

EXHIBIT A - CURRENT TEST MATRIX
Beacon replay, subtype 0x80        works; DS shows ghost player
Raw auth response, subtype 0xb0    rejected by esp_wifi_80211_tx()
SoftAP authentication              works
SoftAP association                 works; DS receives AID=1
Post-association session           fails after ~4 seconds
10

The SoftAP workaround

Instead of injecting the Authentication Response manually, I configured the ESP32 as a SoftAP that cloned enough of the observed BSS parameters for the Wi-Fi stack to handle authentication and association itself.

The first SoftAP clone still failed at association because it advertised an empty SSID. Inspecting the DS Association Request revealed that the console was not using a human-readable SSID at all, but a 32-byte binary value. I restarted the SoftAP with that exact SSID.

Only then did the DS authenticate and associate, receiving AID=1.

The blocker was no longer basic Wi-Fi authentication. For the first time, the local proxy had led a physical DS through discovery, authentication, and association.

11

The current blocker

The association lasts for roughly four seconds. Then the DS leaves.

The most likely reason is no longer the 802.11 authentication exchange. The Nintendo-specific traffic expected immediately after association is missing, malformed, or arriving in the wrong sequence.

The surprising part is that the problem moved lower in the stack: before worrying about game state, the proxy must first impersonate enough of an 802.11 peer that the DS stays associated.
12

What this proves

This is not an online bridge yet, but it is more than a packet capture. The local proxy has now demonstrated that it can:

  • passively capture Nintendo DS local-wireless traffic;
  • replay a legitimate beacon and make a physical DS display a player that does not exist;
  • receive and inspect the DS authentication and association requests;
  • reproduce observed BSS parameters, including the 32-byte binary SSID;
  • complete SoftAP authentication and association with AID=1.

The remaining question is what Nintendo-specific traffic must appear immediately after association for the game to keep the session alive.

The next experiment is therefore narrow: capture and classify the first seconds after a legitimate association, reproduce only what is necessary, and measure exactly where the impersonated session diverges.

Every failed experiment has made the protocol a little less mysterious. At this point, that is already a success.