Files
CydandClaude Opus 4.8 24cdf495e3 Phase 1: RioGamepad virtual HID driver (KMDF + VHF) + C# report packer
Author the custom virtual HID gamepad that replaces vJoy, and pin its wire
format on both sides. Builds clean to RioGamepad.sys against the EWDK
(KMDF 1.15 + VHF, x64, warnings-as-errors).

driver/RioGamepad/:
- ReportDescriptor.h: 6x16-bit axes (X,Y,Z,Rx,Ry,Rz), one 4-direction hat with
  null state, and 96 buttons — the legacy vJoy layout. 25-byte input report.
- Device.c/Driver.c: KMDF root-enumerated device that creates the VHF virtual HID
  device (VhfCreate in DeviceAdd, VhfStart in D0Entry, VhfDelete on cleanup) and
  exposes a device interface + IOCTL_RIO_SUBMIT_REPORT that forwards the caller's
  report bytes to VhfReadReportSubmit. Thin relay: no report logic in the kernel.
- Public.h: device-interface GUID, IOCTL, and the report byte layout shared with
  the C# client. RioGamepad.inf + build.cmd (EWDK build, catalog/sign disabled).

src/RioJoy.Core/Hid/RioHidReport.cs: packs AxisOutputs + hat + 96 buttons into
the exact 25-byte report (LE axes, hat nibble with 0x0F=centered, button bitmap).
13 new xUnit tests (136 total).

Remaining (deploy-side): test-sign + pnputil install + verify in joy.cpl, and
wire the real DeviceIoControl feeder sink (replacing NullJoystickSink). The
EWDK's in-build catalog task (DrvCat) can't load Microsoft.Kits.Logger on this
image, so the .cat is produced with inf2cat/signtool at install time instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:06:16 -05:00

155 lines
4.3 KiB
C

/*++
RioGamepad — device creation, VHF setup, and the report-submit IOCTL.
The driver is a thin relay: it creates a virtual HID device via VHF using the
report descriptor in ReportDescriptor.h, exposes a device interface, and on each
IOCTL_RIO_SUBMIT_REPORT forwards the caller's report bytes to the HID stack with
VhfReadReportSubmit. All report packing lives in the user-mode client.
--*/
#include <ntddk.h>
#include <initguid.h> // makes DEFINE_GUID in Public.h allocate the GUID storage
#include "Driver.h"
#include "ReportDescriptor.h"
NTSTATUS
RioGamepadEvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
WDFDEVICE device;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
WDF_IO_QUEUE_CONFIG queueConfig;
PDEVICE_CONTEXT deviceContext;
VHF_CONFIG vhfConfig;
UNREFERENCED_PARAMETER(Driver);
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
pnpPowerCallbacks.EvtDeviceD0Entry = RioGamepadEvtDeviceD0Entry;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
deviceAttributes.EvtCleanupCallback = RioGamepadEvtDeviceCleanup;
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (!NT_SUCCESS(status))
{
return status;
}
deviceContext = DeviceGetContext(device);
deviceContext->VhfHandle = NULL;
status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_RIOGAMEPAD, NULL);
if (!NT_SUCCESS(status))
{
return status;
}
//
// Default parallel queue for the report-submit IOCTL.
//
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
queueConfig.EvtIoDeviceControl = RioGamepadEvtIoDeviceControl;
status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, NULL);
if (!NT_SUCCESS(status))
{
return status;
}
//
// Create the virtual HID device. VhfStart is deferred to D0Entry.
//
VHF_CONFIG_INIT(
&vhfConfig,
WdfDeviceWdmGetDeviceObject(device),
(USHORT)sizeof(g_RioReportDescriptor),
(PUCHAR)g_RioReportDescriptor);
vhfConfig.VendorID = RIO_VENDOR_ID;
vhfConfig.ProductID = RIO_PRODUCT_ID;
vhfConfig.VersionNumber = RIO_VERSION;
status = VhfCreate(&vhfConfig, &deviceContext->VhfHandle);
if (!NT_SUCCESS(status))
{
deviceContext->VhfHandle = NULL;
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS
RioGamepadEvtDeviceD0Entry(
_In_ WDFDEVICE Device,
_In_ WDF_POWER_DEVICE_STATE PreviousState
)
{
PDEVICE_CONTEXT deviceContext = DeviceGetContext(Device);
UNREFERENCED_PARAMETER(PreviousState);
return VhfStart(deviceContext->VhfHandle);
}
VOID
RioGamepadEvtDeviceCleanup(
_In_ WDFOBJECT Object
)
{
PDEVICE_CONTEXT deviceContext = DeviceGetContext((WDFDEVICE)Object);
if (deviceContext->VhfHandle != NULL)
{
VhfDelete(deviceContext->VhfHandle, TRUE);
deviceContext->VhfHandle = NULL;
}
}
VOID
RioGamepadEvtIoDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
PDEVICE_CONTEXT deviceContext = DeviceGetContext(WdfIoQueueGetDevice(Queue));
UNREFERENCED_PARAMETER(OutputBufferLength);
if (IoControlCode == IOCTL_RIO_SUBMIT_REPORT)
{
if (InputBufferLength != RIO_REPORT_SIZE)
{
status = STATUS_INVALID_BUFFER_SIZE;
}
else
{
PVOID buffer;
size_t bufferSize;
status = WdfRequestRetrieveInputBuffer(Request, RIO_REPORT_SIZE, &buffer, &bufferSize);
if (NT_SUCCESS(status))
{
HID_XFER_PACKET packet;
packet.reportId = 0;
packet.reportBuffer = (PUCHAR)buffer;
packet.reportBufferLen = RIO_REPORT_SIZE;
status = VhfReadReportSubmit(deviceContext->VhfHandle, &packet);
}
}
}
WdfRequestComplete(Request, status);
}