Hacking Joy-Con HID Reverse Engineering

  • Thread starter Thread starter shinyquagsire23
  • Start date Start date
  • Views Views 19,013
  • Replies Replies 25
  • Likes Likes 19

shinyquagsire23

SALT/Sm4sh Leak Guy
Member
Joined
Nov 18, 2012
Messages
1,979
Reaction score
3,289
Trophies
2
Age
28
Location
Las Vegas
XP
3,790
Country
United States
Making a thread to get a bit of visibility and maybe some input from people, but over the last week I've been working on reverse engineering parts of the Joy-Con communication methods, and so far I've had success talking straight with the rails using UART and an ESP32, and more recently, HID via Joy-Con Charging Grip.

Contrary to the name, the Joy-Con charging grip actually does quite a bit more than charge. Plugging it into a computer exposes two USB interfaces (one per Joy-Con) with two endpoints which can be talked with via the HID protocol. By dumping the firmware of the STM32 chip on the charging grip I've also managed to reverse engineer the custom HID commands which allowed me to talk more extensively with the Joy-Con. My code repository for my HID Joy-Con research can be found at https://github.com/shinyquagsire23/HID-Joy-Con-Whispering. Currently it is untested with a Pro Controller, but it has the same STM32 chip as the grip and the firmware is similar, so if anyone has a Pro Controller and wants to try seeing if it works with one, feel free.

What can it do now?
Currently with what I have, the following is already done:
  • Retrieving full input packets from each Joy-Con, including analog joystick values, buttons, etc.
  • Joy-Con SPI firmware dumping. Since UART commands exist to read from the SPI firmware, and the HID protocol exposes a command to send UART commands, the entirety of the Joy-Con on-board SPI flash can be dumped with only a charging grip
What else is possible?
Anything which the Joy-Con can do while clicked into console should be possible. This includes HD rumble, NFC, IR, and other things. It should also be possible to write Linux or maybe Windows drivers which can interact with the Joy-Con over HID so that the device can act as a single controller with what is already done.

What needs to be done?
There are still a lot of unknowns with the wired UART protocol, specifically with most of the extra peripherals like HD rumble. The best way to document these, unfortunately, is by using a logic analyzer to watch the UART communication while it is attached to a console, but dekuNukem has already done a lot of this at his repo here. Additionally, it seems despite Bluetooth using HID, the USB-C HID protocol is not the same as the Bluetooth protocol, which means that reversing Bluetooth will take either Switch privilege escalation or Joy-Con firmware reverse engineering.
 
I'm having trouble compiliing it in debian jessie. What deps am I missing?

Code:
In file included from /usr/include/c++/4.9/chrono:35:0,
                 from hidtest.cpp:7:
/usr/include/c++/4.9/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
 #error This file requires compiler and library support for the \
  ^
hidtest.cpp: In function ‘void spi_flash_dump(hid_device*, char*)’:
hidtest.cpp:62:8: error: ‘uint32_t’ was not declared in this scope
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
        ^
hidtest.cpp:62:17: error: expected primary-expression before ‘)’ token
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
                 ^
hidtest.cpp:62:52: error: expected primary-expression before ‘)’ token
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
                                                    ^
hidtest.cpp:62:93: error: expected primary-expression before ‘)’ token
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
                                                                                             ^
hidtest.cpp: In function ‘int main(int, char**)’:
hidtest.cpp:208:28: error: ‘std::chrono’ has not been declared
  unsigned long last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
                            ^
hidtest.cpp:208:82: error: ‘std::chrono’ has not been declared
  unsigned long last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
                                                                                  ^
hidtest.cpp:210:45: error: ‘std::chrono’ has not been declared
      printf("%02llums delay,  left ", (std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1)) - last);
                                             ^
hidtest.cpp:210:99: error: ‘std::chrono’ has not been declared
      printf("%02llums delay,  left ", (std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1)) - last);
                                                                                                   ^
hidtest.cpp:211:18: error: ‘std::chrono’ has not been declared
      last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
                  ^
hidtest.cpp:211:72: error: ‘std::chrono’ has not been declared
      last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
 
I'm having trouble compiliing it in debian jessie. What deps am I missing?

Code:
In file included from /usr/include/c++/4.9/chrono:35:0,
                 from hidtest.cpp:7:
/usr/include/c++/4.9/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
 #error This file requires compiler and library support for the \
  ^
hidtest.cpp: In function ‘void spi_flash_dump(hid_device*, char*)’:
hidtest.cpp:62:8: error: ‘uint32_t’ was not declared in this scope
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
        ^
hidtest.cpp:62:17: error: expected primary-expression before ‘)’ token
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
                 ^
hidtest.cpp:62:52: error: expected primary-expression before ‘)’ token
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
                                                    ^
