Using HidD_GetInputReport(..) to retrive XBOX ONEs button states

0

I am trying to talk to the XBOX ONE Controller via the Microsoft HID API without using XINPUT. I'm currently able to control all the rumble motors (including the force feedback triggers) by sending the packet using HidD_SetOutputReport(HANDLE, VOID*, ULONG). But I'm stuck reading the button values using HidD_GetInputReport(HANDLE, VOID*, ULONG) or ReadFile() / ReadFileEx() with and without the HANDLE being created with FILE_FLAG_OVERLAPPED and using OVERLAPPED and Windows Events.

I have already reverse engineered the USB URB protocol with the help of the following article https://github.com/quantus/xbox-one-controller-protocol. The main goal is to overcome the XINPUT overhead and writing a flexible framework so that I can integrate other gamepads as well.

That is what I accomplished:

  1. I have connected the gamepad via USB with my computer (So that I can read all the USB Packages sent and received from the device)
  2. I have found the controller’s path using SetupDiGetClassDevs(...), SetupDiEnumDeviceInfo(...), SetupDiEnumDeviceInterfaces(...) and SetupDiGetDeviceInterfaceDetail(...)
  3. I have created a handle to the device using HANDLE gamePad = CreateFile(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL)
  4. Using HidP_GetCaps(HANDLE, HIDP_CAPS*) doesn’t seem to return valid data since it reports a OutputReportByteLength of 0 but I am able to send Output reports of size 5 (Turn ON) and 9 (Set rumble motors)
  5. All in and outcoming data (At least buttons and Rumble motors) seem to follow the following pattern
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. With that information i was able to let the motors and triggers rumble as I desire

For example: Two of my output rumble packets look like this (Using the pulse length to dirty turn on and off the motors): Output packages in Device Monitoring Studio URB View This turns on and of all motors with the rumble motors speed at 0xFF and the triggers at speed 0xF0. This is how I did it:

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 ));

Now to my problem

Looking at the input packages from the controller it looks like you need to create a buffer of size 0x0E = 14, ZeroMemory it (or just write the first byte to 0 like MSDN is suggesting) and just call HidD_GetInputReport(HANDLE, buffer, 14) Input package in Device Monitoring Studio URB View

So what I did was calling HidD_FlushQueue() to make sure the next package is the input package. Then I insert a little delay so that I am able to change some controller values. After that I tried reading into a BYTE array with HidD_GetInputReport(HANDLE, cmd_in, 14) but the function always failed with GetLastError() == 0x00000057 // ERROR_INVALID_PARAMETER

Since HID is able to filter packages it may be required to allocate a buffer one byte larger than expected and pass the required report id to the buffer at location 0. This is what I did:

BYTE cmd_in[15];
ZeroMemory(cmd_in, 15);
cmd_in[0] = 0x20;
HidD_GetInputReport(gamePad, cmd_in, 15);

Still no success. Since the HidP_GetCaps(...) function reported an input report of 16 (But I don't trust this since it already fooled me with output report size of 0) I tried sweeping over many buffer sizes:

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)
    }
}

and

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)
    }
}

Still no success. According to the special required output format and the wrong HidP_GetCaps(...) readings i suspect that the XBOX ONE Gamepad Driver requires a special header already beeing in the input buffer (As far as I know HidD_GetInputReport(...) just calls the User / Kernel Mode Driver call back; So the driver is free to perform checks and reject all data being send to it)

Maybe anyone out there does know how to call HidD_GetInputReport(...) for the XBOX One controller

I know that it is possible to retrieve that input data since the SimpleHIDWrite is able to see the button states. Even through the format is totally different (The two triggers for example are combined in one byte. In the USB Packed each trigger has its own byte):

SimpleHIDWrite XBOX ONE Controller

I should also mention that the HIDWrite sees the data without the press of any button! Looking at the log from the SimpleHIDWrite it looked like it is reading RD from 00 15 bytes of data, having a 16-byte array and element 0 at 00 (Didn't work in my application). Or does it just dump all data coming in. If yes how is this possible? That would be an option for me too!

c++
windows
hid
xbox-one
hidpi
asked on Stack Overflow Nov 7, 2020 by Ohjurot

1 Answer

0

I looked at what XINPUT is doing when executing the following code:

XINPUT_STATE s;
XInputGetState(0, &s);

It turned out that XINPUT is doing the same stuff I did until it comes to reading the data from the controler. Insteal of HidD_GetInputReport(...) XINPUT is calling DeviceIoControl(...). So what I did was a fast google serch "DeviceIoControl xbox" and tada here it is without the need to to figure out the memory layout on my own: Getting xbox controller input without xinput

Edit: Using DeviceIoControl(...) works even if the gamepad is connected via bluetooth while HidD_SetOutputReport(...) does not work when the gamepad is connected via bluetooth. I rember reading something that DeviceIoControl(...) via bluetooth requires an addition parameter to be present in the output buffer. But I'm currently trying to figure out a way to controle the rumble motors via DeviceIoControl(...). If you have any sugestions feel free to comment! The article in the link above only activates the two rumble motors but not the triggers!

Edit 2: I tried sweeping over DeviceIoControl(HANDLE, j, CHAR*, i, NULL, 0, DWORD*, NULL) from j 0x0 to 0xFFFFFFFF and i 0x1 to 0x3F. Well it worked... at first... but after a few values of j I got a Blue-Screen: WDF_Violation (At least I know how to crash a computer ;) )

answered on Stack Overflow Nov 8, 2020 by Ohjurot • edited Nov 8, 2020 by Ohjurot

User contributions licensed under CC BY-SA 3.0