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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
};
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
!INCLUDE $(NTMAKEENV)\makefile.def
|
||||
@@ -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(®, 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(®);
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user