Until now there was one Xbox identity, `device_type = 4` / `045E:0B13`, and Windows folded a client's `XboxOne` request onto it because the only Windows Xbox backend was the XUSB companion, which presents one fixed 360 identity and cannot vary it. The HID backend can, so the fold goes and two identities join it: devtype 4 045E:0B13 pf_xboxwireless Xbox Wireless Controller devtype 5 045E:02FD pf_xboxones Xbox Wireless Controller (One S) devtype 6 045E:0B22 pf_xboxelite Xbox Elite Wireless Controller Series 2 `GamepadPref::XboxElite` takes wire byte 11 — the first unassigned one, and the round-trip test previously asserted `from_u8(11) == Auto` with a comment saying assigning it must update that; the sentinel moved to 12. The C ABI mirror and the generated header moved with it. ⭐ ALL THREE SHARE ONE REPORT DESCRIPTOR, deliberately. In HID terms they are the same pad; the descriptor is the report shape, not the identity. §3 of the handoff records that our single hand-written descriptor already cost three separate bugs, and inventing two more would multiply that debt for no measured gain. They differ in VID/PID, product string, hardware id and Device Manager description only. ⚠️ All three install `pfGamepadXbox`, the section that attaches the `xinputhid` bus filter. That was the open risk: Microsoft's `xinputhid.inf` promotes by an explicit hardware-id allow-list containing `02D1, 02DD, 02E3, 02EA, 0B00, 0B0A, 0B13, 02FF` — and NEITHER `02FD` NOR `0B22` is on it. Measured on .173: promotion does not care, because it comes from our own AddReg rather than from matching Microsoft's ids. All three gain `IG_00`, register an XUSB interface, and are read live by classic XInput. Had this gone the other way the two new identities would have been strictly worse than the one they joined. The XUSB escape hatch needed a runtime degrade to stay honest. `pick_gamepad` is compile-time only, so with `PUNKTFUNK_XBOX_BACKEND=xusb` the host would have resolved and echoed `xboxelite` in its `Welcome` while actually building a 360 pad. `degrade_xbox_identity` folds the identity back at runtime, mirroring `degrade_if_no_uhid`. VERIFIED ON WINDOWS (.173 — none of this compiles on macOS; the driver needs the WDK and the rest is `cfg(windows)`): * `cargo test -p pf-inject --lib` 104/104 — including `hwid_matches_inf`, `hwid_devtype_table_matches_the_driver` and `only_the_xbox_identity_installs_the_xinputhid_section`, all now sweeping the whole identity set and asserting the section split in both directions. * `cargo test -p punktfunk-core --lib gamepad` 7/7; `cargo check -p punktfunk-host` clean. * Driver builds and signs; the descriptor/`wReportLength` const asserts still hold with the descriptor shared three ways. * ON GLASS, per identity, via the new `--xboxones` / `--xboxelite` devtest legs: each gets its own devnode (`PF_XBOX_0` / `PF_XBOX_ONES_0` / `PF_XBOX_ELITE_0`), each HID child gains `IG_00`, each registers an XUSB interface, and XInput reads each live (packets advancing, `buttons=0x1000`). * macOS: `cargo fmt --all --check` clean in both workspaces. NOT VERIFIED / NOT DONE * **Elite paddles are NOT implemented.** `BTN_PADDLE1..4` would need descriptor buttons, and once `xinputhid` promotes the pad it claims the HID collection exclusively — XInput has no paddle fields and the HID consumers that do may be locked out, so the buttons would likely reach nobody. The decisive measurement is cheap and named in the code: hold a paddle bit set and see whether a user-mode HID reader still gets reports. Until then the Edge remains the only virtual pad with native back-button slots and nothing should be advertised otherwise. * **No client picker offers the Elite**, and none can auto-detect it — SDL3's `GamepadType` has no Elite variant. It is reachable today only via `PUNKTFUNK_GAMEPAD=xboxelite` or a hand-edited client setting. All five clients ship the same curated six options by deliberate parity, so adding one is a cross-client UX change, not part of this. * Nothing here has run in a real streaming session; every measurement came from the devtest.
197 lines
9.0 KiB
Plaintext
197 lines
9.0 KiB
Plaintext
;/*++
|
|
; punktfunk virtual gamepads — UMDF2 HID minidriver INF.
|
|
; One package, seven hardware ids: DualSense, DualShock 4, DualSense Edge, Steam Deck, and three
|
|
; Xbox pads (Wireless / One S / Elite Series 2) — which is why the package is called pf_gamepad and
|
|
; not pf_dualsense (it never was one identity).
|
|
;
|
|
; ⚠️ The HARDWARE IDS below deliberately keep their old names (`pf_dualsense`, `pf_dualshock4`,
|
|
; `pf_dualsenseedge`, `pf_steamdeck`). They are the binding contract with every devnode the host
|
|
; SwDeviceCreate's and with every already-installed system; renaming them would orphan existing
|
|
; installs and buy nothing. Only the PACKAGE identity (INF/CAT/DLL/service) moved to pf_gamepad.
|
|
; Adapted from the WDK vhidmini2 UMDF2 sample (VhidminiUm.inx).
|
|
; Depends on MsHidUmdf.inf (build >= 22000).
|
|
; Install: devgen /add /hardwareid "root\pf_dualsense" (after pnputil /add-driver /install)
|
|
;--*/
|
|
[Version]
|
|
Signature="$WINDOWS NT$"
|
|
Class=HIDClass
|
|
ClassGuid={745a17a0-74d3-11d0-b6fe-00a0c90f57da}
|
|
Provider=%ProviderString%
|
|
CatalogFile=pf_gamepad.cat
|
|
PnpLockdown=1
|
|
|
|
[DestinationDirs]
|
|
DefaultDestDir = 13
|
|
|
|
[SourceDisksNames]
|
|
1=%Disk_Description%,,,
|
|
|
|
[SourceDisksFiles]
|
|
pf_gamepad.dll=1
|
|
|
|
[Manufacturer]
|
|
%ManufacturerString%=pf, NT$ARCH$.10.0...22000
|
|
|
|
[pf.NT$ARCH$.10.0...22000]
|
|
; Hardware ids: `root\pf_dualsense` for a root-enumerated devnode (devgen/devcon tests); `pf_dualsense`
|
|
; for the host's SwDeviceCreate'd DualSense (the `root\` prefix is reserved for root enumeration, so
|
|
; SwDeviceCreate rejects it with E_INVALIDARG); `pf_dualshock4` / `pf_dualsenseedge` /
|
|
; `pf_steamdeck` / `pf_xboxwireless` / `pf_xboxones` / `pf_xboxelite` for the host's other virtual
|
|
; pads — ONE driver binds all of them and serves the matching HID identity per the device_type byte
|
|
; the host stamps into shared memory. TWO install sections, though: the PlayStation/Deck ids share
|
|
; `pfGamepad`, and the three Xbox ids install `pfGamepadXbox`, which additionally attaches the
|
|
; `xinputhid` bus filter (see the ⚠️ below the Deck line).
|
|
;
|
|
; Each id carries its OWN description: Device Manager reads this string, and a single shared
|
|
; "Virtual DualSense" made an emulated DualShock 4 look like the controller-type setting had been
|
|
; ignored. The HID layer (VID/PID, report descriptor, product string) was always per-type; this
|
|
; makes the human-readable name agree with it.
|
|
%DeviceDesc%=pfGamepad, root\pf_dualsense, pf_dualsense
|
|
%DeviceDescDS4%=pfGamepad, pf_dualshock4
|
|
%DeviceDescEdge%=pfGamepad, pf_dualsenseedge
|
|
%DeviceDescDeck%=pfGamepad, pf_steamdeck
|
|
; ⚠️ The Xbox lines install their OWN section, `pfGamepadXbox`, and must keep doing so. Every other
|
|
; identity shares `pfGamepad`; the Xbox ones additionally attach the `xinputhid` bus filter, and
|
|
; putting that on a DualSense / DualShock 4 / Edge / Steam Deck would hand a PlayStation pad to
|
|
; Microsoft's Xbox translator. The two sections are otherwise identical — keep them in step.
|
|
; (`only_the_xbox_identity_installs_the_xinputhid_section`, in pf-inject, asserts both directions.)
|
|
;
|
|
; The three Xbox identities differ ONLY in hardware id, Device Manager description and the VID/PID
|
|
; + product string the driver serves off the resulting device_type — they share one report
|
|
; descriptor and one install section, because in HID terms they are the same pad. See the
|
|
; `XBOX_RDESC` header in src/lib.rs for why that sharing is deliberate.
|
|
%DeviceDescXbox%=pfGamepadXbox, root\pf_xboxwireless, pf_xboxwireless
|
|
%DeviceDescXboxOneS%=pfGamepadXbox, root\pf_xboxones, pf_xboxones
|
|
%DeviceDescXboxElite%=pfGamepadXbox, root\pf_xboxelite, pf_xboxelite
|
|
|
|
[pfGamepad.NT]
|
|
CopyFiles=UMDriverCopy
|
|
Include=MsHidUmdf.inf
|
|
Needs=MsHidUmdf.NT
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT
|
|
|
|
[pfGamepad.NT.hw]
|
|
Include=MsHidUmdf.inf
|
|
Needs=MsHidUmdf.NT.hw
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT.hw
|
|
|
|
[pfGamepad.NT.Services]
|
|
Include=MsHidUmdf.inf
|
|
Needs=MsHidUmdf.NT.Services
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT.Services
|
|
|
|
[pfGamepad.NT.Filters]
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT.Filters
|
|
|
|
[pfGamepad.NT.Wdf]
|
|
UmdfService="pf_gamepad", pf_gamepad_Install
|
|
UmdfServiceOrder=pf_gamepad
|
|
UmdfKernelModeClientPolicy=AllowKernelModeClients
|
|
UmdfFileObjectPolicy=AllowNullAndUnknownFileObjects
|
|
UmdfMethodNeitherAction=Copy
|
|
UmdfFsContextUsePolicy=CanUseFsContext2
|
|
; Each pad gets its OWN WUDFHost so the driver's per-pad statics (incl. the shm index) don't collide
|
|
; across multiple simultaneous controllers (multi-pad).
|
|
UmdfHostProcessSharing=ProcessSharingDisabled
|
|
|
|
; ---------------------------------------------------------------------------------------------
|
|
; The Xbox identity: `pfGamepad` plus the two registry values that make Windows PROMOTE the pad.
|
|
;
|
|
; Measured on .173, 2026-08-09. Without these, our HID Xbox pad is invisible to classic XInput and
|
|
; to WGI `Gamepad`, and gets no rumble — the exact field symptom that started this work. With them
|
|
; the HID child gains the `IG_00` token, an XUSB interface appears, XInput reads it (full stick
|
|
; range and buttons) and `XInputSetState` rumble arrives back as HID output report 0x03.
|
|
;
|
|
; ⭐ Both values come straight out of Microsoft's own `xinputhid.inf`, which promotes Xbox pads by
|
|
; an explicit hardware-id ALLOW-LIST (its own comment: "we can not use a Compatability ID … and so
|
|
; rely on individual hardware IDs"). A software-enumerated devnode can never match those ids, so we
|
|
; write what the matching install sections would have written. `045E:0B13`, the PID this identity
|
|
; claims, is on that allow-list — twice.
|
|
;
|
|
; 🛑 THE PAIRING IS LOAD-BEARING AND THE TWO VALUES GO IN DIFFERENT KEYS. An A/B on the live box:
|
|
; removing `DevicePropertyFlags` alone reverts ALL of it — no `IG_00`, no XUSB interface, no XInput,
|
|
; no WGI entry — while `UpperFilters` alone is completely inert. `DevicePropertyFlags = 1` is
|
|
; `BusDevice` in `xinputhid.h`, which Microsoft's comment glosses as "a focused bus filter driver
|
|
; for the IG_ problem". It is not a description of the device; it is the switch that tells the
|
|
; filter what job to do. An earlier session installed the filter WITHOUT it, measured a device that
|
|
; produced nothing, and concluded the filter was broken and must never ship. It was not broken; it
|
|
; had never been switched on.
|
|
;
|
|
; ⚠️ Both go on THIS node — the parent/transport devnode — not on the HID child. That is where a
|
|
; real Xbox pad carries them: the Elite's Bluetooth transport node has `DevicePropertyFlags=1` and
|
|
; the filter, while its HID child has plain `input.inf` and neither.
|
|
[pfGamepadXbox.NT]
|
|
CopyFiles=UMDriverCopy
|
|
Include=MsHidUmdf.inf
|
|
Needs=MsHidUmdf.NT
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT
|
|
; HKR in a DDInstall section is the SOFTWARE (driver) key — Control\Class\{...}\<NNNN>.
|
|
AddReg=pfGamepadXbox_SW_AddReg
|
|
|
|
[pfGamepadXbox.NT.hw]
|
|
Include=MsHidUmdf.inf
|
|
Needs=MsHidUmdf.NT.hw
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT.hw
|
|
; HKR in a .HW section is the HARDWARE (device) key — Enum\<instance>.
|
|
AddReg=pfGamepadXbox_HW_AddReg
|
|
|
|
[pfGamepadXbox.NT.Services]
|
|
Include=MsHidUmdf.inf
|
|
Needs=MsHidUmdf.NT.Services
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT.Services
|
|
|
|
[pfGamepadXbox.NT.Filters]
|
|
Include=WUDFRD.inf
|
|
Needs=WUDFRD_LowerFilter.NT.Filters
|
|
|
|
[pfGamepadXbox.NT.Wdf]
|
|
UmdfService="pf_gamepad", pf_gamepad_Install
|
|
UmdfServiceOrder=pf_gamepad
|
|
UmdfKernelModeClientPolicy=AllowKernelModeClients
|
|
UmdfFileObjectPolicy=AllowNullAndUnknownFileObjects
|
|
UmdfMethodNeitherAction=Copy
|
|
UmdfFsContextUsePolicy=CanUseFsContext2
|
|
UmdfHostProcessSharing=ProcessSharingDisabled
|
|
|
|
[pfGamepadXbox_SW_AddReg]
|
|
; 1 = BusDevice. See the block above — this is the half that actually does the work.
|
|
HKR,,"DevicePropertyFlags",0x00010001,1
|
|
|
|
[pfGamepadXbox_HW_AddReg]
|
|
; 0x00010008 = REG_MULTI_SZ | APPEND, matching xinputhid.inf: append rather than replace, so we
|
|
; never clobber a filter someone else put on the stack.
|
|
HKR,,"UpperFilters",0x00010008,"xinputhid"
|
|
|
|
[pf_gamepad_Install]
|
|
UmdfLibraryVersion=$UMDFVERSION$
|
|
ServiceBinary="%13%\pf_gamepad.dll"
|
|
|
|
[UMDriverCopy]
|
|
pf_gamepad.dll
|
|
|
|
[Strings]
|
|
ProviderString ="Punktfunk"
|
|
ManufacturerString ="Punktfunk"
|
|
ClassName ="HID device"
|
|
Disk_Description ="Punktfunk Gamepad Installation Disk"
|
|
; One per hardware id — these are what Device Manager shows. Keep them aligned with the product
|
|
; strings the driver serves per device_type (src/lib.rs `on_get_string`).
|
|
DeviceDesc ="Punktfunk Virtual DualSense"
|
|
DeviceDescDS4 ="Punktfunk Virtual DualShock 4"
|
|
DeviceDescEdge ="Punktfunk Virtual DualSense Edge"
|
|
DeviceDescDeck ="Punktfunk Virtual Steam Deck Controller"
|
|
DeviceDescXbox ="Punktfunk Virtual Xbox Wireless Controller"
|
|
; ⚠️ This one deliberately does NOT match the product string the driver serves for device_type 5.
|
|
; A real Xbox One S pad reports "Xbox Wireless Controller" over Bluetooth, exactly like the Series
|
|
; X|S pad above — the PID is the only thing that separates them on the wire. Device Manager,
|
|
; however, has to let a human tell our two virtual pads apart, and this string is ours to choose.
|
|
DeviceDescXboxOneS ="Punktfunk Virtual Xbox One S Controller"
|
|
DeviceDescXboxElite="Punktfunk Virtual Xbox Elite Wireless Controller Series 2"
|