The RioGamepad driver now installs and enumerates, so the user-mode feeder path is verifiable. Add tools/RioJoySmokeTest, a standalone on-cabinet utility that drives the real HidFeederJoystickSink (open the device, submit reports via IOCTL_RIO_SUBMIT_REPORT) and reads the gamepad back through winmm joyGetPosEx, asserting axes (min/mid/max), buttons, and the POV hat all surface to the OS. Verified: all checks pass against the installed driver. Update docs to match reality (PLAN.md predated the HidFeederJoystickSink commit): Phase 1 is now test-signed/installed/verified with the VHF LowerFilters requirement noted; the stale "NullJoystickSink placeholder" remainders in Phases 3 and 5 are corrected to reflect the wired, verified feeder. driver/README.md notes the end-to-end verification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
140 lines
6.7 KiB
Markdown
140 lines
6.7 KiB
Markdown
# RioGamepad — virtual HID driver
|
|
|
|
The `RioGamepad` virtual HID device replaces the legacy **vJoy** dependency. It
|
|
is a KMDF driver built on the Windows **Virtual HID Framework (VHF, `vhf.sys`)**
|
|
that presents a single HID game controller to Windows matching the layout the
|
|
old app drove through vJoy:
|
|
|
|
| Report field | Count | Notes |
|
|
|--------------|-------|-------|
|
|
| Axes | 6 | X, Y, Z, Rx, Ry, Rz — 16-bit |
|
|
| Hat switch | 1 | 4-direction POV with null state |
|
|
| Buttons | 96 | 12 bytes of button bits |
|
|
|
|
The C# tray app feeds input reports to the driver through a custom
|
|
`DeviceIoControl` IOCTL on the driver's control device; the driver relays them
|
|
to Windows via `VhfReadReportSubmit`.
|
|
|
|
## Status
|
|
|
|
**Phase 1 — implemented, installed, and enumerating.** The driver source under
|
|
[`RioGamepad/`](RioGamepad/) builds cleanly to `RioGamepad.sys` against the EWDK
|
|
(KMDF 1.15 + VHF, x64). It is a thin VHF relay: it creates the virtual HID device
|
|
from the report descriptor and, on each `IOCTL_RIO_SUBMIT_REPORT`, forwards the
|
|
caller's 25-byte report to `VhfReadReportSubmit`. All report packing lives in the
|
|
C# client (`RioJoy.Core.Hid.RioHidReport`, unit-tested) so the wire format is
|
|
pinned on both sides ([`RioGamepad/Public.h`](RioGamepad/Public.h)).
|
|
|
|
Test-signed, installed, and verified on the cabinet: the devnode starts clean
|
|
(`CM_PROB_NONE`), `vhf` attaches as a lower filter, the VHF HID child enumerates,
|
|
and the controller registers under `VID_1209&PID_5249` in `joy.cpl`.
|
|
|
|
The user-mode feeder (`RioJoy.Core.Output.HidFeederJoystickSink`) is wired in and
|
|
**verified end-to-end**: driving it moves the six axes, 96 buttons, and POV hat,
|
|
read back through `winmm joyGetPosEx` / `joy.cpl` exactly as a game would see
|
|
them. The tray app selects it automatically when the driver is present and falls
|
|
back to a no-op sink when it is not.
|
|
|
|
## Building
|
|
|
|
With the **EWDK** mounted (e.g. drive `E:`), from this folder:
|
|
|
|
```cmd
|
|
RioGamepad\build.cmd E:
|
|
```
|
|
|
|
This sources the EWDK env (`<EWDK>\BuildEnv\SetupBuildEnv.cmd`) and runs MSBuild,
|
|
producing `RioGamepad\x64\Release\RioGamepad.sys`. The project is a WDK/MSBuild
|
|
`.vcxproj`; it is **not** part of `RioJoy.sln` (different toolchain).
|
|
|
|
> **EWDK note:** the build disables the managed catalog task
|
|
> (`/p:DriverCatalog_Enable=false`) and auto-signing (`/p:SignMode=Off`). On this
|
|
> EWDK image the in-build `DrvCat` task can't load `Microsoft.Kits.Logger`, so the
|
|
> `.cat` is produced separately with `inf2cat.exe` and signed with `signtool.exe`
|
|
> as part of install (below), rather than during compilation.
|
|
|
|
## Test-signing & install (cabinets you own)
|
|
|
|
Scripts in this folder automate the test-signing flow. Steps marked **(admin)**
|
|
need an elevated shell; everything else is non-admin.
|
|
|
|
```cmd
|
|
:: 1. Build the driver (non-admin), EWDK mounted at E:
|
|
RioGamepad\build.cmd E:
|
|
|
|
:: 2. Test-sign: makes a self-signed cert, builds + signs the .cat and .sys,
|
|
:: and exports RIOJoyTest.cer (non-admin)
|
|
powershell -ExecutionPolicy Bypass -File sign.ps1 -Ewdk E:
|
|
|
|
:: 3. (admin) Trust the cert, stage the driver, enable test signing
|
|
powershell -ExecutionPolicy Bypass -File install.ps1 -Ewdk E:
|
|
|
|
:: 4. Verify test signing is actually set before rebooting:
|
|
:: bcdedit /enum "{current}" -> must list testsigning Yes
|
|
:: (if missing, Code 52 will persist; enable with: bcdedit /set testsigning on)
|
|
|
|
:: 5. REBOOT (test signing only takes effect after a restart)
|
|
|
|
:: 6. (admin) Create the device; PnP then installs the staged driver
|
|
powershell -ExecutionPolicy Bypass -File install.ps1 -Ewdk E: -CreateDevice
|
|
```
|
|
|
|
Then open **`joy.cpl`** and confirm **"RIOJoy Virtual Gamepad"** appears with 6
|
|
axes / POV / 96 buttons. To roll back: `uninstall.ps1 [-DisableTestSigning] [-RemoveCert]`.
|
|
|
|
> **Friendly name in `joy.cpl`.** A VHF virtual HID device cannot supply a HID
|
|
> product string: `VHF_CONFIG` has no string field and VHF handles
|
|
> `IOCTL_HID_GET_STRING` itself rather than forwarding it to the source driver.
|
|
> Left alone, the controller shows as the generic *"Virtual HID Framework (VHF)
|
|
> HID device"*. DirectInput instead reads the display name from a registry
|
|
> `OEMName` value keyed by VID/PID, so `install.ps1 -CreateDevice` writes
|
|
> `RIOJoy Virtual Gamepad` to
|
|
> `…\MediaProperties\PrivateProperties\Joystick\OEM\VID_1209&PID_5249\OEMName`
|
|
> (under both HKLM and HKCU); `uninstall.ps1` removes it. `joy.cpl` must be
|
|
> reopened to pick up the change.
|
|
|
|
> **Secure Boot, drive encryption, and Code 52.** Test signing does not take
|
|
> effect while **Secure Boot is enabled**, and a test-signed driver shows up in
|
|
> Device Manager as **Code 52** ("Windows cannot verify the digital signature")
|
|
> until Secure Boot is turned off in the BIOS/UEFI. **Turn off drive encryption
|
|
> (BitLocker / device encryption) *before* you disable Secure Boot.** Changing
|
|
> Secure Boot alters the platform measurements BitLocker is sealed to, so if the
|
|
> drive is still encrypted the machine boots into BitLocker recovery and demands
|
|
> the recovery key. Decrypt the system drive (or at minimum suspend BitLocker)
|
|
> first, then disable Secure Boot, then reboot.
|
|
>
|
|
> If Code 52 persists *after* Secure Boot is already disabled: confirm test
|
|
> signing is actually on (`bcdedit /enum` should list `testsigning Yes`),
|
|
> re-trust the cert and re-stage with `install.ps1`, and reboot once more — the
|
|
> `testsigning` flag only takes effect after a restart.
|
|
|
|
> **Code 31 (`CM_PROB_FAILED_ADD`) — VHF lower filter.** A different failure
|
|
> from Code 52: here the image *loads* (signing is fine) but `AddDevice` fails.
|
|
> For a VHF driver this is almost always `VhfCreate` returning
|
|
> `STATUS_INVALID_DEVICE_REQUEST` (`0xC0000010`) because **`vhf.sys` is not
|
|
> attached beneath the device as a lower filter**. The INF must add it:
|
|
>
|
|
> ```ini
|
|
> [RioGamepad_Device.NT.HW]
|
|
> AddReg = RioGamepad_Device.NT.AddReg
|
|
>
|
|
> [RioGamepad_Device.NT.AddReg]
|
|
> HKR,, "LowerFilters", 0x00010000, "vhf"
|
|
> ```
|
|
>
|
|
> Confirm with `Get-PnpDeviceProperty -InstanceId <id> -KeyName DEVPKEY_Device_LowerFilters`
|
|
> (should return `vhf`). The exact failing NTSTATUS for any Code 31/52 is in the
|
|
> devnode's `DEVPKEY_Device_ProblemStatus`, and image-load rejections are logged
|
|
> under **Event Viewer → Microsoft-Windows-CodeIntegrity/Operational** and System
|
|
> event 219.
|
|
|
|
The signed package (`package/`) and the exported cert are local artifacts and are
|
|
git-ignored. Redistribution beyond owned hardware would instead use Microsoft
|
|
**attestation signing** via Partner Center (see [../docs/PLAN.md](../docs/PLAN.md),
|
|
Phase 6).
|
|
|
|
The user-mode side opens this device by `GUID_DEVINTERFACE_RIOGAMEPAD` and feeds
|
|
reports via `IOCTL_RIO_SUBMIT_REPORT` — implemented by
|
|
`RioJoy.Core.Output.HidFeederJoystickSink`, which the tray app uses automatically
|
|
when the driver is present (falling back to a no-op sink when it is not).
|