- 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.
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?
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.
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.
Then I found Celio-Link
A few days later, I came across a project called Celio-Link almost by accident. It is not even about the Nintendo DS.
Celio-Link was built for the Game Boy Advance. It lets two real consoles communicate over the internet through a small hardware device connected to each Link Cable.
What caught my attention was not just the result. It was the architecture.
The device does not try to behave like a longer cable. It actively participates in the protocol.
It handles the exchanges with extremely tight deadlines locally, leaving the internet to carry only the information that can afford to arrive later.
I was not looking for an implementation to copy. I was looking for evidence that my assumptions might be wrong.
That was the moment I realised I had probably been trying to solve the wrong problem.
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.
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.

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