I thought I'd put this one on GitHub due to my own personal website being a) down and b) not able to handle the influx that this could generate.
I've been affiliated with the ckb-next project (if you could call it a project - it's basically myself, @tatokis and @light2yellow at the moment) for about six months now, and so far the project has been essentially on maintenance mode of the original ckb by @ccMSC. The team inherited a barely commented codebase filled with a lot of magic numbers and little explanation and tried their best to document it. It was hard work.
Not that Corsair have been much help. With no need to worry about backwards compatibility, they have broken the firmware protocol with every major revision, and some devices, such as the original M95 use an entirely different protocol at all. With version 3 around the corner (we have firmware files for it), another protocol change is incoming, and we needed help.
So, I took it upon myself to reverse engineer the version 2 protocol. While Corsair's changes have been breaking, they've not been a complete revamp; the rough outlines are the same. Having a guide to V2 would be a benefit to V3 as well.
The team didn't have much prior knowledge of how the protocol worked, other than that
learned through reading the ckb source code and through CUE wireshark dumps. But we
did know that 0e 01
was the firmware initialisation packet, so that was a start.
0e
commands are read commands; the device will respond to them when you poll for an
answer if they are valid, and that makes checking for valid 0e XX
commands easy
enough. You just walk through all values of XX and see what the device responds to.
From this, I got 0e 00
, 0e 01
, 0e 0e
, 0e 11
, 0e 13
, 0e 15
, 0e 16
,
0e 20
and 0e 40
. At first glance, 0e 00
, 0e 01
and 0e 0e
all seemed to do
similar, if not the same thing. (We would later learn that because the device reuses
the same 64-byte buffer, we were seeing the remnants of the reply to the 0e 01
packet.) I therefore decided to throw random numbers at the rest of the packet and
found that the device echoes the first four bytes of the command. From this we
assumed the first four bytes of the command were the most important ones; we had to
draw the line somewhere.
I asked @tatokis to run through the commands on his K70 RGB, and pruned 0e 11
as
something seemingly specific to my device. 0e 13
was dropped from his keyboard too, but ckb references it in mouse-specific code, so I kept that and instead decided to
explore it.`
Through poking the 3rd and 4th bytes, I found that the Corsair protocol will reply to
an invalid byte 3/4 by echoing it, but otherwise not changing the reponse payload.
Running through all permutations of 0e 13 XX
, I uncovered a fair chunk of
commands, some of which were in the ckb codebase (and thus could be documented from
that), and others which were not. Of particular note is 0e 13 11
, which I discovered
returned the RGB value of the logo light on my mouse.
Next on my list was 0e 15
, which the ckb code always sent alongside 0e 16
. Walking
through the 0e 15 XX
bytes showed that only 0e 15 01
was a valid command, and
likewise for 0e 16 01
. I need to investigate those more.
Armed with the knowledge that I had learned from 0e 13
and friends, I went back
through the others - 0e 00
, 0e 20
and 0e 40
did not reply with anything, meaning
it was possibly a bug to be accepted, or that it toggled some internal state. 0e 01
was scanned and found to respond the same regardless of what the 3rd and 4th bytes
were. 0e 0e
was found to reply with a single zero byte (I don't know what it means
either).
With 0e
cleared, I turned to one of the other packets I could find in the ckb code.
07
commands are write commands; they change internal state, and don't reply. The
lack of a reply makes it difficult to distinguish a valid packet from an invalid
packet, so I just naively scanned through all the 07 XX
commands.
And my mouse promptly reset.
Stepping through the commands manually, I found that 07 02
could reliably make my
mouse reset itself. By sending the command twice and checking if the second one fails
due to the device not existing, I could check if the command resets the device.
Cycling through the third byte, I found 07 02 00
was a reset, 07 02 01
reset
faster than that, and most of the packets were ignored until 07 02 aa
, which made my
mouse unresponsive (I initially nicknamed it the rat poison packet, until @tatokis
pointed to dmesg
output showing the mouse had mounted itself as a USB storage
device, indicating we were in bootloader/firmware update mode). That sweep put me to
here, which is more or less where I am at at the moment.
Join #ckb-next on Freenode - we'd all appreciate the company.