我正在尝试通过 Microsoft HID API 与 XBOX ONE 控制器交谈,而不使用XINPUT。我目前能够通过使用HidD_SetOutputReport(HANDLE, VOID*, ULONG)
. 但是我一直坚持使用HidD_GetInputReport(HANDLE, VOID*, ULONG)
或使用和不使用和使用Windows 事件ReadFile() / ReadFileEx()
创建 HANDLE 来读取按钮值。FILE_FLAG_OVERLAPPED
OVERLAPPED
在以下文章https://github.com/quantus/xbox-one-controller-protocol的帮助下,我已经对 USB URB 协议进行了逆向工程。主要目标是克服 XINPUT 开销并编写一个灵活的框架,以便我也可以集成其他游戏手柄。
这就是我完成的:
- 我已经通过 USB 将游戏手柄连接到我的电脑(这样我就可以读取从设备发送和接收的所有 USB 包)
- 我使用
SetupDiGetClassDevs(...)
、SetupDiEnumDeviceInfo(...)
和找到了控制器SetupDiEnumDeviceInterfaces(...)
的路径SetupDiGetDeviceInterfaceDetail(...)
- 我已经使用创建了设备的句柄
HANDLE gamePad = CreateFile(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL)
- Using
HidP_GetCaps(HANDLE, HIDP_CAPS*)
似乎没有返回有效数据,因为它报告的 OutputReportByteLength 为 0,但我能够发送大小为 5(打开)和 9(设置震动马达)的输出报告 - 所有输入和输出数据(至少按钮和 Rumble 电机)似乎都遵循以下模式
byte 0: Package type
byte 1: was always 0x00
byte 2: Package number (always incrementing every package)
byte 3: Size of following data
byte 4+: <Data>
- 有了这些信息,我就可以让马达和触发器按照我的意愿发出隆隆声
例如:我的两个输出隆隆声数据包看起来像这样(使用脉冲长度来脏地打开和关闭电机): 这会打开所有电机,其中隆隆声电机的速度为 0xFF,触发器的速度为 0xF0。我是这样做的:
struct RumbleContinous{
BYTE mode;
BYTE mask; // Motor Mask 0b 0 0 0 0 LT RT L R
BYTE lTForce;
BYTE rTForce;
BYTE lForce;
BYTE rForce;
BYTE pulseLength;
BYTE offTime;
BYTE terminator; // Terminator / Dummy / ?? (XINPUT sends that as 0xEB!) / Changing seems to not make any changes
};
RumbleContinous rc = {0x00, 0x0F, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF 0x00, 0xEB};
HidD_SetOutputReport(gamePad, (PVOID)&rc, sizeof(RumbleContinous ));
现在我的问题
查看来自控制器的输入包,您似乎需要创建一个大小为 0x0E = 14 的缓冲区,ZeroMemory 它(或者像 MSDN 建议的那样将第一个字节写入 0)然后调用HidD_GetInputReport(HANDLE, buffer, 14)
所以我所做的是调用HidD_FlushQueue()
以确保下一个包是输入包。然后我插入一点延迟,以便能够更改一些控制器值。之后我尝试读入一个 BYTE 数组,HidD_GetInputReport(HANDLE, cmd_in, 14)
但函数总是失败GetLastError() == 0x00000057 // ERROR_INVALID_PARAMETER
由于 HID 能够过滤包,因此可能需要分配一个比预期大一字节的缓冲区,并将所需的报告 ID 传递到位置 0 的缓冲区。这就是我所做的:
BYTE cmd_in[15];
ZeroMemory(cmd_in, 15);
cmd_in[0] = 0x20;
HidD_GetInputReport(gamePad, cmd_in, 15);
仍然没有成功。由于该HidP_GetCaps(...)
函数报告了 16 的输入报告(但我不相信这一点,因为它已经用 0 的输出报告大小欺骗了我)我尝试扫描许多缓冲区大小:
BYTE cmd_in[30];
for (UINT bs = 0; bs < 30; bs++) {
ZeroMemory(cmd_in, 30);
HidD_FlushQueue(gamePad); // Flushing works
Sleep(500);
if (HidD_GetInputReport(gamePad, cmd_in, bs)) {
// Output result (ommited)
}
else {
// Print error (ommited)
}
}
和
BYTE cmd_in[30];
for (UINT bs = 0; bs < 30; bs++) {
ZeroMemory(cmd_in, 30);
cmd_in[0] = 0x20;
HidD_FlushQueue(gamePad); // Flushing works
Sleep(500);
if (HidD_GetInputReport(gamePad, cmd_in, bs)) {
// Output result (ommited)
}
else {
// Print error (ommited)
}
}
仍然没有成功。根据特殊要求的输出格式和错误的HidP_GetCaps(...)
读数,我怀疑 XBOX ONE 游戏手柄驱动程序需要输入缓冲区中已经存在的特殊标头(据我所知 HidD_GetInputReport(...) 只是调用用户/内核模式驱动程序调用返回;因此驱动程序可以自由地执行检查并拒绝发送给它的所有数据)
也许那里的任何人都知道如何为 XBOX One 控制器调用 HidD_GetInputReport(...)
我知道可以检索输入数据,因为SimpleHIDWrite能够看到按钮状态。即使格式完全不同(例如,两个触发器组合在一个字节中。在 USB Packed 中,每个触发器都有自己的字节):
我还应该提到,HIDWrite 无需按任何按钮即可查看数据!查看来自 SimpleHIDWrite 的日志,它看起来像是RD
从00
15 个字节的数据中读取的,有一个 16 字节的数组和 00 处的元素 0(在我的应用程序中不起作用)。或者它只是转储所有传入的数据。如果是,这怎么可能?这对我来说也是一个选择!