Phase 8B: RioGamepadXP.sys — WDM HID minidriver for Windows XP

The XP flavor of our own virtual joystick (no third-party driver), built
with WDK 7.1.0 to x86/subsystem-5.01. Exposes the identical Public.h
contract as the modern KMDF+VHF driver — same GUID, IOCTL_RIO_SUBMIT_REPORT,
25-byte report, VID/PID — so HidFeederJoystickSink drives both.

Design (driver/RioGamepadXP/rioxp.c):
- WDM HID minidriver via HidRegisterMinidriver presenting the 6-axis/hat/
  96-button joystick. POLLED mode (DevicesArePolled=TRUE): hidclass paces
  IOCTL_HID_READ_REPORT and we complete each synchronously from a cached
  report — no pending-IRP or cancel-routine machinery (the usual crash
  surface in a virtual HID driver).
- Named sideband control device (\Device\RioGamepadXP + \DosDevices symlink)
  takes IOCTL_RIO_SUBMIT_REPORT and updates the cache under a spinlock.
- hidclass overwrites our CREATE/CLOSE/DEVICE_CONTROL during registration;
  we save its pointers and reinstall wrappers that route the control device's
  IRPs to us and forward the HID FDO's to hidclass.

Packaging/install:
- Root-enumerated, so install uses devcon (built from the same WDK) —
  InstallHinfSection can't create the devnode. install-core.bat runs
  "devcon install RioGamepadXP.inf root\RioGamepadXP"; unsigned is fine
  (XP x86 enforces no kernel signing). Bundled into vendor\xp\.
- net40 feeder opens the driver by name (\.\RioGamepadXP); XP can't put a
  device interface on a bare control device (no PDO) — the one nuance.

