Nintendo_Switch_Reverse_Engineering icon indicating copy to clipboard operation
Nintendo_Switch_Reverse_Engineering copied to clipboard

HID Protocol for Bluetooth / USB

Open ghost opened this issue 8 years ago • 216 comments

Hi,

Loving the work you've done - thanks ever so much. Couldn't find you on social media, so am just gonna drop a message here.

Have you been able to sniff the USB or Bluetooth HID protocol it's using? Been trying to bodge a driver together, and don't really have the tools to analyse the protocol. Whilst the buttons and joysticks work fine, I believe the console sends a feature report to the joy-cons to activate gyroscope input. So far I've not had much luck getting the gyroscope and accelerometers to send anything over the standard protocol.

Could be me, or it could be Nintendo's very kind way of introducing security through obscurity. Some help on the matter would be great if you are free.

Thanks

ghost avatar Mar 15 '17 16:03 ghost

I'm also working to reverse engineer the joycons, I found this documentation. I think that Nintendo might use something similar to the joycons.

[http://wiibrew.org/wiki/Wiimote](Wiimote HID)

alejandrorangel avatar Mar 15 '17 22:03 alejandrorangel

thanks, I did figure out the accelerometer and gyroscope data in the joycon status packet today, but only in the physical connection bewteen the joycon and console. I haven't began working on the bluetooth level yet, but I'll keep it in mind when I do.

dekuNukem avatar Mar 15 '17 22:03 dekuNukem

just to make you guys aware, there is a program similar to glovepie called freepie, the possibility of developing a module for the joycons may be faster/easier than an entire new driver. thanks and gj with all of this.

heres the link btw [http://andersmalmgren.github.io/FreePIE/]

forerofore avatar Mar 16 '17 06:03 forerofore

@alejandrorangel Cool, thanks for that. I might try and throw some of the Wiimote packets at it this weekend and see if it reacts to any of them.

ghost avatar Mar 16 '17 15:03 ghost

Over USB the pro controller just does a handshake, then switches to bluetooth. Descriptor: procontroller descriptor.txt Beagle analyzer trace: data-center-windows-x86_64-v6.72.zip

aspalmer avatar Mar 16 '17 17:03 aspalmer

@aspalmer That actually makes sense from the HID reports I was getting back, actually. Was able to get the HID info from the USB device, although no data was being sent. However when I paired the USB and Bluetooth together using some test code, I noticed one thing. Over the USB, the HID protocol reports that there are 2 extra components called "Z-Acceleration, and Z-Rotation". I can only imagine this is the gyroscope and accelerometer. However, as it was over USB, no data was sent. This leads me to believe you do indeed have to send a feature report packet to the controller via bluetooth to enable the gyro/acc.

Also, via USB the HID device is reported as a "stick" or "joystick", versus the Bluetooth HID which reports as "gamepad". Can actually see this from the descriptor you sent over actually.

ghost avatar Mar 21 '17 12:03 ghost

I'd love to see an iOS API for the JoyCon via Bluetooth.

UPDATE: iOS uses its own Bluetooth controller specification which means it does not work natively with the Joy-Con or Switch Pro controllers.

rlaferla avatar Mar 23 '17 15:03 rlaferla

I have some notes on the Bluetooth hid buttons. The left stick & right stick buttons are adjacent button numbers. So are + - and home, capture.

It sends the stick data as hat input, though. (0-8, 8 is neutral, 0 is up) Annoying.

riking avatar Mar 25 '17 20:03 riking

@riking What are you using to capture the Bluetooth traffic?

fossephate avatar Mar 26 '17 23:03 fossephate

@mfosse Oh, I just straight up connected the joycon to my Linux laptop and read the input data with hidapi. So it's default drivers.

Was about to try sending some of the 0x19 packets as OUPTUT or FEATURE reports and see what happens.


So when I hid_write a packet with command 0x01, I get this response back:

Joycon R: Packet 0x21
C9 8E 80 00 00 00 00 00 
E6 C8 6F 01 81 01 0C 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 

Joycon R: Packet 0x21
8F 8E 80 00 00 00 00 00 
E6 A8 6F 01 80 00 03 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 

Joycon L: Packet 0x21
4F 8E 00 01 4F 9F 57 89
00 00 00 20 81 01 0C 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 

Perhaps Byte 0 is the checksum? I should try calculating it.

Byte 1 is always 0x8E. Byte 2, 3, 4 is the button status.

    case 0x21: // Packet 0x21
        // Button status:
        // Byte 1: 0x8E
        //  Byte 2
        //   Bit 0: JR Y
        //   Bit 1: JR X
        //   Bit 2: JR B
        //   Bit 3: JR A
        //   Bit 4: JR SR
        //   Bit 5: JR SL
        //   Bit 6: JR R
        //   Bit 7: JR ZR
        // Byte 4
        //   Bit 0: JL Down
        //   Bit 1: JL Up
        //   Bit 2: JL Right
        //   Bit 3: JL Left
        //   Bit 4: JL SR
        //   Bit 5: JL SL
        //   Bit 6: JL L
        //   Bit 7: JL ZL
        // Byte 3
        //   Bit 2: RStick
        //   Bit 3: LStick
        //   Bit 4: Home
        //   Bit 5: Capture

Next 3 bytes are only set by the left joycon. Next 3 bytes are only set by the right joycon. ~~Haven't figured out what data they carry.~~ Found the stick data! They seem relatively stable when the stick isn't moving.

riking avatar Mar 27 '17 18:03 riking

Yup, I found the stick data.

uint8_t *pckt = buf65 + 2; // skip the HID header (0x21) and checksum byte
// print buttons...
uint8_t *stick_data;
if (jc->left_right == 1) {
    stick_data = pckt + 4;
} else {
    stick_data = pckt + 7;
}
uint8_t stick_unk = stick_data[0]; // maybe checksum
// horizontal stick is nibble swapped??
uint8_t stick_hz = ((stick_data[1] & 0x0F) << 4) | ((stick_data[1] & 0xF0) >> 4);
uint8_t stick_vert = stick_data[2];
printf("Stick %c: [%02X] %d %d\n", L_OR_R(jc->left_right), stick_unk, -128 + (int)(unsigned int)stick_hz, -128 + (int)(unsigned int)stick_vert);

Haven't figured out the last bit of data. It only seems to send a couple of bit patterns:

(L) 10 81 01 0C (L) 10 80 00 03 (L) 10 80 0C 03 (L) 40 80 00 03

(R) 01 80 00 03 (R) 03 80 00 03 (R) 03 81 01 0B

Definitely not the gyro data.

riking avatar Mar 27 '17 19:03 riking

Oooookay, looks like I'm going to need to pair the controllers before I move further - they started connecting to someone else's console, which is erroring out my Bluetooth stack.

Here's what I have so far: https://github.com/riking/joycon/blob/master/src/joycon_input.c https://github.com/riking/joycon/blob/master/src/joycon.h

riking avatar Mar 28 '17 02:03 riking

Can confirm the joy-cons and pro controller do operate using the standard HID protocol, all except the gyro and accelerometer.

ghost avatar Mar 28 '17 08:03 ghost

I've got a working "combine both joycons into a single controller device" program here: https://github.com/riking/joycon

riking avatar Mar 28 '17 08:03 riking

I'm working on a vJoy feeder for the JoyCons here: https://github.com/mfosse/JoyCon-Driver

fossephate avatar Apr 03 '17 02:04 fossephate

The Charging Joy-Con grip seems to have two interfaces with an in/out endpoint per interface for each Joy-Con. I haven't managed to get any replies from input I've sent, however the Joy-Con grip, once an HID session is started, seems to send packets matching the first MAC address handshake command for UART every time the Joy-Con are inserted and ejected. The format seems to go as follows:

81 01 <03 for eject, 00 otherwise> <inserted Joy-Con type, 01 for Left, 02 for right> <MAC>

This can also be observed from Wireshark:

The charging grip also has the same STM32 chip as the pro controller, and the same 5-pin layout found on the dock. Maybe the firmware can be dumped same as the dock to learn more about UART and Bluetooth commands @dekuNukem?

shinyquagsire23 avatar Apr 08 '17 09:04 shinyquagsire23

Checked out the USB capture from @aspalmer and sending 0x80 0x01 gives me that MAC packet. It seems to respond to some of the other output reports in the capture similarly, so it seems that two Joy-Con in a charging grip effectively functions as a Pro Controller with two interfaces.

shinyquagsire23 avatar Apr 08 '17 11:04 shinyquagsire23

Guys, please see #11; I don't think the HD rumble data is HID compliant.

a7a00 avatar Apr 09 '17 02:04 a7a00

I have the firmware for the Joy-Con charging grip, doing a quick search for A1 A2 A3 A4 gets me a hit so it does seem that they talk to Joy-Con and have some sort of shim to HID. The firmware is pretty small though, 0x10000 bytes total. Willing to bet there's a point where UART and HID have to match up, that or this thing is a dedicated MAC address fetcher but I have my doubts there.

EDIT: For anyone else's curiousity, the 5 througholes are an SWD debug interface, from top to bottom:

??
SWDIO
GND
VDD
SWCLK

I also had to tap the test pad above the middle bottom screw for nRST. Dumping is pretty straightforward with OpenOCD.

EDIT 2: It seems this thing is definitely capable of sending post-handshake UART packets, and most of the handshake is done by the device, though for post-handshake packets the question is how it gets triggered I suppose:

shinyquagsire23 avatar Apr 09 '17 06:04 shinyquagsire23

Has anyone managed to read any gyroscope or accelerometer data over bluetooth? I haven't had any luck so far.

fossephate avatar Apr 09 '17 16:04 fossephate

@riking I figured out what stick_unknown is it appears that the X component of the stick data isn't just nibble reversed, specifically, the second nibble of second byte is combined with the first nibble of the first byte to get the correct X stick value:

uint8_t stick_horizontal = ((stick_data[1] & 0x0F) << 4) | ((stick_data[0] & 0xF0) >> 4);

I don't have any idea what the other nibbles are, they seem random to me, but I haven't looked at them too closely.

fossephate avatar Apr 09 '17 21:04 fossephate

It seems there is indeed a set of HID commands which allow sending UART commands, ~however they cannot be accessed until handshaking is complete, and I'm not sure of the command to do that (seems to be done elsewhere from the main loop)~.

Commands which seem to exist at 0x0800BB00 are:

80 01 (Handled elsewhere? Returns MAC packet)
80 02 (Do two handshakes 19 01 03 07 00 91 10 00, 19 01 03 0B 00 91 12 04)
80 03 (Do baud switch)
80 04 (Set something to 1)
80 05 (Set something to 0)
80 06 (Something with sending post-handshake command 01 00 00 00 00 00 00 00 01 06 00 00, maybe baud related as well?)
80 91 ... (Send pre-handshake command? Has some weird lookup table stuff)
80 92 ... (Something with pre-handshake commands?)
01 ... (Send post-handshake command? Sends 0x31-large UART starting with 19 01 03 07 00 92 00 00 with checksum edited in and the rest of the HID request pinned on)
10 ... (Same as above)

EDIT: turns out the 'lookup table' stuff for 80 91 is actually for incoming UART data size, just managed to send a raw pre-handshake UART packet like such:

memset(buf, 0x00, 0x100);
buf[0] = 0x80;
buf[1] = 0x91;
buf[2] = 0x01;
buf[3] = 0x0;
buf[4] = 0x0;
buf[5] = 0x0;
buf[6] = 0x0;
hid_write(handle_l, buf, 0x7);

And the resulting data I got back was the MAC packet in a slightly different form:

81 91 00 94 01 08 00 00 3c 68 01 fd bf e8 8a bb 7c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

EDIT 2: Seems 80 92 actually works to a degree to send post-handshake commands (with pre-handshake headers?), this works to get something resembling an input packet, but none of the actual input shows up.

memset(buf, 0x00, 0x100);
buf[0] = 0x80;
buf[1] = 0x92;
buf[2] = 0x00;
buf[3] = 0x01;
buf[4] = 0x0;
buf[5] = 0x0;
buf[6] = 0x69;
buf[8] = 0x1F;
hid_exchange(handle_l, buf, 0x9);

EDIT 3: For the record...

Since the firmware tells me this, these are the return sizes for any 19 01 03 07 00 91 XX ... UART command:

01 - 0xF
03, 05, 06, 07, 13, anything else? - 0xB
18 - 0x37
40 - The size specified in the u16 following the XX command

shinyquagsire23 avatar Apr 10 '17 06:04 shinyquagsire23

I left my controllers on the grip for a few days and now the status packet[1] is 0x6E on the R, but it's still 0x8E on the L. Battery level?

EDIT: Docked the controller for a bit and it's back to 0x8E. Definitely a single nibble of battery

Also, noticed something weird. The kernel is always returning 0 for the number of actual bytes written :thinking: This may be why I had no response to writes other than 0x1 :thinking:

riking avatar Apr 11 '17 02:04 riking

Here's what the Linux Kernel gives for the report descriptor over bluetooth:

https://www.irccloud.com/pastebin/P60ntzyH/

@shinyquagsire23 can you dump /sys/kernel/debug/hid/*/rdesc with the Charging Grip?

riking avatar Apr 11 '17 04:04 riking

@riking That file doesn't seem to exist for me, but the charging grip also doesn't function as a controller by default.

Also, I've managed to get a full initialization of the Joy-Con via the charging grip. The charging grip is able to send any UART command, so I am able to pull for full input at around 16ms per input, in addition to being able to dump my Joy-Con SPI firmware over HID. My HID program can be found at my repo, https://github.com/shinyquagsire23/HID-Joy-Con-Whispering

Hopefully with that, it should be possible to test Joy-Con functionality more in-depth. I did try to have the same HID commands sent over Bluetooth but it seems it's not the same protocol, unfortunately...

shinyquagsire23 avatar Apr 11 '17 08:04 shinyquagsire23

Sorry, the underscores weren't meant to be literal - replace them with the kernel-assigned device identifier. The file should be there if hidapi can open the device, so sudo ls the directory.

but it seems it's not the same protocol, unfortunately...

Dang :(

riking avatar Apr 11 '17 09:04 riking

@riking Ah, I did replace the underscores but I guess I forgot to escape something and it failed, https://gist.github.com/shinyquagsire23/89ea38220e221950a233cd23f2fde28f

shinyquagsire23 avatar Apr 11 '17 09:04 shinyquagsire23

Okay, wow. Much more straightforward than the Bluetooth dump. Looks like a charging grip is almost all you need to use the joycons as a controller, I'll have to get one and add button mapping support in to my program.

Still doesn't help with the Bluetooth protocol except to reinforce that yes, 0x80 is definitely a bridge to the wires and it's definitely not available over Bluetooth.

riking avatar Apr 11 '17 09:04 riking

Bit of a long shot, but input 33 and 129 look like pitch/yaw inputs of a gyro

ghost avatar Apr 11 '17 16:04 ghost

33 is the status report (0x21) sent in reply to a 0x1 HID output report and parsed here https://github.com/riking/joycon/blob/master/src/joycon_input.c#L53

riking avatar Apr 12 '17 04:04 riking