Picking apart the Joy-Con: Wired Protocol and Firmware Dumping
For the past few days I’ve poked around a bit with the fancy new controllers that ship with the Nintendo Switch, the Joy-Con. My primary motivation in looking at these devices mostly come back to the fact that they’re almost everything I wanted in my VR controller project: analog joysticks, four buttons, and grip buttons (to a degree). Position tracking aside, I think they’re basically perfect for VR and have huge potential as a standard Bluetooth controller as well, with some interesting bits added in like linear resonant actuators for HD rumble, an IR camera and NFC. And while they do work out of the box as Bluetooth controllers, there is no real analog Joy-Stick values, all of the extra peripherals are still locked off, and the Joy-Con don’t send data fast enough at times, resulting in missed button presses.
Unfortunately, at least until the Switch is fully cracked open to the point where normal Bluetooth communication can be observed, the only means of communicating with the Joy-Con aside from Bluetooth is through the wired protocol present under the rails. When the Joy-Con are docked, a 10 pin connector is used for charging and data transfer as opposed to keeping Bluetooth on constantly.
Before I go too far in though, I’d like to send a huge shoutout and thanks to dekuNukem and his repository at https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering, he got much farther into Joy-Con protocol reversing before I even started, and I doubt I would have been able to get this far without some of his data since my logic analyzer is fairly weak and can’t capture a whole lot at a time. However, for some simple debugging it and some wires is more than good enough to recreate his experiments and try things on my own:
The Joy-Con rails communicate wired to the console via UART, with an initial baudrate of 1000000bps (1Mb/s) and with flow control for both sending and receiving. The Joy-Con Rx and CTS lines are also inverted (possibly for easier detection?). To talk to my Joy-Con, I opted to use my ESP32 which actually supports inverted UART lines, however a bug in uart_set_line_inverse did end up causing errors regardless of the masks I sent, and it turned out to be a driver bug which nobody has encountered since inverted lines aren’t used all that often.
To begin communication, an initial test packet of A1 A2 A3 A4 is sent from the Joy-Con to the console, and following this a packet requesting the device’s MAC address is sent, which it promptly returns:
Additionally, these handshaking packets can be sent repeatedly, however the final handshake command must occur before additional commands are opened. This fact at least makes while(1) debugging while tweaking UART settings possible.
Once handshaking is done, different functionality of the Joy-Con may be polled. Most notably, full input can be retrieved with analog joystick values and 6-axis values (though it seems the IMU is off by default and needs a command to enable it). Additionally, one of the first things done after initialization is reading calibration and identification values from the inbuilt Joy-Con SPI flash. For my initial test case, I wanted to try grabbing some input values before getting too crazy:
With that done and working though, my main goal in all of this is Bluetooth, and once again it’s not something that can easily be observed. What can be observed, in this case, is the Joy-Con firmware which resides in the SPI flash. By using the same packet used for reading calibration and identification data from SPI, the entire SPI flash can be dumped.
Even with the SPI flash dumped, however, I am having difficulty finding any code to reverse at all. There are definitely strings to indicate that there is code, and some things almost appear to be proper Thumb code, but nothing quite makes sense or flows at all. However there are still some notable things in the firmware:
- Strings for “Joy-Con (L)”, “Joy-Con ®”, and “Pro Controller” exist, which seems to imply that all three share the same exact firmware (though probably with configuration blocks tweaked to identify what device it is)
- A string for “Joy-Con Charging Grip” exists, possibly implying that it can (or originally was planned to?) bridge two wired Joy-Con connections into one wireless connection? Or at the very least, the grip may identify itself to Joy-Con for the console to know a charging grip is being used.
- The Joy-Con serial, color, and MAC are all stored in SPI flash. It’s possible these blocks are write protected, but somehow I doubt it.
Some guesses with Bluetooth:
- The protocol is probably fairly similar if not identical to the wired protocol.
- The Joy-Con work, by default, as game controllers, but my guess is that there is something similar to UART where it will not display an extended set of commands until a specific handshake is done. Hopefully a firmware RE will reveal any nuances required for this…
While I won’t have my SPI flash dump uploaded with this post, I’m more than willing to send it the direction of anyone who is interested in reverse engineering it. For anyone who wants to dump their own Joy-Con, my ESP32 source can be found here.