Reverse Engineering Rockband Guitar Controllers

The Rockband series of games was fantastic when it released, and represents a type of game that isn’t very common anymore: coop games. It is more fun to work alongside friends rather than competing with them. The original guitar controllers for Rockband on the Nintendo Wii were all wireless. This is great to cut down on wire clutter and trip hazards, but it has some major drawbacks. For one, wireless controllers need batteries to run. Secondly, and chiefly, both of the guitar controllers I had for Rockband both died in identical ways: the controller stopped connecting with its adapter. This renders them useless, and seems to be a very common fail-state for these controllers. In this project I reverse engineered the controller’s USB interface, set up an Arduino to mimic a guitar controller, and integrate the Arduino into the guitar controllers using a detachable USB wire instead of a wireless communication chip.

The guitar controllers use a USB HID interface to communicate with the Nintendo Wii. HID stands for Human Interface Device and is the interface typically used for keyboards, mice, and controllers that use USB. When using HID you send a packet with the HID descriptor which defines how data will be laid out in the periodic data packets. Then data packets are sent containing information in the same format described by the HID descriptor. So, first we need to determine what this HID descriptor is for the guitar controller.

To find the HID descriptor I plugged the wireless adapter for one of the controllers into my computer and used the following Linux commands:

lsusb
usbhid-dump -a bus:address > GuitarHIDDescriptor

The bus and address fields you will fill in based on the output of the lsusb command. Look for “Harmonix” or the maker of your guitar controller to find the correct entry. The usbhid-dump command will dump the output into a file called “GuitarHIDDescriptor”. You can then take the raw data and input into http://eleccelerator.com/usbdescreqparser/ to get something relatively human readable. Here is what I got:

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x35, 0x00,        //   Physical Minimum (0)
0x45, 0x01,        //   Physical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x0D,        //   Report Count (13)
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x0D,        //   Usage Maximum (0x0D)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x03,        //   Report Count (3)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x25, 0x07,        //   Logical Maximum (7)
0x46, 0x3B, 0x01,  //   Physical Maximum (315)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x65, 0x14,        //   Unit (System: English Rotation, Length: Centimeter)
0x09, 0x39,        //   Usage (Hat switch)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x65, 0x00,        //   Unit (None)
0x95, 0x01,        //   Report Count (1)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x46, 0xFF, 0x00,  //   Physical Maximum (255)
0x09, 0x30,        //   Usage (X)
0x09, 0x31,        //   Usage (Y)
0x09, 0x32,        //   Usage (Z)
0x09, 0x35,        //   Usage (Rz)
0x75, 0x08,        //   Report Size (8)
0x95, 0x04,        //   Report Count (4)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF,  //   Usage Page (Vendor Defined 0xFF00)
0x09, 0x20,        //   Usage (0x20)
0x09, 0x21,        //   Usage (0x21)
0x09, 0x22,        //   Usage (0x22)
0x09, 0x23,        //   Usage (0x23)
0x09, 0x24,        //   Usage (0x24)
0x09, 0x25,        //   Usage (0x25)
0x09, 0x26,        //   Usage (0x26)
0x09, 0x27,        //   Usage (0x27)
0x09, 0x28,        //   Usage (0x28)
0x09, 0x29,        //   Usage (0x29)
0x09, 0x2A,        //   Usage (0x2A)
0x09, 0x2B,        //   Usage (0x2B)
0x95, 0x0C,        //   Report Count (12)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x21, 0x26,  //   Usage (0x2621)
0x95, 0x08,        //   Report Count (8)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x0A, 0x21, 0x26,  //   Usage (0x2621)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
0x46, 0xFF, 0x03,  //   Physical Maximum (1023)
0x09, 0x2C,        //   Usage (0x2C)
0x09, 0x2D,        //   Usage (0x2D)
0x09, 0x2E,        //   Usage (0x2E)
0x09, 0x2F,        //   Usage (0x2F)
0x75, 0x10,        //   Report Size (16)
0x95, 0x04,        //   Report Count (4)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

Based on this (and let’s be honest some guessing), I came up with the following structure describing the data packets sent for the guitar controller:

typedef struct
{
uint16_t buttons;
uint8_t hatAndConstant;
uint8_t axis[4];
uint8_t reserved1[12];
uint64_t finalConstant;
} InstrumentButtonState;

