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>
155 lines
4.3 KiB
C
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);
|
|
}
|