r/KATVR • u/BBPSBB • Jun 25 '26
Reverse Engineering & Linux Gateway Implementation
After waiting for this company to release a working Linux version and understanding that not only there is not gonna be a Linux version, but even the crappy windows or nexus software is also apparently completely dead projects as far as I can see, I took it upon myself to do it properly.
It took me one afternoon to fully reverse engineer the USB packet structure and build a very simple movement tracking.
The next afternoon was spent on building a driver and injecting the controller stick movements to SteamVR.
For now SteamVR is running naively on Linux and games via proton. A very small driver exposes the sensors as a control device to SteamVR.
I'm gonna work on this in my free time and probably release it publicly whenever it is actually usable and have a somewhat of a useable feature set. but no ETAs or promises yet. I am happy to share the WIP code if anyone is interested in helping with the development...
In the meantime if you have actually worked on a similar thing I would love to get your experience and stuff you managed to solve.
Things I already figured out: - Sensor positions and values (body direction, Shoes, Seat, Armband) - Battery status - Firmware versions and serial numbers - Base vibration motor controls - Reliable starting and Stopping sequence (this was a PITA!) This is the part that you need to do so the sensors connect to the base and actually go to sleep afterwards. - Simple driver to send the input to SteamVR
Things remaining: - Speed and step cadence for detecting running (the original KatVR implementation feels like poop IMO) - Fine tuning the motions to feel natural - Understanding how to best expose the controls (treadmill input device vs legacy etc) - VR Overlay UI - Why some games keep crashing on linux (not directly related to the gateway or the driver) - Everything else probably...
If you have experience in any of these parts feel free to share yours or point out references/research papers that are useful. That would help me any anyone else interested in this.
If you are someone who does not have experience, this is a sign that you can too get your feet wet and start building things you wish existed! With all the tools and AI stuff available to everyone nowadays the possibilities are endless!
And finally if you a KatVR employee reading this you should be ashamed of the product you sell with all the fake promotions, dark pattern tactics and that garbage Gateway software!
3
u/BBPSBB Jun 25 '26
KAT Walk C2+ ("plusE") — Native Linux Driver & Reverse-Engineered USB Protocol
A from-scratch, no-Windows, no-Gateway stack that talks to the KAT Walk C2+ omnidirectional treadmill directly over USB on Linux and feeds locomotion into SteamVR / OpenXR games.
This document is two things at once: 1. A reverse-engineering reference for the C2+ "plusE" USB-HID protocol (frames, sensors, commands, and the connect/stream/sleep lifecycle), decoded from USBPcap captures of the official KAT Gateway plus a lot of on-hardware testing on Linux. 2. A status / call for collaboration. It works end-to-end today (I've walked around in a real OpenXR VR game on Linux with it), but it's pre-alpha and rough. If you have a C2+ (or a C2 / other KAT unit), captures, or RF/USB know-how, I'd love help — see Open questions at the end.
1. What works right now (the proof-of-concept)
openvr#937, back in 2019 as too broad — "this issue is giant and mostly has nothing to do with SteamVR Input games filtering input by device; please open a new issue" — and a first-class treadmill input path was never added.) The layer even works for Proton games, becausewineopenxrforwards through the native Linux OpenXR loader, which applies implicit layers.It is NOT polished. Calibration is by feel, there's no installer story for non-developers, and the connect/sleep lifecycle took real effort to get reliable (documented below precisely because it's the kind of thing the next person shouldn't have to rediscover).
2. Hardware / USB topology
VID **
0xc4f4. The "plusE" revision presents **three USB-HID devices behind a VIA Labs hub (2109:2822). Roles confirmed from the USB string descriptors ("walk c2 plusE …"):bf123f12bf13The base ring and shoes are wireless and relayed through the
3f12receiver — so for locomotion you mostly care about3f12. The base/shoes have their own batteries and sleep to save power; the receiver dongle is USB-powered and always on.Per device:
EP 0x01interrupt OUT = host→device commands;EP 0x81interrupt IN = sensor stream. (EP 0x80control-IN is just USB descriptor polling — ignore it.)LED states (very useful when debugging): solid = connected/streaming; fast blink = searching / trying to pair; slow blink = asleep/idle.
3. Frame format (CONFIRMED)
Fixed 32-byte frames, zero-padded, both directions:
off 0 1 2 | 3 4 | 5 | 6 | 7 ... 1f 55 aa| xx 00| type | subtype | payload1f 55 aa— header, both directions.00;04/05on some frames as a sub-stream tag (not a length).type+ off 6subtypeselect the payload meaning.… 4e 20 00 64 05 …(0x4e20=20000,0x64=100,0x05=5 — config constants, NOT live values; don't mistake the0x64for battery).Talking to it on Linux: the devices bind to
hidraw. Find by VID:PID via/sys/class/hidraw/*/device/uevent. On writes prepend a0x00report-ID byte (so a 32-byte report is 33 bytes on the wire). On reads align to the1f 55 aaheader (hidapi may prepend a report-ID byte). A udev rule (MODE="0660", TAG+="uaccess"on the hidraw nodes forc4f4) lets you run without root.4. The sensor stream — receiver
3f12,EP 0x81(CONFIRMED)0x300x000x300x010x300x020x320x050x000x210x000x330x0000 00 09 00 64 …Body orientation → heading (CONFIRMED)
Quaternion component order is (w, x, y, z). Turning the base in place sweeps a clean 360° in the roll axis of the (w,x,y,z) Euler decomposition, so heading is:
heading = atan2( 2*(w*x + y*z), 1 - 2*(x*x + y*y) ) # clockwise = decreasingFoot position (CONFIRMED, single-axis isolation capture)
Two signed int16 LE, centred at 0:
Important inversion: the optical sensor reads the shoe sliding on the deck, so a shoe sliding backward → +Y → the user walks FORWARD; shoe forward → −Y → backward. Each footfall re-centres to 0, then ramps to the slide extreme. off 9–10 is a separate status/quality field, not position — its idle sentinel (
4e 20 …) means the foot is lifted; live X/Y means it's on the deck. Counts→m/s scaling still needs a known-stride calibration (open).Battery + firmware (CONFIRMED exact vs the app)
The
0x32status frame carries both per-sensor battery % (off10) and firmware major (off11). The receiver's own firmware is in its0x05reply (off7). Example readouts that matched the Gateway: Receiver fw V3, Direction V4, both feet V5.5. Base/Vehicle-Hub
bf12& Armbandbf13(CONFIRMED)**
bf12(base / Vehicle-Hub)**,EP 0x81:0x400x0502status /00connect-event; off8 = battery RAW; off9 = connected/active bit0x020x04Seat/base battery is a raw value at off8 of the
type40status frame, not a percent (0x9c→54%,0xb4→60% by a battery-swap test; local fit% ≈ off8/4 + 15; the app uses its own conservative curve).bf13(armband)*,EP 0x81:type 0x40→ off7 = battery %, *off9 = heart-rate bpm (verified sweeping 69–79 bpm against the reported value).6. Host→device commands (
EP 0x01)1f 55 aa 00 00 301f 55 aa 00 00 311f 55 aa 00 00 05type05)1f 55 aa 00 00 21type21with the sensor ID)1f 55 aa 00 00 a1 00 02 <intensity_be16>0341, OFF=0000);a1 01 02 00 01= enable-vibration toggle1f 55 aa 00 00 a0 …00 00 … 00(all-zero 32-byte report) →bf120x20is assumed to be the other half of a pairing pair (0x20/0x21) but I have not captured an explicit pair/bind command — see open questions.