Reverse Engineering the USB Driver for the  Corsair Virtuoso SE Headset

Reverse Engineering the USB Driver for the Corsair Virtuoso SE Headset

12/17/22: My goal is to have the ability to control the LEDs of the headset via the program ckb-next and sidetone with HeadsetControl

This device uses the Slipstream Wireless Dongle, and I won't be. I'm figuring this out as I go, and adding a wireless dongle (that neither program has worked on yet) will add a layer of complexity to something I already don't know how to do. For now, USB only.

1/2/23: As you can see in the "USB Description Overview w/ lsusb" section, I've managed to map out my best estimation of what each nibble does. I am working solely on ckb-next integration for LED control at this stage. The unfortunate issue I face is that I was hoping I could build off of the bragi protocol already implemented in ckb-next, but it looks like it isn't compatible.

1/3/23: When I first started out, I focused on the packets sent to control functionality. With the new info that it isn't bragi compatible, I have to decipher the initiation packets sent when iCUE first recognizes the headset. Yay me.

References:

USB.org - HID 1.1 Specsheet

How to setup Wireshark for the purpose of USB packet capture on liquidctl wiki

beyondlogic: USB in a NutShell

liquidctl Wiki Techniques for analyzing USB protocols

A Reddit post outlining the reverse engineering of a Corsair Keyboard

ckb-next wiki on the Corsair Protocol

liquidctl wiki on the Corsair Commander Core

FFR

Device ID: 0x0a3d

dev_address determined from the "GET DESCRIPTOR Response DEVICE" Packet, which is like the fourth one sent in in Wireshark, depending on how many USB devices are passed to the VM. For me, #1 was the VirtualBox mouse driver.

Wireshark Filter:

((((usb.device_address == 2)) && (usb.data_len == 64)) ) && (usb.irp_info.direction == 0x0)

Bash output in OSS-Code looks pretty when put in a JSON file.

USB Description Overview w/ lsusb

lsusb -v -d 1b1c:0a3d verbose output for the specific device

Bus 001 Device 008: ID 1b1c:0a3d Corsair CORSAIR VIRTUOSO SE USB

1b1c:0a3d = Vendor ID, unique to manufacturer (usually)

1b1c:0a3d = Product ID, unique to device

Ref: USB HID Appendix

Device Descriptor

USB device required by OS to explain descriptor. Not all fields are required.

idVendor 0x1b1c Corsair

iProduct 2 CORSAIR VIRTUOSO SE USB

Configuration Descriptor

MaxPower 150mA

Interface Descriptor

bInterfaceProtocol 0 "none," not a keyboard or mouse

The headset has 4 interfaces starting at 0. Interface 3 is the HID device to control the LEDs and other settings.

Endpoint Descriptor

The host computer initiates everything. Interrupts in USB are seen by the host at a certain poll rate and know, based on this descriptor, what that interval is, and what packet size is expected. In this case, 64 bytes.

bEndpointAddress 0x81 EP 1 IN Endpoint 1 IN, Most significant bit sent 8

The host is telling the device to send it data.

bEndpointAddress 0x01 EP 1 OUT Endpoint 1 OUT, Most significant bit sent 0

The host is giving the device stuff to do.

Frame Analysis: Initialization

Raw Frame

0000   [1b 00] [e0 a6 c5 1d 82 91 ff ff] [00 00 00 00] [09 00]
0010   [00] [01 00] [02 00] [01] [01] [40 00 00 00] {[02] [08] [(02 11 00
0020   00 00 00 00 00 00 00 00 00 00 00) 00 00 00 00 00
0030   00 00 00 00 00 00 00 00 00 00 00] [00 00 00 00 00
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0050   00 00 00 00 00 00 00 00 00 00 00]}

91 bytes on the wire, this specific frame is the first interrupt_out sent to the headset.

I broke the bytes up in square brackets by function, according to Wireshark. Curly brackets indicate the HID data. I'll break down each square bracket by field name indicated in Wireshark.

  1. usbpcap_header_len

  2. irp_id

  3. usbd_status

  4. function

  5. irp_info

  6. bus id

  7. device address

  8. endpoint

  9. transfer_type

  10. packet data length

  11. report id

  12. vendor data

  13. padding (only first 15 bytes have been observed to be used)

  14. not specified

