Reverse-engineering a racing wheel's USB protocol
Adding a new wheel to CrossWheel means knowing exactly what bytes its official driver sends over USB to produce each force-feedback effect. There's no datasheet for that — you have to observe it. This post walks through how we do it: capture the real Windows driver's USB traffic while driving the wheel with known effects, then diff the packets to decode each field.
The trick that makes it tractable is changing one variable at a time. If you send a constant force at magnitude 2500, then 5000, then 7500, and capture each, the byte that counts up in lockstep is your magnitude field. Do that for every parameter and an opaque byte-soup turns into a decoded protocol.
To make that easy we built ffbprobe, a tiny Windows console tool that enumerates your wheel, then plays a scripted sequence of DirectInput effects — one parameter changed per step — logging a timestamp for each so you can line the packets up afterwards.
What you need
- A Windows machine (or a Windows VM with USB passthrough) with the wheel's official driver installed — G HUB for Logitech, the Thrustmaster driver for Thrustmaster, and so on. This matters: the translation from a DirectInput effect into USB packets happens inside that driver. Without it you'd be capturing the wrong protocol.
- Wireshark with USBPcap. The Wireshark installer for Windows offers USBPcap as a checkbox — take it. USBPcap is the kernel driver that captures raw USB traffic; Wireshark is what turns that capture into readable, labelled packets. You need both.
- ffbprobe.exe — download it here:
It's a single statically-linked executable — no installer, no runtime to ship alongside it. Just download and run it from a terminal.
Step 1 — confirm the wheel
Open a terminal (Command Prompt or PowerShell), cd to wherever you downloaded
it, and list the force-feedback devices:
ffbprobe.exe --list
You'll get something like:
Force-feedback devices:
[0] 044f:b696 Thrustmaster Advanced Mode Racer
Note the VID:PID (here 044f:b696) — you'll use it to bind exactly this wheel
if more than one force-feedback device is attached.
Step 2 — start capturing
- Open Wireshark and start a capture on the USBPcap interface that corresponds to the wheel's bus.
- Filter the noise down to just your wheel and just the commands going to it.
A good starting display filter is the host→device direction on the wheel's
address; for Thrustmaster wheels the force-feedback commands ride in output
report
0x60, sousb.capdata[0] == 60isolates them. - Set View → Time Display Format → Time of Day so Wireshark's timestamps match the clock ffbprobe logs against.
You can also capture headless with USBPcapCMD.exe -o wheel.pcap and do all the
filtering later in Wireshark — either works.
Step 3 — run the script
With the capture running:
ffbprobe.exe --vidpid 044f:b696
(Or --pick 0 by index, or no arguments to be prompted.) The tool acquires the
wheel exclusively, turns off autocenter so it doesn't add uncommanded traffic,
and waits. Press Enter to start the scripted sequence.
Each step prints a banner with a wall-clock timestamp and the exact parameters it sent, for example:
[14:02:07.418] CONSTANT force magnitude=5000 direction=0 deg duration=500ms
The script sweeps, one variable at a time:
- constant force — magnitude 0 → 10000, then reversed, to find the force field, its sign encoding and full-scale value
- direction — same magnitude rotated through 0/90/180/270°
- update semantics — changing a running effect with and without
DIEP_NORESTART, to see whether an update restarts the effect - spring — coefficient sweep, then saturation and deadband in isolation
- damper / friction / inertia — the other condition effects
- periodics — sine, then phase / offset / period changed one at a time, then with an envelope; plus square, triangle and both sawtooths
- ramp — start level to end level
The whole run takes a couple of minutes. When it prints SCRIPT COMPLETE, stop
the Wireshark capture.
Step 4 — two files come out
A session produces two files, and you need both:
- The USB capture — the
.pcapngyou saved from Wireshark (or the.pcapfromUSBPcapCMD). These are the actual packet bytes. - The step log — ffbprobe writes
ffbprobe-steps.login the directory you ran it from (override with--log <path>). One timestamped banner per step.
Copy both to wherever you're doing the analysis. Because both files are stamped with the same Windows clock, they align by wall-clock time regardless of what machine you open them on: the packets sitting between two banner timestamps are the USB traffic for exactly that one parameter change.
Step 5 — decode by diffing
Now the actual reverse-engineering. Open the capture, and for each pair of adjacent steps, diff the payloads:
- The bytes that changed between "magnitude=2500" and "magnitude=5000" are the magnitude field. Their values tell you the encoding — signed 16-bit little-endian, half-scale, whatever it turns out to be.
- The bytes that move when only the deadband changed are the deadband field.
- The packet that appears when you call start, and the one on stop, give you the play/stop opcodes.
Staring at Wireshark's hex pane for hundreds of near-identical packets gets old
fast, so it helps to export the payloads as text — tshark -r wheel.pcapng -Y 'usb.capdata[0]==60' -T fields -e frame.time -e usb.capdata — and script a
byte-column diff, or just sort | uniq -c to collapse the thousands of repeated
identical packets and let the interesting ones stand out.
Step 6 — check it against what's known
If the wheel is a variant of one that's already supported, you're usually not decoding from scratch — you're confirming a layout and hunting for the handful of bytes that differ. Line your decoded fields up against the existing packet builder for that family and the deltas are all that's left to work out. That's the difference between weeks of work and an afternoon.
And decoding is only a hypothesis until the hardware obeys it: once you believe a field is the force magnitude, send that packet yourself and confirm the wheel pushes the way you expect. Round-trip confirmation is what turns "I think this is the protocol" into "this is the protocol."
ffbprobe is a developer tool, not part of the CrossWheel app — if you're just here to use your wheel in a game, head back to the download page. But if you're curious how support for a new wheel gets built, this is the first step of it.