From here, the next step is to determine the mapping of each button / control on the guitar to the corresponding field in this structure. Most of this was guess and check. Luckily, the drum controller has the exact same HID descriptor and so I was able to get a head start on this step by plugging a drum controller into my computer and using Wire Shark to analyze the HID packets from the drums in real time to determine the 5 color buttons, A, B, +, -, Up, Down, Left, Right, 1, and 2. This left guessing and checking for the strum (which turned out to just be Up and Down), overdrive tilt-switch, solo modifier, and whammy bar. I won’t explain the exact mapping of these controls here, but it is available in the code on Github.

From here, we need to program an Arduino to be able to transmit this data over a USB port. I chose an Arduino Pro Micro for this since it uses a microcontroller with a built in USB controller. Because of this, this model of Arduino is popular for creating HID devices. The Arduino software stack has an HID library which acts as a good base to use. I say ‘base’ instead of solution because (at the time I worked on this project) there is a bug in the HID code where every data packet starts with sending an ID. This causes a buffer overrun when sending a data packet since a byte is used for the ID. The code available on Github copies the Arduino HID code and removes this bug.

To get the Wii to see the Arduino as a valid controller, the vendor ID (VID) and product ID (PID) need to be set to match that of Wii guitar controllers. I found these values for a guitar controller by plugging in a wireless adapter for a guitar controller to my computer and looking at the USB devices visible from the computer. Then, this information needs to be populated into the Arduino Micro options in boards.txt so that the Arduino shows up as a guitar controller:

micro.build.vid=0x1bad
micro.build.pid=0x0004

As a side note, you could also make an Arduino act as a drum controller by changing just the PID here, everything else is the same! I believe the PID for a drum controller is 0x0005.

One last quirk that needs to be fixed in order to communicate with the Nintendo Wii is the HID endpoint number. To properly communicate with the Wii, data must be sent via endpoint 1. Unfortunately, by default, this endpoint is used by the Arduino serial communication library. You can disable this by adding a special compiler option to the build flags for the Arduino Micro in boards.txt:

micro.build.extra_flags={build.usb_flags} -DCDC_DISABLED

Disabling CDC will disable the serial communication via USB to the Arduino. This means that you will not be able to use the serial monitor, but more importantly it messes with programming the Arduino. The Arduino can still be programmed, but you will need to press the reset button on the Arduino to get it to show up in the Arduino IDE to target it, then after compilation when it attempts to upload the program to the board you need to press the reset button again. This is because there is a short window of time at boot where the serial communication is available for flashing firmware. Otherwise, you can use another Arduino as a serial programmer and avoid the USB interface entirely.

Most of the controls on the guitar controllers are pretty standard. The buttons buy-and-large act as normally open switches, and the whammy bar is a potentiometer. Wire these up to pass the data into the HID packet and you will have a working controller!

A few notes on how I put together the controllers and what I found:

  • I used pins in INPUT_PULLUP mode with !digitalRead() in order to avoid the extra complication of external pullup resistors.

  • 6-7 ground wires are needed to service all banks of switches and there are only 2 ground pins on the Arduino Micro. So, I soldered together a harness to expand 1 ground into 7.

  • Instead of using the micro USB-b port on the Arduino Micro, I got USB-C port / breakout boards from Amazon. Then I took a micro USB-b cable, cut it, and soldered the leads to the breakout board. I then mounted the USB-C port in the guitar controller case with hot glue. This allows me to have a detachable USB-C cable.

  • One of the guitar controllers I have is a Psyclone Essentials brand. The strum switches for this guitar were keyboard switches soldered into the main board. I couldn’t remove them because the main board was a structural support for hitting the buttons. However, with the added components on the main board these did not act like ideal switches. I had to use an analogRead() < 20 check instead of a digital read on these.

  • I had to replace a tilt switch that was faulty. You can get standalone tilt switches (the one I had looks like a capacitor but sounds like it has a little ball rolling around inside). I just taped the switch to the inside of the case in the correct orientation and treat it like any other button.

Best of luck with repairing your controllers! Feel free to use or modify my Arduino sketch here

Next
Next

Docker, Qt, Vulkan, and CI