Only items 11, 12, and 13 are relevant HID data.

Initialization Frames

There are 33 of these.... Hopefully they'll help decipher the Interrupt_out mappings.

HID Data Decoding

INTERRUPT_OUT Mapping

Pkt  Purpose            Out    IN
[0]  Report ID          02     01
[1]  Vendor Data        08     00
[2]  Commands                  Echo
[3]  Feature Select
[4]  Data (1)
[5]  Data (2)
[6]  Unknown (2)
[7]  Unknown (3)
[8]  Red
[9]  Mic (1)? 
[10] Mic (2)
[11] Green 
[12] Charge LED
[13] Mic (3)
[14] Blue

[2] Commands

Possibly indicates what feature of the headset is being modified

*** Seen in Initialization ***
01
02
0d Sleep Enabled Status
09
08
05

*** Seen while expirementing with settings ***
06 RGB
0e Sleep wait time

*** Seen Ocasionally with no user input ***
12

[3] Feature Select

Indicates the feature being written to/read from

00 LED
02 Brightness
0d Sleep toggle 
0e Sleep time

[4] [5] Data Bytes

I'm pretty sure these two bytes are the values passed by whatever setting is altered. I have only seen them change when changing the sleep time and brightness.

Case of the Battery: I started a capture when I knew the headset wasn't fully charged. The reply packets indicate that Data (2) was the only byte that changed slowly over a few minutes. "XX 03" seems to indicate that "XX" (data 1) is the battery level, and "03" (data 2) maybe indicates that data 1 is outputting battery info. TODO: VERIFY SETTING INDICATOR DURING CHARGE

09 00     Mic LED color
XX 03     Battery Level

XX = byte changes

[9][10][13]Microphone

Mic (2)    ff
Mic (3)    00
Microphone is Muted and "Disabled LED When Mic is Active" does nothing.
Mic LED is RED

Mic (2)    00
Mic (3)    ff
Microphone is not Muted and "Disabled LED When Mic is Active" is disabled.
Mic LED is Green

Mic (2)    00
Mic (3)    00
Microphone is not Muted and "Disabled LED When Mic is Active" is enabled.
Mic LED is off

Mic (2)    ff
Mic (3)    ff
unknown
Mic LED is yellow


I think Mic (1) indicates that the Green RGB contol byte is being used to control the charge LED rather than the main logo LEDs. "e1"

Data (1) is 09. Also, the button on the microphone controls the mute/unmute bytes.

Microphone LED is also full RGB which can be observed when switching the headset to wireless mode and may be independently controllable, but iCUE won't let users change this.

Features without dedicated bytes

Sleep Toggle and Wait Time

Sleep on, 2 packets. Sleep off, 1 packet. Change sleep time, 2 packets.

When sleep is turned on, the ("when to start" setting in iCUE) sleep time must be re-assigned every time. The two Data Bytes change when the sleep time value has been changed. I think the Setting Indicator byte flips to 0d to toggle the actual sleep function since all other values are zeros at this point, and it is present for the first sleep-on packet and the only sleep-off packet. The byte goes to 0e only after the first sleep-on packet to reassign the sleep time to the newly re-enabled sleep function.

Order of operations

  1. Turn sleep on in iCUE

    Packet 1 = Feature Select 0d , Data Bytes ff

    Packet 2 = Feature Select 0e , Data Bytes specify the when to start time

  2. Turn sleep off in iCUE

    Packet 1 = Feature Select 0d , Data Bytes 00

Charge indicator LED

When the headset is connected VIA USB and fully charged, this led flashes. The speed at which interrupt-out packets are sent corresponds to the speed of the LED pulsing. When it's solid green, it sticks to 80. This LED is also full RGB which can be observed when switching the headset to wireless mode and may be independently controllable, but iCUE won't let users change this.

This byte is the dedicated brightness of the charge led.

You can send datum to this byte to change the intensity, but it is overwritten by, assumably, the firmware.

Software Brightness control packets

Six packets are sent to the device when using the dedicated brightness slider.

Logo-LED

No contention here. Pretty obvious which bytes control the primary LED just by toggling them.

Software only

There are no packets sent from the host for the following functions: Sidetone and its volume slider, 7.1/Stereo, lighting link presets, EQ presets, Voice Prompts, Battery indicator in the status bar.