hidtest.cpp:62:93: error: expected primary-expression before ‘)’ token
  for(*(uint32_t*)(&spi_read[0x13]) = 0; *(uint32_t*)(&spi_read[0x13]) < 0x80000; *(uint32_t*)(&spi_read[0x13]) += 0x1C)
                                                                                             ^
hidtest.cpp: In function ‘int main(int, char**)’:
hidtest.cpp:208:28: error: ‘std::chrono’ has not been declared
  unsigned long last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
                            ^
hidtest.cpp:208:82: error: ‘std::chrono’ has not been declared
  unsigned long last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
                                                                                  ^
hidtest.cpp:210:45: error: ‘std::chrono’ has not been declared
      printf("%02llums delay,  left ", (std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1)) - last);
                                             ^
hidtest.cpp:210:99: error: ‘std::chrono’ has not been declared
      printf("%02llums delay,  left ", (std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1)) - last);
                                                                                                   ^
hidtest.cpp:211:18: error: ‘std::chrono’ has not been declared
      last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
                  ^
hidtest.cpp:211:72: error: ‘std::chrono’ has not been declared
      last = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
I don't know for sure, but I suspect this line is important:
#error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
 
It compiled after I added -std=gnu++11 but running it with a Pro controller connected returns:
Code:
Could not get handle(s) for Joy-Con! Handles L 00000000, R 29b14b50
The R number is random each time.


Pro controller in lsusb -v:
Code:
Bus 001 Device 002: ID 057e:2009 Nintendo Co., Ltd
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x057e Nintendo Co., Ltd
  idProduct          0x2009
  bcdDevice            2.00
  iManufacturer           1 Nintendo Co., Ltd.
  iProduct                2 Pro Controller
  iSerial                 3 000000000001
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              500mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     203
         Report Descriptors:
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               8
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               8
Device Status:     0x0001
  Self Powered
 
Last edited by normal19,
since the pro controller uses the exact same firmware, theoretically, once the joycons are completely exposed, the pro controller should be exactly the same right?
 
since the pro controller uses the exact same firmware, theoretically, once the joycons are completely exposed, the pro controller should be exactly the same right?
I imagine it's not exactly the same because Pro controller has most but not all joycon features like IR.
 
It compiled after I added -std=gnu++11 but running it with a Pro controller connected returns:
Code:
Could not get handle(s) for Joy-Con! Handles L 00000000, R 29b14b50
The R number is random each time.


Pro controller in lsusb -v:
Code:
Bus 001 Device 002: ID 057e:2009 Nintendo Co., Ltd
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x057e Nintendo Co., Ltd
  idProduct          0x2009
  bcdDevice            2.00
  iManufacturer           1 Nintendo Co., Ltd.
  iProduct                2 Pro Controller
  iSerial                 3 000000000001
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              500mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     203
         Report Descriptors:
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               8
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               8
Device Status:     0x0001
  Self Powered
Hm, looks about the same as the grip with just one less interface, so I think it should work if you just remove all references to handle_l like this? I'll need to figure out exactly how I want to handle Pro Controllers because the grip requires both Joy-Con to initialize. I'll also go ahead and add -std=gnu++11 to the README, latest GCC uses C++11 by default but Debian tends to stick to older versions for a while.
 
Hm, looks about the same as the grip with just one less interface, so I think it should work if you just remove all references to handle_l like this? I'll need to figure out exactly how I want to handle Pro Controllers because the grip requires both Joy-Con to initialize. I'll also go ahead and add -std=gnu++11 to the README, latest GCC uses C++11 by default but Debian tends to stick to older versions for a while.
This program recognizes pro controller, the controller vibrates lightly for a moment and then it outputs mostly this
Code:
Found Joy-Con (R), MAC: 08:30:00:00:00:01
Switching baudrate...
Start input poll loop
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 11 ac 3b 7f 00 00 b0 f6 10 ac 3b 7f 00 00 c0 d3 f4 37 ff 7f 00 00 00 25 11 ac 3b 7f 00 00 e8 d3 f4 37 ff 7f 00 00 a8 21 11 ac 3b
            right 80 92 00 01 01 00 00 00 1f 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 00 00 00 00 00
And sometimes this
Code:
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 11 ac 3b 7f 00 00 b0 f6 10 ac 3b 7f 00 00 c0 d3 f4 37 ff 7f 00 00 00 25 11 ac 3b 7f 00 00 e8 d3 f4 37 ff 7f 00 00 a8 21 11 ac 3b
            right 81 01 00 03 56 2c fa 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 00 00 00 00
pressing buttons and moving sticks doesn't change anything.
 
Last edited by normal19,
I've been waiting for someone to do this! I like using the Joy-Con on my computer but I don't like that you can't get analog values for the sticks. And that there's no rumble. And it's great that you can use the grip to do this over USB! The Bluetooth on my computer is pretty bad.

So eventually (hopefully soon?), we will be able to use the charge grip to use two Joy-Con like a traditional controller over USB?
 