Static build only: compiles clean but runtime bring-up (joy.cpl enumeration,
report flow) needs a real XP target — that's 8E. 275 tests still green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-17 14:01:16 -05:00
co-authored by Claude Fable 5
parent 2ecb617c09
commit 52712f8409
12 changed files with 613 additions and 25 deletions
+7
View File
@@ -372,6 +372,13 @@ FodyWeavers.xsd
# WDK / driver build outputs
[Dd]river/**/[Xx]64/
[Dd]river/package/
# WDK build.exe output trees + logs (RioGamepadXP / any ddkbuild sample)
[Dd]river/**/objfre_*/
[Dd]river/**/objchk_*/
[Dd]river/**/buildfre_*.log
[Dd]river/**/buildchk_*.log
[Dd]river/**/build.err
[Dd]river/**/build.wrn
*.cer
*.cat
*.pvk
+2 -1
View File
@@ -102,7 +102,8 @@ try {
@{ Name = 'dotNetFx40_Full_x86_x64.exe'; What = '.NET Framework 4.0 offline installer' },
@{ Name = 'NDP40-KB2468871-v2-x86.exe'; What = '.NET 4.0 update KB2468871 (required by Bcl.Async)' },
@{ Name = 'RioGamepadXP.inf'; What = 'RioGamepadXP driver INF (Phase 8B)' },
@{ Name = 'RioGamepadXP.sys'; What = 'RioGamepadXP driver binary (Phase 8B)' }
@{ Name = 'RioGamepadXP.sys'; What = 'RioGamepadXP driver binary (Phase 8B)' },
@{ Name = 'devcon.exe'; What = 'devcon (creates the root-enumerated driver devnode on XP)' }
)
foreach ($item in $xpWanted) {
$src = Join-Path $vendorXpSrc $item.Name
+11 -4
View File
@@ -55,12 +55,19 @@ if exist "%PKGROOT%vendor\xp\NDP40-KB2468871-v2-x86.exe" (
echo will not run without KB2468871. Install it before first use.
)
rem RioGamepadXP virtual-joystick driver (bundled from Phase 8B onward).
rem RioGamepadXP virtual-joystick driver. Root-enumerated, so it must be
rem created with devcon (rundll32/InstallHinfSection would not make the
rem devnode). devcon is idempotent: re-running "install" updates in place.
if exist "%PKGROOT%vendor\xp\RioGamepadXP.inf" (
echo Installing the RioGamepadXP virtual joystick driver...
rundll32 setupapi.dll,InstallHinfSection DefaultInstall 132 %PKGROOT%vendor\xp\RioGamepadXP.inf
if exist "%PKGROOT%vendor\xp\devcon.exe" (
echo Installing the RioGamepadXP virtual joystick driver...
"%PKGROOT%vendor\xp\devcon.exe" install "%PKGROOT%vendor\xp\RioGamepadXP.inf" root\RioGamepadXP
if errorlevel 1 echo WARNING: driver install returned an error - check Device Manager.
) else (
echo WARNING: vendor\xp\devcon.exe missing - cannot create the driver devnode.
)
) else (
echo Note: RioGamepadXP driver not bundled yet - joystick output is
echo Note: RioGamepadXP driver not bundled - joystick output is
echo unavailable on XP; keyboard/mouse, lamps and plasma still work.
)
+24 -20
View File
@@ -322,26 +322,30 @@ XP consumes pre-rendered wallpapers.
(`ViGEmJoystickSink`, `HidFeederJoystickSink`, `Hid/`).
*Risk fallback:* if Bcl.Async misbehaves on real XP, the receive loop
reverts to a dedicated thread (the legacy `CommWatchProc` shape) for net40.
- **8B — RioGamepadXP.sys, the XP flavor of our own driver.** Third-party
virtual-joystick drivers are ruled out (decided: **no vJoy** — the project
is unmaintained; PPJoy likewise). Instead, rebuild the thin driver side of
our existing split for XP: a **WDM HID minidriver** in the shape of the
DDK `vhidmini` sample (`HidRegisterMinidriver`, x86), exposing the **same
`Public.h` contract** as the modern driver — identical
`IOCTL_RIO_SUBMIT_REPORT`, identical 25-byte report, same descriptor
(6×16-bit axes, hat, 96 buttons) — so the existing `HidFeederJoystickSink`
drives it unchanged (only the device path differs). Precedent: the
original FASA `tasgame.sys` was exactly an XP HID minidriver
(docs/Win32RIO/, analyzed); ours stays thin with serial in user mode.
Toolchain: **WDK 7.1.0** (last XP-capable kit) under `driver/RioGamepadXP/`;
XP x86 enforces no kernel signing, so install is just the INF — none of
the Phase 1 test-signing/Secure Boot friction exists there.
Acquisition order (decided): **the Xbox 360 pad (ViGEm) stays preferred
on 10/11** — net48: ViGEm → RioGamepad → Null (unchanged);
net40: RioGamepadXP → Null.
*Staging:* the XP app is useful before the driver lands — milestone 1
ships keyboard/mouse + lamps + plasma (joystick = Null sink), the driver
follows as milestone 2.
- **8B — RioGamepadXP.sys — built ✅ (static; XP bring-up pending in 8E).**
Source in [`driver/RioGamepadXP/`](../driver/RioGamepadXP/): a WDM HID
minidriver (`HidRegisterMinidriver`, **polled mode** so there's no pending-
IRP/cancel machinery) presenting the 6-axis/hat/96-button joystick, plus a
named sideband control device (`\\.\RioGamepadXP`) that takes
`IOCTL_RIO_SUBMIT_REPORT` — the **identical `Public.h` contract** as the
modern driver (same 25-byte report, VID/PID). A dispatch wrapper routes the
control device's CREATE/CLOSE/DEVICE_CONTROL to us and forwards the HID FDO's
to hidclass. Builds with WDK 7.1.0 (`build.cmd``setenv … fre x86 WXP
no_oacr`) to `RioGamepadXP.sys` (x86, subsystem 5.01). XP enforces no kernel
signing, so install is unsigned; the device is root-enumerated so it needs
**devcon** (`devcon install RioGamepadXP.inf root\RioGamepadXP`, also built
from the WDK) rather than InstallHinfSection. The net40 feeder opens the
driver by name (`#if NET40` branch in `HidFeederJoystickSink`); XP can't
register a device interface on a bare control device (no PDO), the one
contract nuance. Bundled into the package's `vendor\xp\`. Precedent: the
original FASA `tasgame.sys` was itself an XP HID minidriver (docs/Win32RIO/,
analyzed); ours stays thin with serial in user mode. No third-party virtual
joystick (vJoy/PPJoy unmaintained). Acquisition order: net48 ViGEm →
RioGamepad → Null (unchanged); net40 RioGamepadXP → Null. ⏳ **8E:** the
driver compiles clean but is **unverified at runtime** — needs an XP target
to confirm joy.cpl enumeration + report flow. *Staging:* the app is useful
before the driver — milestone 1 ships keyboard/mouse + lamps + plasma
(joystick = Null sink) if the driver isn't present.
- **8C — Tray on net40/x86 — done ✅:** multi-target `RioJoy.Tray` (net40 drops the
ViGEm + RioJoy.Overlay references); gate `WallpaperMakerForm` + overlay
generation; `WallpaperApplier` converts PNG→BMP via GDI+ before
+56
View File
@@ -0,0 +1,56 @@
/*++
RioGamepadXP — shared definitions for the Windows XP virtual HID gamepad driver
and its user-mode client (the RIOJoy net40 tray app's HID feeder).
This is the XP-flavor sibling of driver/RioGamepad/Public.h. The contract is
IDENTICAL — same interface GUID, same IOCTL_RIO_SUBMIT_REPORT, same 25-byte
report layout, same VID/PID — so RioJoy.Core's HidFeederJoystickSink drives
either driver with only the device-open path differing.
The one XP difference: XP-era hidclass minidrivers cannot register a device
interface on a bare control device (IoRegisterDeviceInterface needs a PDO), so
the sideband control device is reachable by a fixed symbolic-link name instead
of by interface GUID. The net40 feeder opens RIO_XP_USERMODE_PATH directly; the
net48 feeder keeps using SetupDi + GUID_DEVINTERFACE_RIOGAMEPAD.
The input report is fixed-size and has no report ID:
bytes 0..11 : 6 axes (X,Y,Z,Rx,Ry,Rz), each unsigned 16-bit little-endian
byte 12 : low nibble = hat (0..3; 0x0F = centered/null), high nibble pad
bytes 13..24 : 96 button bits (button N at byte 13 + (N-1)/8, bit (N-1)%8)
--*/
#pragma once
//
// Device interface GUID (same as the modern driver; kept for parity, and used
// if a future XP build ever exposes an interface). {b6a3f1c2-...}
//
DEFINE_GUID(GUID_DEVINTERFACE_RIOGAMEPAD,
0xb6a3f1c2, 0x7e84, 0x4d2a, 0x9c, 0x1f, 0x2a, 0x5e, 0x8d, 0x3b, 0x60, 0x71);
//
// Custom IOCTL: submit one input report (input buffer = RIO_REPORT_SIZE bytes).
// Identical code to the modern driver.
//
#define IOCTL_RIO_SUBMIT_REPORT \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_WRITE_ACCESS)
//
// HID input report size in bytes: 6*2 (axes) + 1 (hat+pad) + 12 (96 buttons).
//
#define RIO_REPORT_SIZE 25
//
// Virtual device identity (matches the modern driver).
//
#define RIO_VENDOR_ID 0x1209 // pid.codes test/community VID
#define RIO_PRODUCT_ID 0x5249 // 'R','I'
#define RIO_VERSION 0x0100
//
// Sideband control-device names. The kernel object and its DOS-device symlink;
// the user-mode client opens RIO_XP_USERMODE_PATH.
//
#define RIO_XP_DEVICE_NAME L"\\Device\\RioGamepadXP"
#define RIO_XP_SYMLINK_NAME L"\\DosDevices\\RioGamepadXP"
#define RIO_XP_USERMODE_PATH "\\\\.\\RioGamepadXP"
+56
View File
@@ -0,0 +1,56 @@
/*++
RioGamepad — HID report descriptor: a joystick with 6 16-bit axes (X,Y,Z,Rx,
Ry,Rz), one 4-direction hat with null state, and 96 buttons. This mirrors the
fidelity the legacy app drove through vJoy. The resulting input report is
RIO_REPORT_SIZE (25) bytes; see Public.h for the byte layout.
--*/
#pragma once
static const UCHAR g_RioReportDescriptor[] =
{
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x09, 0x35, // Usage (Rz)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x75, 0x10, // Report Size (16)
0x95, 0x06, // Report Count (6)
0x81, 0x02, // Input (Data,Var,Abs)
0xC0, // End Collection (Physical)
0x09, 0x39, // Usage (Hat switch)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x03, // Logical Maximum (3)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x0E, 0x01, // Physical Maximum (270)
0x65, 0x14, // Unit (Degrees)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x42, // Input (Data,Var,Abs,Null)
0x65, 0x00, // Unit (None)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x03, // Input (Const,Var,Abs) ; 4-bit padding
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x60, // Usage Maximum (Button 96)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x60, // Report Count (96)
0x81, 0x02, // Input (Data,Var,Abs)
0xC0 // End Collection (Application)
};
+53
View File
@@ -0,0 +1,53 @@
;
; RioGamepadXP.inf — RIOJoy virtual HID gamepad for Windows XP (WDM HID
; minidriver, root-enumerated). No catalog / signing: 32-bit XP does not
; enforce kernel-mode driver signing, so this installs unsigned.
;
; Install (see deploy\install-core.bat):
; devcon.exe install RioGamepadXP.inf root\RioGamepadXP
;
[Version]
Signature = "$WINDOWS NT$"
Class = HIDClass
ClassGuid = {745a17a0-74d3-11d0-b6fe-00a0c90f57da}
Provider = %ManufacturerName%
DriverVer = 07/11/2026,1.0.0.0
[DestinationDirs]
DefaultDestDir = 12 ; %SystemRoot%\System32\drivers
RioGamepadXP_CopyFiles = 12
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
RioGamepadXP.sys = 1
[Manufacturer]
%ManufacturerName% = Standard, NTx86
[Standard.NTx86]
%DeviceName% = RioGamepadXP_Device, root\RioGamepadXP
[RioGamepadXP_Device.NT]
CopyFiles = RioGamepadXP_CopyFiles
[RioGamepadXP_CopyFiles]
RioGamepadXP.sys
[RioGamepadXP_Device.NT.Services]
AddService = RioGamepadXP, 0x00000002, RioGamepadXP_Service
[RioGamepadXP_Service]
DisplayName = %ServiceName%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\RioGamepadXP.sys
[Strings]
ManufacturerName = "VWE"
DiskName = "RioGamepadXP Installation Disk"
DeviceName = "RIOJoy Virtual Gamepad (XP)"
ServiceName = "RIOJoy Virtual Gamepad Service (XP)"
+7
View File
@@ -0,0 +1,7 @@
@echo off
rem Build RioGamepadXP.sys for Windows XP (x86, free). Requires WDK 7.1.0.
rem Override WDK if installed elsewhere: set WDK=<path> before calling.
if "%WDK%"=="" set WDK=C:\WinDDK\WinDDK\7600.16385.win7_wdk.100208-1538
call %WDK%\bin\setenv.bat %WDK% fre x86 WXP no_oacr
cd /d %~dp0
build -cZ
+1
View File
@@ -0,0 +1 @@
!INCLUDE $(NTMAKEENV)\makefile.def
+381
View File
@@ -0,0 +1,381 @@
/*++
RioGamepadXP — a WDM HID minidriver for Windows XP that presents a virtual
joystick (6 axes, hat, 96 buttons) and accepts input reports from the RIOJoy
tray app over a sideband control device. The XP-era replacement for the modern
KMDF+VHF RioGamepad driver, exposing the same 25-byte report contract
(see Public.h) so the user-mode feeder is unchanged apart from the open path.
Architecture
------------
There is exactly one virtual device, so state is global (guarded by a spinlock):
* HID side: we register as a HID minidriver (HidRegisterMinidriver). hidclass
creates and owns the HID FDO and drives PnP/Power; it calls our
IRP_MJ_INTERNAL_DEVICE_CONTROL handler to fetch the HID/report descriptors,
device attributes, and input reports. We run in POLLED mode
(DevicesArePolled = TRUE): hidclass paces IOCTL_HID_READ_REPORT and we
complete each synchronously with the current cached report. This avoids
pending-IRP + cancel-routine machinery entirely (the usual crash surface in
a virtual HID driver).
* Sideband: a raw control device (\Device\RioGamepadXP + a \DosDevices symlink)
that the app opens by name. On IOCTL_RIO_SUBMIT_REPORT we copy the 25 report
bytes into the cache. hidclass, on its next poll, hands them to the OS.
Dispatch ownership: HidRegisterMinidriver replaces our driver object's
CREATE/CLOSE/DEVICE_CONTROL (and PnP/Power/etc.) entries with hidclass thunks
that assume a HID FDO. Our control device shares the same driver object, so we
save hidclass's pointers and reinstall thin wrappers that route the control
device's IRPs to us and forward everything else to hidclass.
--*/
#include <ntddk.h>
#include <hidport.h>
#include <initguid.h> // realize DEFINE_GUID storage from Public.h
#include "Public.h"
#include "ReportDescriptor.h"
//
// Global single-instance state.
//
static PDEVICE_OBJECT g_ControlDevice = NULL;
static KSPIN_LOCK g_ReportLock;
static UCHAR g_Report[RIO_REPORT_SIZE];
// hidclass-installed dispatch pointers we wrap.
static PDRIVER_DISPATCH g_HidCreate = NULL;
static PDRIVER_DISPATCH g_HidClose = NULL;
static PDRIVER_DISPATCH g_HidDeviceControl = NULL;
static UNICODE_STRING g_SymlinkName;
//
// The HID descriptor that advertises our single report descriptor. bcdHID 1.11.
//
#include <pshpack1.h>
typedef struct _RIO_HID_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
USHORT bcdHID;
UCHAR bCountry;
UCHAR bNumDescriptors;
UCHAR bReportType;
USHORT wReportLength;
} RIO_HID_DESCRIPTOR;
#include <poppack.h>
static const RIO_HID_DESCRIPTOR g_HidDescriptor =
{
sizeof(RIO_HID_DESCRIPTOR),
HID_HID_DESCRIPTOR_TYPE, // 0x21
0x0111, // HID 1.11
0x00, // not localized
1, // one report descriptor
HID_REPORT_DESCRIPTOR_TYPE, // 0x22
sizeof(g_RioReportDescriptor)
};
//
// Reset the cached report to the neutral rest state: axes centered (16383 =
// 0x3FFF little-endian), hat null (0x0F), all buttons released.
//
static VOID
RioResetReport(VOID)
{
ULONG i;
KIRQL irql;
KeAcquireSpinLock(&g_ReportLock, &irql);
RtlZeroMemory(g_Report, sizeof(g_Report));
for (i = 0; i < 6; i++) // 6 axes, 2 bytes each, centered
{
g_Report[i * 2] = 0xFF;
g_Report[i * 2 + 1] = 0x3F;
}
g_Report[12] = 0x0F; // hat centered/null
KeReleaseSpinLock(&g_ReportLock, irql);
}
//
// Complete an IRP with a status and information count.
//
static NTSTATUS
RioComplete(PIRP Irp, NTSTATUS Status, ULONG_PTR Information)
{
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = Information;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
//
// IRP_MJ_INTERNAL_DEVICE_CONTROL on the HID FDO: hidclass asks us for the
// descriptors / attributes / input reports. These IOCTLs are METHOD_NEITHER;
// hidclass supplies the output buffer at Irp->UserBuffer with the length in
// Parameters.DeviceIoControl.OutputBufferLength.
//
static NTSTATUS
RioInternalDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG_PTR info = 0;
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;
PVOID out = Irp->UserBuffer;
UNREFERENCED_PARAMETER(DeviceObject);
switch (stack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_HID_GET_DEVICE_DESCRIPTOR:
if (outLen < sizeof(g_HidDescriptor)) { status = STATUS_BUFFER_TOO_SMALL; break; }
RtlCopyMemory(out, &g_HidDescriptor, sizeof(g_HidDescriptor));
info = sizeof(g_HidDescriptor);
break;
case IOCTL_HID_GET_REPORT_DESCRIPTOR:
if (outLen < sizeof(g_RioReportDescriptor)) { status = STATUS_BUFFER_TOO_SMALL; break; }
RtlCopyMemory(out, g_RioReportDescriptor, sizeof(g_RioReportDescriptor));
info = sizeof(g_RioReportDescriptor);
break;
case IOCTL_HID_GET_DEVICE_ATTRIBUTES:
{
PHID_DEVICE_ATTRIBUTES attr = (PHID_DEVICE_ATTRIBUTES)out;
if (outLen < sizeof(HID_DEVICE_ATTRIBUTES)) { status = STATUS_BUFFER_TOO_SMALL; break; }
RtlZeroMemory(attr, sizeof(HID_DEVICE_ATTRIBUTES));
attr->Size = sizeof(HID_DEVICE_ATTRIBUTES);
attr->VendorID = RIO_VENDOR_ID;
attr->ProductID = RIO_PRODUCT_ID;
attr->VersionNumber = RIO_VERSION;
info = sizeof(HID_DEVICE_ATTRIBUTES);
break;
}
case IOCTL_HID_READ_REPORT:
{
KIRQL irql;
if (outLen < RIO_REPORT_SIZE) { status = STATUS_BUFFER_TOO_SMALL; break; }
KeAcquireSpinLock(&g_ReportLock, &irql);
RtlCopyMemory(out, g_Report, RIO_REPORT_SIZE);
KeReleaseSpinLock(&g_ReportLock, irql);
info = RIO_REPORT_SIZE;
break;
}
case IOCTL_HID_WRITE_REPORT:
case IOCTL_HID_SET_FEATURE:
case IOCTL_HID_GET_FEATURE:
// No output reports / feature reports on this device.
status = STATUS_NOT_SUPPORTED;
break;
case IOCTL_HID_GET_STRING:
// DirectInput takes the friendly name from the registry OEMName value
// (set at install), not from a HID string, so an empty success is fine.
status = STATUS_SUCCESS;
info = 0;
break;
default:
status = STATUS_NOT_SUPPORTED;
break;
}
return RioComplete(Irp, status, info);
}
//
// hidclass calls this (our saved AddDevice) after it has created the HID FDO.
// Nothing to attach — hidclass owns the stack — so just succeed.
//
static NTSTATUS
RioAddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT FunctionalDeviceObject)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(FunctionalDeviceObject);
return STATUS_SUCCESS;
}
//
// Sideband handlers (control device only). CREATE/CLOSE just succeed.
//
static NTSTATUS
RioControlCreateClose(PIRP Irp)
{
return RioComplete(Irp, STATUS_SUCCESS, 0);
}
//
// IOCTL_RIO_SUBMIT_REPORT (METHOD_BUFFERED): copy the 25 report bytes into the
// cache; hidclass hands them out on its next poll.
//
static NTSTATUS
RioControlDeviceControl(PIRP Irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status;
if (stack->Parameters.DeviceIoControl.IoControlCode == IOCTL_RIO_SUBMIT_REPORT)
{
if (stack->Parameters.DeviceIoControl.InputBufferLength != RIO_REPORT_SIZE)
{
status = STATUS_INVALID_BUFFER_SIZE;
}
else
{
KIRQL irql;
KeAcquireSpinLock(&g_ReportLock, &irql);
RtlCopyMemory(g_Report, Irp->AssociatedIrp.SystemBuffer, RIO_REPORT_SIZE);
KeReleaseSpinLock(&g_ReportLock, irql);
status = STATUS_SUCCESS;
}
}
else
{
status = STATUS_INVALID_DEVICE_REQUEST;
}
return RioComplete(Irp, status, 0);
}
//
// Dispatch wrappers: route the control device's IRPs to us, forward the HID
// FDO's IRPs to the saved hidclass handlers.
//
static NTSTATUS
RioDispatchCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
if (DeviceObject == g_ControlDevice)
return RioControlCreateClose(Irp);
return g_HidCreate(DeviceObject, Irp);
}
static NTSTATUS
RioDispatchClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
if (DeviceObject == g_ControlDevice)
return RioControlCreateClose(Irp);
return g_HidClose(DeviceObject, Irp);
}
static NTSTATUS
RioDispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
if (DeviceObject == g_ControlDevice)
return RioControlDeviceControl(Irp);
return g_HidDeviceControl(DeviceObject, Irp);
}
//
// Unload: drop the symlink and control device. hidclass tears down the HID FDO.
//
static VOID
RioUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
if (g_SymlinkName.Buffer != NULL)
IoDeleteSymbolicLink(&g_SymlinkName);
if (g_ControlDevice != NULL)
IoDeleteDevice(g_ControlDevice);
}
//
// Create the sideband control device and its DOS-device symbolic link.
//
static NTSTATUS
RioCreateControlDevice(PDRIVER_OBJECT DriverObject)
{
NTSTATUS status;
UNICODE_STRING deviceName;
RtlInitUnicodeString(&deviceName, RIO_XP_DEVICE_NAME);
RtlInitUnicodeString(&g_SymlinkName, RIO_XP_SYMLINK_NAME);
status = IoCreateDevice(
DriverObject,
0, // no device extension; state is global
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE, // not exclusive
&g_ControlDevice);
if (!NT_SUCCESS(status))
return status;
g_ControlDevice->Flags |= DO_BUFFERED_IO; // IOCTL_RIO_SUBMIT_REPORT is METHOD_BUFFERED
status = IoCreateSymbolicLink(&g_SymlinkName, &deviceName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(g_ControlDevice);
g_ControlDevice = NULL;
g_SymlinkName.Buffer = NULL;
return status;
}
g_ControlDevice->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
HID_MINIDRIVER_REGISTRATION reg;
KeInitializeSpinLock(&g_ReportLock);
RioResetReport();
//
// Our own dispatch entries. hidclass replaces the HID-related ones during
// registration; we re-wrap CREATE/CLOSE/DEVICE_CONTROL afterwards.
//
DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = RioInternalDeviceControl;
DriverObject->MajorFunction[IRP_MJ_CREATE] = RioDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = RioDispatchClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = RioDispatchDeviceControl;
DriverObject->DriverExtension->AddDevice = RioAddDevice;
DriverObject->DriverUnload = RioUnload;
RtlZeroMemory(&reg, sizeof(reg));
reg.Revision = HID_REVISION;
reg.DriverObject = DriverObject;
reg.RegistryPath = RegistryPath;
reg.DeviceExtensionSize = 0; // per-device state is global (single instance)
reg.DevicesArePolled = TRUE; // polled reads → no pending-IRP/cancel machinery
status = HidRegisterMinidriver(&reg);
if (!NT_SUCCESS(status))
return status;
//
// hidclass has now overwritten CREATE/CLOSE/DEVICE_CONTROL (and PnP/Power/
// etc.) with its own thunks. Save the ones we need to forward, then reinstall
// our wrappers so the control device's IRPs come back to us.
//
g_HidCreate = DriverObject->MajorFunction[IRP_MJ_CREATE];
g_HidClose = DriverObject->MajorFunction[IRP_MJ_CLOSE];
g_HidDeviceControl = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL];
DriverObject->MajorFunction[IRP_MJ_CREATE] = RioDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = RioDispatchClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = RioDispatchDeviceControl;
//
// hidclass also replaces DriverUnload; chain our teardown ahead of nothing
// (hidclass's unload runs via its own bookkeeping). Reassert ours.
//
DriverObject->DriverUnload = RioUnload;
status = RioCreateControlDevice(DriverObject);
if (!NT_SUCCESS(status))
{
// The HID device can still work without the sideband, but the app can't
// feed it, so fail registration cleanly.
return status;
}
return STATUS_SUCCESS;
}
+7
View File
@@ -0,0 +1,7 @@
TARGETNAME=RioGamepadXP
TARGETTYPE=DRIVER
# Link against hidclass's minidriver import library (HidRegisterMinidriver).
TARGETLIBS=$(DDK_LIB_PATH)\hidclass.lib
SOURCES=rioxp.c
@@ -36,7 +36,15 @@ public sealed class HidFeederJoystickSink : IJoystickSink, IDisposable
public static bool TryCreate(out HidFeederJoystickSink? sink)
{
sink = null;
#if NET40
// Windows XP: RioGamepadXP is a HID minidriver whose sideband control
// device can't carry a device interface (no PDO), so it's opened by its
// fixed symbolic-link name rather than via SetupDi + interface GUID.
// Contract is otherwise identical (see driver/RioGamepadXP/Public.h).
string? path = @"\\.\RioGamepadXP";
#else
string? path = FindDevicePath();
#endif
if (path is null)
return false;