CROSS WHEEL

Want your wheel supported? Email me at support@crosswheel.seastian.com

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

Download ffbprobe.exe

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

  1. Open Wireshark and start a capture on the USBPcap interface that corresponds to the wheel's bus.
  2. 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, so usb.capdata[0] == 60 isolates them.
  3. 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:

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:

  1. The USB capture — the .pcapng you saved from Wireshark (or the .pcap from USBPcapCMD). These are the actual packet bytes.
  2. The step log — ffbprobe writes ffbprobe-steps.log in 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:

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.