Ordinarily XInput controllers are identified simply using an index corresponding to the player number of the controller. Is there a way to obtain more information about a controller with a specific index, such as its vendor ID, product ID, or device name?

Even better would be a identifier that corresponds uniquely and consistently to just that controller so that it can be distinguished from all other XInput devices regardless of its index, including another controller that's an identical model (i.e. same product and vendor ID), similar to the instance GUID available using DirectInput.

Can this be accomplished using XInput or another Microsoft API? I'm also open to using undocumented functions if need be.

2

There are 2 answers

2
Toni Georgiev On

There are a few undocumented functions inside the XInput1_4.dll. You can get the Vendor ID and Product ID like this:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Xinput.h>
#include <stdio.h>

struct XINPUT_CAPABILITIES_EX
{
    XINPUT_CAPABILITIES Capabilities;
    WORD vendorId;
    WORD productId;
    WORD revisionId;
    DWORD a4; //unknown
};

typedef DWORD(_stdcall* _XInputGetCapabilitiesEx)(DWORD a1, DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES_EX* pCapabilities);
_XInputGetCapabilitiesEx XInputGetCapabilitiesEx;

void main()
{
    HMODULE moduleHandle = LoadLibrary(TEXT("XInput1_4.dll"));
    XInputGetCapabilitiesEx = (_XInputGetCapabilitiesEx)GetProcAddress(moduleHandle, (char*)108);

    for (int i = 0; i < 4; ++i)
    {
        printf("Gamepad %d ", i);

        XINPUT_CAPABILITIES_EX capsEx;
        if (XInputGetCapabilitiesEx(1, i, 0, &capsEx) == ERROR_SUCCESS)
        {
            printf("connected, vid = 0x%04X pid = 0x%04X\n", (int)capsEx.vendorId, (int)capsEx.productId);
        }
        else
        {
            printf("not connected\n");
        }
    }
}
0
Dwedit On

What XInput internally does is open a device, then call DeviceIoControl on it every time it reads the joypad. (control code 0x8000e00c)

You need to hook these functions imported by "XInput1_4.dll":

  • CreateFileW from "api-ms-win-core-file-l1-1-0.dll"
  • DuplicateHandle from "api-ms-win-core-handle-l1-1-0.dll"
  • CloseHandle from "api-ms-win-core-handle-l1-1-0.dll"
  • DeviceIoControl from "api-ms-win-core-io-l1-1-0.dll"

Using the hooks for CreateFileW, DuplicateHandle and CloseHandle, you can keep track of what filename is associated with a handle.

Then when you see a call to DeviceIoControl with control code 0x8000e00c, you will know what filename is being read.


The first time you call XInputGetState, it will open multiple devices, and call DeviceIoControl multiple times, regardless of what player number you have asked for. You are only interested in the last filename seen by DeviceIoControl before XInputGetState returns. And if XInputGetState indicates the controller is not plugged in, disregard the filename you have collected for that controller number.


Examples of filenames I have seen on my own computer:

  • \\?\hid#{00001124-0000-1000-8000-00805f9b34fb}&vid_045e&pid_02e0&ig_00#8&7074921&2&0000#{ec87f1e3-c13b-4100-b5f7-8b84d54260cb}
  • \\?\usb#vid_045e&pid_028e#1&1a590e2c&1&01#{ec87f1e3-c13b-4100-b5f7-8b84d54260cb}

edit:

One more hook is required as well.

  • CoCreateInstance from "api-ms-win-core-com-l1-1-0.dll", to hook creating the undocumented IDeviceBroker COM object. If it can successfully create an IDeviceBroker COM object, it will use that instead of the call to CreateFileW. Parameters will be: CLSID_DeviceBroker = {acc56a05-e277-4b1e-a43e-7a73e3cd6e6c}, IID_IDeviceBroker = {8604b268-34a6-4b1a-a59f-cdbd8379fd98}. The method OpenDeviceFromInterfacePath will be called instead of CreateFileW. Alternatively, you can make creating the IDeviceBroker object simply fail, and it will proceed to use CreateFileW as usual.