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>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using RioJoy.Core.Calibration;
|
||||
using RioJoy.Core.Hid;
|
||||
using RioJoy.Core.Mapping;
|
||||
using Xunit;
|
||||
|
||||
namespace RioJoy.Core.Tests.Hid;
|
||||
|
||||
public class RioHidReportTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultReport_CentersAxesAndHat()
|
||||
{
|
||||
var report = new RioHidReport();
|
||||
byte[] b = report.ToArray();
|
||||
|
||||
Assert.Equal(RioHidReport.Size, b.Length);
|
||||
// Each axis = 16383 = 0x3FFF, little-endian.
|
||||
for (int offset = 0; offset < 12; offset += 2)
|
||||
{
|
||||
Assert.Equal(0xFF, b[offset]);
|
||||
Assert.Equal(0x3F, b[offset + 1]);
|
||||
}
|
||||
|
||||
Assert.Equal(0x0F, b[12] & 0x0F); // hat centered/null
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetAxis_WritesLittleEndian_AndClamps()
|
||||
{
|
||||
var report = new RioHidReport();
|
||||
report.SetAxis(JoyAxis.Z, 0x1234);
|
||||
Assert.Equal(0x34, report.Bytes[4]);
|
||||
Assert.Equal(0x12, report.Bytes[5]);
|
||||
|
||||
report.SetAxis(JoyAxis.Z, 70000); // over max → clamped to 32767
|
||||
Assert.Equal(0xFF, report.Bytes[4]);
|
||||
Assert.Equal(0x7F, report.Bytes[5]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RioHat.Up, 0x00)]
|
||||
[InlineData(RioHat.Right, 0x01)]
|
||||
[InlineData(RioHat.Down, 0x02)]
|
||||
[InlineData(RioHat.Left, 0x03)]
|
||||
[InlineData(RioHat.Centered, 0x0F)]
|
||||
public void SetHat_EncodesNibble(RioHat hat, int expected)
|
||||
{
|
||||
var report = new RioHidReport();
|
||||
report.SetHat(hat);
|
||||
Assert.Equal(expected, report.Bytes[12] & 0x0F);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 13, 0x01)] // button 1 → byte 13 bit 0
|
||||
[InlineData(8, 13, 0x80)] // button 8 → byte 13 bit 7
|
||||
[InlineData(9, 14, 0x01)] // button 9 → byte 14 bit 0
|
||||
[InlineData(96, 24, 0x80)] // button 96 → byte 24 bit 7
|
||||
public void SetButton_SetsCorrectBit(int button, int byteIndex, int mask)
|
||||
{
|
||||
var report = new RioHidReport();
|
||||
report.SetButton(button, pressed: true);
|
||||
Assert.Equal(mask, report.Bytes[byteIndex]);
|
||||
|
||||
report.SetButton(button, pressed: false);
|
||||
Assert.Equal(0, report.Bytes[byteIndex]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetButton_RejectsOutOfRange()
|
||||
{
|
||||
var report = new RioHidReport();
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => report.SetButton(0, true));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => report.SetButton(97, true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Buttons_ComposeIndependently()
|
||||
{
|
||||
var report = new RioHidReport();
|
||||
report.SetButton(1, true);
|
||||
report.SetButton(2, true);
|
||||
report.SetButton(1, false);
|
||||
Assert.Equal(0x02, report.Bytes[13]); // only button 2 remains
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user