Files
CydandClaude Opus 4.8 2deeb374ae Verify HID feeder end-to-end; add smoke-test tool; update docs
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>
2026-06-26 23:56:16 -05:00

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

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:

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