This program recognizes pro controller, the controller vibrates lightly for a moment and then it outputs mostly this
Code:
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 11 ac 3b 7f 00 00 b0 f6 10 ac 3b 7f 00 00 c0 d3 f4 37 ff 7f 00 00 00 25 11 ac 3b 7f 00 00 e8 d3 f4 37 ff 7f 00 00 a8 21 11 ac 3b
            right 80 92 00 01 01 00 00 00 1f 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 00 00 00 00 00
And sometimes this
Code:
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 11 ac 3b 7f 00 00 b0 f6 10 ac 3b 7f 00 00 c0 d3 f4 37 ff 7f 00 00 00 25 11 ac 3b 7f 00 00 e8 d3 f4 37 ff 7f 00 00 a8 21 11 ac 3b
            right 81 01 00 03 56 2c fa 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 00 00 00 00
pressing buttons and moving sticks doesn't change anything.
Hm, the "left" one is irrelevant, I just didn't want to mess with the formatting. It looks like it didn't successfully handshake since it's sending MACs, maybe try a few more times (and without firmware dumping enabled)? During development it was finnicky with handshaking sometimes, I might be missing something needed for Pro Controllers. Also, with the firmware dump, it should be 0x80000 bytes large, though that does look valid for what you have. It almost sounds like it's working at least for a little bit, and then just disconnecting somehow? Definitely a good sign though, I have a copy of the STM32 firmware on the controller so I might see if there's significant differences which might make a difference.

EDIT: Maybe also take out the baud switch and the second handshake packet as well? Maybe the Pro Controller initializes UART at a higher baudrate since it's all internal.
 
Last edited by shinyquagsire23,
I ran the program several times and the output is always the same. The delay is usually 08ms but it occasionally comes out as 07ms and 16ms
 
I commented out
Code:
    printf("Switching baudrate...\n");

   memset(buf_r, 0x00, 0x40);
   buf_r[0] = 0x80;
   buf_r[1] = 0x03;
   hid_exchange(handle_r, buf_r, 0x2);
  
   memset(buf_r, 0x00, 0x40);
   buf_r[0] = 0x80;
   buf_r[1] = 0x02;
   hid_exchange(handle_r, buf_r, 0x2);
but it does the same thing, I ran the program a few times and noticed the output is different each time, but it still prints mostly the same random line over and over with a different random line about every second.
Code:
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 60 5b 68 7f 00 00 b0 f6 5f 5b 68 7f 00 00 a0 7d 3c 70 fe 7f 00 00 00 25 60 5b 68 7f 00 00 c8 7d 3c 70 fe 7f 00 00 a8 21 60 5b 68
            right 81 01 00 03 56 2c fa 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 00 00 00 00
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 60 5b 68 7f 00 00 b0 f6 5f 5b 68 7f 00 00 a0 7d 3c 70 fe 7f 00 00 00 25 60 5b 68 7f 00 00 c8 7d 3c 70 fe 7f 00 00 a8 21 60 5b 68
            right 81 01 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 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
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 21 60 5b 68 7f 00 00 b0 f6 5f 5b 68 7f 00 00 a0 7d 3c 70 fe 7f 00 00 00 25 60 5b 68 7f 00 00 c8 7d 3c 70 fe 7f 00 00 a8 21 60 5b 68
            right 80 92 00 01 01 00 00 00 1f 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 00 00 00 00 00
Run it again and it prints
Code:
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 a1 b6 b1 06 7f 00 00 b0 d6 b6 b1 06 7f 00 00 30 85 6b 27 ff 7f 00 00 00 a5 b6 b1 06 7f 00 00 58 85 6b 27 ff 7f 00 00 a8 a1 b6 b1 06
            right 80 92 00 01 01 00 00 00 1f 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 00 00 00 00 00
08ms delay,  left 80 92 00 01 01 00 00 00 1f 08 00 00 01 00 00 00 a8 a1 b6 b1 06 7f 00 00 b0 d6 b6 b1 06 7f 00 00 30 85 6b 27 ff 7f 00 00 00 a5 b6 b1 06 7f 00 00 58 85 6b 27 ff 7f 00 00 a8 a1 b6 b1 06
            right 81 01 00 03 56 2c fa 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 00 00 00 00
 
Last edited by normal19,
something tells me theres a reason this can't be done since I have never heard of anything like this on other systems
Somebody edit me if I'm wrong, but wasn't it done with the wii u this way? I mean, one of the major exploits was found due to unsecured connection between the gamepad and the console.
 
Somebody edit me if I'm wrong, but wasn't it done with the wii u this way? I mean, one of the major exploits was found due to unsecured connection between the gamepad and the console.
That wasn't an exploit, it was a way of controlling and grabbing feed from the console wirelessly by using your computer as a gamepad
 
  • Like
Reactions: jt_1258

Site & Scene News

Popular threads in this forum