Files
riojoy/driver
CydandClaude Opus 4.8 38ffb27458 Fix RioGamepad Code 31: add VHF lower filter + joy.cpl friendly name
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>
2026-06-26 23:39:55 -05:00
..

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-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.

:: 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:

[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, 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).