`XBOX_RDESC` is the only report descriptor in `pf-gamepad` that was hand-written rather than
captured off hardware, and its own provenance warning has now come true three times. The fix for
that class of bug is not another careful reading — it is a tool that goes and asks the device.
`tools/hid-descriptor-dump` does that: it dumps a real HID device's report descriptor, decodes it
into an annotated item listing plus a bit-offset LAYOUT TABLE, and can decode a blob we already
ship through the same decoder (`--rust-source <file> --symbol <NAME>`) so the two are diffable
line for line. `--read N` pulls live wire bytes, which is the only ground truth a reconstructed
descriptor cannot give you.
Deliberately NOT a workspace member — it pulls `hidapi`, a C library wanting libudev on Linux,
which has no business in `cargo build --workspace` or on a CI leg with no pad attached. It is a
bring-your-own-hardware tool and it is excluded in the root manifest, so CI never sees it.
The captured Elite disagrees with our blob in four ways, and the dangerous one is field ORDER:
the real pad reports sticks, ONE combined 16-bit Z trigger, then BUTTONS, then the hat, in an
UNNUMBERED 15-byte report; ours declares Report ID 1, two Simulation-page trigger axes, then the
hat, then 15 buttons. Since we claim a genuine Microsoft VID/PID and SDL/Steam/Windows all apply
stock mappings keyed on it, that ordering difference is exactly how every control silently lands
on the wrong action. The driver comment now records the diff and the two blockers that stop the
capture from simply being pasted in.
VERIFIED
* `cargo fmt --check` clean, `cargo clippy --all-targets -- -D warnings` clean (macOS).
* The tool builds and runs on macOS and on .173 (Windows 11 26200, cargo 1.96, MSVC, no WDK).
* TOOL VALIDATED AGAINST A KNOWN-GOOD CONTROL: pointed at the live DualSense on .173, it
reproduces the real `DUALSENSE_RDESC` layout exactly (input 0x01, 64 B, X,Y,Z,Rz,Rx,Ry at
bytes 1..6, hat 8.0, 15 buttons 8.4, output 0x02, the full feature ladder), and `--read`
returned live len=64 reports with sticks centred at 80 80 80 80 and the counter incrementing.
* `cargo metadata` on the root workspace still resolves and does NOT list this crate.
* The Elite capture is reproducible: `--vid 045E --pid 0B22`.
NOT VERIFIED
* That the capture equals the pad's NATIVE report map. Windows exposes no API for a device's
literal descriptor bytes, so hidapi reconstructs from `HidD_GetPreparsedData` — faithful in
structure, item order and bit offsets, not byte-exact (measured: the DualSense's real 273-byte
descriptor reconstructs to 467). `xinputhid` also filters that pad, and the captured shape is
the legacy DirectInput view. A byte-exact answer needs Linux hidraw.
* Why the Elite returned ZERO input reports across two runs (72 s and 90 s) while the DualSense
streamed fine on the same code path — untouched pad, or exclusive claim by the XInput
translator. Unresolved.
* Nothing here was built on Windows as a driver: `XBOX_RDESC` itself is UNCHANGED, so no
behaviour changes. The only edit to the driver is its provenance comment.
pf-gamepad — the virtual-gamepad UMDF2 HID minidriver
Renamed from pf-dualsense (2026-07-28). One driver has always served four identities — DualSense, DualShock 4, DualSense Edge and Steam Deck — so the old name read as if the other three lived somewhere else. Only the PACKAGE identity moved (crate, INF, CAT, DLL, UMDF service); the four hardware ids (
pf_dualsense,pf_dualshock4,pf_dualsenseedge,pf_steamdeck) are deliberately unchanged — they bind every devnode the host creates and every installed system.driver install --gamepadretires the pre-rename store package so the two can't both claim them.
A self-authored Rust UMDF2 HID minidriver that presents a virtual Sony DualSense
(VID 054C / PID 0CE6) to Windows, so games drive adaptive triggers / lightbar / rumble —
capabilities ViGEm structurally cannot deliver. It's how the punktfunk Windows host gives a client's
DualSense a near-native feel with no external gamepad dependencies (no ViGEmBus).
Shipping: the driver is one member of the in-tree driver workspace
(packaging/windows/drivers/), built from source in CI, and bundled +
pnputil-installed by the Windows host installer. The host feeds it over a shared
memory channel from crates/punktfunk-host/src/inject/windows/dualsense_windows.rs. The same UMDF driver also
serves the DualShock 4 identity per a device_type byte the host stamps.
This README captures the driver-authoring lore — the bugs and the signing recipe that make a self-signed UMDF HID driver actually load. The authoritative build/sign/package flow (CI + Inno Setup) lives in the Windows host packaging README.
Build workspace
This crate builds as a member of the packaging/windows/drivers/ workspace, which
uses the published crates.io wdk/wdk-sys/wdk-build (0.4/0.5) — not the old dev-box
windows-drivers-rs path-deps. It's a separate cargo workspace from the main tree because driver
crates are cdylibs built with the WDK toolchain on Windows only; it path-deps the shared ABI crate
crates/pf-driver-proto.
Build / sign / install recipe (the one that actually loads)
Prereqs on the Windows box: WDK 26100, LLVM (the current default; bindgen 0.72 builds on clang
22), Rust MSVC. Built as a member of the packaging/windows/drivers/ workspace (plain cargo build, no
cargo-make). A self-signed CodeSigning cert in CurrentUser\My + LocalMachine\Root +
TrustedPublisher.
Every build needs:
$env:LIBCLANG_PATH = 'C:\Program Files\LLVM\bin'
$env:Version_Number = '10.0.26100.0' # else wdk-build picks 10.0.28000.0 (no km/crt) and bindgen fails
The shipping flow is build-gamepad-drivers.ps1 (one level up): workspace cargo build --release
plus the sign steps below, staged for the installer. The original manual dev-box recipe, kept as
lore (paths reflect that era's cargo-make layout):
cargo make # -> target\debug\pf_gamepad_package\ (.inf/.cat/.dll)
# *** CRITICAL: clear the PE FORCE_INTEGRITY bit ***
# windows-drivers-rs links the DLL with /INTEGRITYCHECK, which forces a CI-trusted page-hash
# signature a self-signed cert cannot satisfy (CodeIntegrity 3004 "hash not found" /
# 3089 VerificationError 7). SudoVDA.dll (third-party VDD prior art, not used by punktfunk) has
# this bit OFF. Clear bit 0x80 at PE-header offset +0x5e:
$f = 'target\debug\pf_gamepad_package\pf_gamepad.dll'
$b = [IO.File]::ReadAllBytes($f); $pe = [BitConverter]::ToInt32($b,0x3c); $off = $pe + 0x5e
$dc = [BitConverter]::ToUInt16($b,$off); $bb = [BitConverter]::GetBytes([uint16]($dc -band 0xFF7F))
$b[$off]=$bb[0]; $b[$off+1]=$bb[1]; [IO.File]::WriteAllBytes($f,$b)
signtool sign /fd SHA256 /sha1 <cert-thumbprint> $f
Remove-Item target\debug\pf_gamepad_package\pf_gamepad.cat
Inf2Cat /driver:target\debug\pf_gamepad_package /os:10_x64
signtool sign /fd SHA256 /sha1 <cert-thumbprint> target\debug\pf_gamepad_package\pf_gamepad.cat
pnputil /add-driver target\debug\pf_gamepad_package\pf_gamepad.inf /install
devgen /add /hardwareid "root\pf_dualsense" # creates the (transient, SWD) device node
devgen (under Windows Kits\10\Tools\<ver>\x64\) is only for manual testing — the shipping
install is punktfunk-host.exe driver install --gamepad, and the host SwDeviceCreate's the device
per session (no persistent devnode). SWD devgen devices clear on reboot. TODO: drop the post-build
PE patch by stopping wdk-build emitting /INTEGRITYCHECK.
The three bugs that made it work (porting a WDK C sample to Rust)
WDF_*_CONFIG_INIT / WDF_OBJECT_ATTRIBUTES_INIT macros set non-zero defaults — mem::zeroed()
silently breaks them:
- FORCE_INTEGRITY (above) — the load wall.
- Timer
ExecutionLevel— zeroed = Invalid →WdfTimerCreate0xC0200209. SetExecutionLevel/SynchronizationScope = InheritFromParent+AutomaticSerialization = TRUE(the working vhidmini2 shape). - Queue
Settings.Parallel.NumberOfPresentedRequests— zeroed = 0 → a parallel queue presents zero requests →EvtIoDeviceControlnever fires → no HID handshake → ~5 s timeout →CM_PROB_FAILED_START. Set tou32::MAX.
Notes
- Multi-pad works via
UmdfHostProcessSharing=ProcessSharingDisabled— each pad gets its own WUDFHost (so the per-instance statics don't collide), and the driver reads its pad index from the device Location (WdfDeviceAllocAndQueryProperty) to poll its own*-boot-<index>bootstrap mailbox (the DATA section itself is unnamed — the sealed pad channel, punktfunk-planning:gamepad-channel-sealing.md— and itspad_indexis validated against this index on attach). - Port of the WDK
vhidmini2UMDF2 sample; the DualSense identity + 273-byte descriptor + feature blobs0x05/0x09/0x20come fromcrates/punktfunk-host/src/inject/proto/dualsense_proto.rs.