使用HidD_GetInputReport..检索XBOX ONEs按钮状态

问题描述

我试图通过Microsoft HID API与XBox ONE控制器通话,不使用 XINPUT。目前,我可以通过使用HidD_SetoutputReport(HANDLE,VOID*,ULONG)发送数据包来控制所有的隆隆声电机(包括力反馈触发器)。但是我坚持使用HidD_GetInputReport(HANDLE,ULONG)ReadFile() / ReadFileEx()来读取按钮值,无论是否使用FILE_FLAG_OVERLAPPED以及使用OVERLAPPED和Windows事件来创建HANDLE。

借助以下文章https://github.com/quantus/xbox-one-controller-protocol,我已经对USB URB协议进行了反向工程。主要目标是克服XINPUT的开销并编写一个灵活的框架,以便我也可以集成其他游戏手柄。

这就是我所完成的:

  1. 我已通过USB将游戏手柄与计算机连接(以便可以读取从设备发送和接收的所有USB软件包)
  2. 我使用SetupDiGetClassDevs(...)SetupDiEnumDeviceInfo(...)SetupDiEnumDeviceInterfaces(...)SetupDiGetDeviceInterfaceDetail(...)找到了控制器的路径
  3. 我已经使用HANDLE gamePad = CreateFile(path,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL)创建了设备的句柄
  4. 使用HidP_GetCaps(HANDLE,HIDP_CAPS*)似乎未返回有效数据,因为它报告的OutputReportByteLength为0,但我能够发送大小为5(打开)和9(设置隆隆声马达)的输出报告
  5. 所有输入和输出数据(至少按钮和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>
  1. 有了这些信息,我就可以按我的意愿让马达和触发器隆隆声

例如:我的两个输出隆隆声包如下所示(使用脉冲长度来脏乱地打开和关闭电机):

Output packages in Device Monitoring Studio URB View

这将打开所有隆隆声电动机的速度为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,0xFF,0xFF 0x00,0xEB};

HidD_SetoutputReport(gamePad,(PVOID)&rc,sizeof(RumbleContinous ));

我遇到了问题

从控制器看输入包,看起来您需要创建一个大小为0x0E = 14的缓冲区,将它存储为ZeroMemory(或者像MSDN建议的那样将第一个字节写为0),然后调用HidD_GetInputReport(HANDLE,buffer,14)

Input package in Device Monitoring Studio URB View

所以我所做的就是调用HidD_FlushQueue(),以确保下一个包是输入包。然后插入一点延迟,以便能够更改一些控制器值。之后,我尝试使用HidD_GetInputReport(HANDLE,cmd_in,14)读入BYTE数组,但函数始终因GetLastError() == 0x00000057 // ERROR_INVALID_ParaMETER失败

由于HID能够过滤包,因此可能需要分配比预期大一字节的缓冲区,并将所需的报告ID传递到位置0的缓冲区。这是我所做的:

BYTE cmd_in[15];
ZeroMemory(cmd_in,15);
cmd_in[0] = 0x20;
HidD_GetInputReport(gamePad,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,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,bs)) {
        // Output result (ommited)
    }
    else {
        // Print error (ommited)
    }
}

仍然没有成功。根据特殊要求的输出格式和错误HidP_GetCaps(...)读数,我怀疑XBox ONE Gamepad驱动程序需要一个已经在输入缓冲区中蜂鸣的特殊标头(据我所知,HidD_GetInputReport(...)只是调用用户/内核模式驱动程序回叫;因此驱动程序可以自由执行检查并拒绝发送给它的所有数据)

也许外面有人知道如何为XBox One控制器调用HidD_GetInputReport(...)

我知道,由于SimpleHIDWrite能够看到按钮状态,因此可以检索该输入数据。即使格式完全不同(例如,两个触发器组合在一个字节中。在USB Pack中,每个触发器都有自己的字节):

SimpleHIDWrite XBOX ONE Controller

我还要提到的是,HIDWrite无需按下任何按钮即可查看数据!从SimpleHIDWrite查看日志,看起来好像正在从RD读取00的15字节数据,具有16字节数组和元素0为00(在我的应用程序中不起作用)。还是只是转储所有传入的数据。如果是,这怎么可能?这对我来说也是一个选择!

解决方法

执行以下代码时,我观察了XINPUT在做什么:

XINPUT_STATE s;
XInputGetState(0,&s);

事实证明,在从控制器读取数据之前,XINPUT的工作与我相同。 HidD_GetInputReport(...)的窃窃私语正在呼叫DeviceIoControl(...)。因此,我所做的就是快速的Google Serch“ DeviceIoControl xbox”和tada,它不需要我自己弄清楚内存布局:Getting xbox controller input without xinput

编辑: 即使通过蓝牙连接了游戏手柄,也可以使用DeviceIoControl(...),而当通过蓝牙连接游戏手柄时HidD_SetOutputReport(...)不起作用。我通过蓝牙读取DeviceIoControl(...)要求通过输出缓冲区中存在附加参数的内容。但是我目前正在尝试找出一种通过DeviceIoControl(...)控制隆隆声电机的方法。如果您有任何建议,请随时发表评论!上面链接中的文章仅激活了两个隆隆声电机,而不激活触发器!

编辑2: 我尝试将DeviceIoControl(HANDLE,j,CHAR*,i,NULL,DWORD*,NULL)从j 0x0扫到0xFFFFFFFF,将i 0x1扫到0x3F。很好,起初……但是……经过几个j值后,我得到了蓝屏显示:WDF_Violation(至少我知道如何使计算机崩溃;))

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...