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:
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.
usbpcap_header_len
irp_id
usbd_status
function
irp_info
bus id
device address
endpoint
transfer_type
packet data length
report id
vendor data
padding (only first 15 bytes have been observed to be used)
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
Turn sleep on in iCUE
Packet 1 = Feature Select
0d
, Data Bytesff
Packet 2 = Feature Select
0e
, Data Bytes specify the when to start timeTurn sleep off in iCUE
Packet 1 = Feature Select
0d
, Data Bytes00
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.