The driver installed but the device failed with Code 31 (CM_PROB_FAILED_ADD): VhfCreate returned STATUS_INVALID_DEVICE_REQUEST (0xC0000010) because vhf.sys was never attached beneath the FDO. Add the required LowerFilters=vhf AddReg to the INF so VHF loads as a lower filter; the device now starts clean and enumerates in joy.cpl. A VHF virtual HID device cannot supply a HID product string (VHF_CONFIG has no string field and VHF owns IOCTL_HID_GET_STRING), so register the DirectInput OEMName (VID_1209&PID_5249 -> RIOJoy Virtual Gamepad) in install.ps1 -CreateDevice and remove it in uninstall.ps1, giving the controller a proper name instead of the generic VHF default. Also fix a non-ASCII em-dash in sign.ps1 that broke parsing under Windows PowerShell 5.1 (UTF-8 without BOM), and document the Code 52 / Code 31 / friendly-name troubleshooting in driver/README.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.5 KiB
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/ 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).
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.
Not yet done: wiring the real DeviceIoControl feeder sink (replacing the C#
side's NullJoystickSink).
Building
With the EWDK mounted (e.g. drive E:), from this folder:
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-buildDrvCattask can't loadMicrosoft.Kits.Logger, so the.catis produced separately withinf2cat.exeand signed withsigntool.exeas 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.
:: 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_CONFIGhas no string field and VHF handlesIOCTL_HID_GET_STRINGitself 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 registryOEMNamevalue keyed by VID/PID, soinstall.ps1 -CreateDevicewritesRIOJoy Virtual Gamepadto…\MediaProperties\PrivateProperties\Joystick\OEM\VID_1209&PID_5249\OEMName(under both HKLM and HKCU);uninstall.ps1removes it.joy.cplmust 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 /enumshould listtestsigning Yes), re-trust the cert and re-stage withinstall.ps1, and reboot once more — thetestsigningflag 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) butAddDevicefails. For a VHF driver this is almost alwaysVhfCreatereturningSTATUS_INVALID_DEVICE_REQUEST(0xC0000010) becausevhf.sysis not attached beneath the device as a lower filter. The INF must add it:[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 returnvhf). The exact failing NTSTATUS for any Code 31/52 is in the devnode'sDEVPKEY_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,
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).