Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get descriptor (windows fixes) #501

Merged
merged 5 commits into from
Feb 22, 2023
Merged

Conversation

jkcmusork
Copy link

@jkcmusork jkcmusork commented Feb 2, 2023

Hello @JoergAtGithub and @Youw, First off, thank you both for your work on supporting reading/reconstructing report descriptors within this hidapi get-descriptors branch. It has been helpful for a project I've been working on.

I believe I've found a few small errors on windows within the reconstruction process that I'm submitting for your review and consideration merging back to the main hidapi repository. Thank you in advance for your attention.

The first commit is simply a null pointer test, which may be irrelevant now that I'm successfully sorting collection bit ranges, but harmless to include and might future proof other problematic cases.

The second commit contains two changes:

  1. The sorting of child collections based on bit ranges was not working properly for me while testing with an xbox controller for example. As best I could tell, as the code was comparing and sorting values in coll_child_order array, it was not using the appropriate collection index for looking up the bit ranges, and assuming that the coll_child_order array indexing and the coll_bit_range array indexing were the same. I believe there is a level of indirection here that is necessary to take into account as reflected in the changeset. Without this change, child collections were not properly sorted and then the code would try to insert padding where there should be none.
  2. The final bit padding as far as I could tell, had an off by one bug. I needed to add one to mark the correct bits of padding without overlapping the final valid bit in the report.

@JoergAtGithub
Copy link
Contributor

@jkcmusork Thank you for the patch! Could you please provide the report descriptor (and the preparsed data dump) of the device where these issues occured.

@jkcmusork
Copy link
Author

My pleasure. I'll need a little more time to get a preparsed data dump, unless you have a quick code snippet for the appropriate way to determine preparsed data size (looks like I'll need to walk it manually) however here are the reconstructed report descriptors before the changes and after the changes (with an xbox one game controller). You'll notice that in the original reconstructed report description the first and second collections are swapped, that there is an extra 32bits of padding in the XY collection, and that there is an extra bit of padding at the end. These are all remedied in the reconstructed report description after the fixes.

Original code's reconstructed HID Report Descriptor:

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x33,        //     Usage (Rx)
0x09, 0x34,        //     Usage (Ry)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x35, 0x00,        //     Physical Minimum (0)
0x45, 0xFF,        //     Physical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //     Report Count (1)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x20,        //     Report Size (32)
0x95, 0x01,        //     Report Count (1)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x32,        //     Usage (Z)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x10,        //   Usage Maximum (0x10)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x10,        //   Report Count (16)
0x45, 0x00,        //   Physical Maximum (0)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x08,        //   Logical Maximum (8)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x10,  //   Physical Maximum (4155)
0x65, 0x0E,        //   Unit (None)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

// 121 bytes

Modified code's reconstructed HID Report Descriptor (fixed version):

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x35, 0x00,        //     Physical Minimum (0)
0x45, 0xFF,        //     Physical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x33,        //     Usage (Rx)
0x09, 0x34,        //     Usage (Ry)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x32,        //     Usage (Z)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x10,        //   Usage Maximum (0x10)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x10,        //   Report Count (16)
0x45, 0x00,        //   Physical Maximum (0)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x08,        //   Logical Maximum (8)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x10,  //   Physical Maximum (4155)
0x65, 0x0E,        //   Unit (None)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x03,        //   Report Size (3)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

// 111 bytes

@JoergAtGithub
Copy link
Contributor

The tool to save the preparsed data as dump file is in the get_descriptor branch: https://github.com/libusb/hidapi/tree/get-descriptor/windows/pp_data_dump

@jkcmusork
Copy link
Author

Perfect. Sorry for overlooking that tool. Below is the relevant dump file:

# HIDAPI device info struct:
dev->vendor_id           = 0x045E
dev->product_id          = 0x02FF
dev->manufacturer_string = ""
dev->product_string      = "Controller (Xbox One For Windows)"
dev->release_number      = 0x0000
dev->interface_number    = -1
dev->usage               = 0x0005
dev->usage_page          = 0x0001
dev->path                = "\\?\HID#VID_045E&PID_02FF&IG_00#7&5ea4a81&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"

# Preparsed Data struct:
pp_data->MagicKey                             = 0x48696450204B4452
pp_data->Usage                                = 0x0005
pp_data->UsagePage                            = 0x0001
pp_data->Reserved                             = 0x00000000
# Input caps_info struct:
pp_data->caps_info[0]->FirstCap           = 0
pp_data->caps_info[0]->LastCap            = 7
pp_data->caps_info[0]->NumberOfCaps       = 7
pp_data->caps_info[0]->ReportByteLength   = 16
# Output caps_info struct:
pp_data->caps_info[1]->FirstCap           = 7
pp_data->caps_info[1]->LastCap            = 7
pp_data->caps_info[1]->NumberOfCaps       = 0
pp_data->caps_info[1]->ReportByteLength   = 0
# Feature caps_info struct:
pp_data->caps_info[2]->FirstCap           = 7
pp_data->caps_info[2]->LastCap            = 7
pp_data->caps_info[2]->NumberOfCaps       = 0
pp_data->caps_info[2]->ReportByteLength   = 0
# LinkCollectionArray Offset & Size:
pp_data->FirstByteOfLinkCollectionArray       = 0x02D8
pp_data->NumberLinkCollectionNodes            = 4
# Input hid_pp_cap struct:
pp_data->cap[0]->UsagePage                    = 0x0001
pp_data->cap[0]->ReportID                     = 0x00
pp_data->cap[0]->BitPosition                  = 0
pp_data->cap[0]->BitSize                      = 16
pp_data->cap[0]->ReportCount                  = 1
pp_data->cap[0]->BytePosition                 = 0x0003
pp_data->cap[0]->BitCount                     = 16
pp_data->cap[0]->BitField                     = 0x02
pp_data->cap[0]->NextBytePosition             = 0x0005
pp_data->cap[0]->LinkCollection               = 0x0001
pp_data->cap[0]->LinkUsagePage                = 0x0001
pp_data->cap[0]->LinkUsage                    = 0x0000
pp_data->cap[0]->IsMultipleItemsForArray      = 0
pp_data->cap[0]->IsButtonCap                  = 0
pp_data->cap[0]->IsPadding                    = 0
pp_data->cap[0]->IsAbsolute                   = 1
pp_data->cap[0]->IsRange                      = 0
pp_data->cap[0]->IsAlias                      = 0
pp_data->cap[0]->IsStringRange                = 0
pp_data->cap[0]->IsDesignatorRange            = 0
pp_data->cap[0]->Reserved1                    = 0x000000
pp_data->cap[0]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[0]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[0]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[0]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[0]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[0]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[0]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[0]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[0]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[0]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[0]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[0]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[0]->NotRange.Usage                        = 0x0031
pp_data->cap[0]->NotRange.Reserved1                    = 0x0031
pp_data->cap[0]->NotRange.StringIndex                  = 0
pp_data->cap[0]->NotRange.Reserved2                    = 0
pp_data->cap[0]->NotRange.DesignatorIndex              = 0
pp_data->cap[0]->NotRange.Reserved3                    = 0
pp_data->cap[0]->NotRange.DataIndex                    = 0
pp_data->cap[0]->NotRange.Reserved4                    = 0
pp_data->cap[0]->NotButton.HasNull                   = 0
pp_data->cap[0]->NotButton.Reserved4                 = 0x000000
pp_data->cap[0]->NotButton.LogicalMin                = 0
pp_data->cap[0]->NotButton.LogicalMax                = -1
pp_data->cap[0]->NotButton.PhysicalMin               = 0
pp_data->cap[0]->NotButton.PhysicalMax               = -1
pp_data->cap[0]->Units                    = 0
pp_data->cap[0]->UnitsExp                 = 0

pp_data->cap[1]->UsagePage                    = 0x0001
pp_data->cap[1]->ReportID                     = 0x00
pp_data->cap[1]->BitPosition                  = 0
pp_data->cap[1]->BitSize                      = 16
pp_data->cap[1]->ReportCount                  = 1
pp_data->cap[1]->BytePosition                 = 0x0001
pp_data->cap[1]->BitCount                     = 16
pp_data->cap[1]->BitField                     = 0x02
pp_data->cap[1]->NextBytePosition             = 0x0003
pp_data->cap[1]->LinkCollection               = 0x0001
pp_data->cap[1]->LinkUsagePage                = 0x0001
pp_data->cap[1]->LinkUsage                    = 0x0000
pp_data->cap[1]->IsMultipleItemsForArray      = 0
pp_data->cap[1]->IsButtonCap                  = 0
pp_data->cap[1]->IsPadding                    = 0
pp_data->cap[1]->IsAbsolute                   = 1
pp_data->cap[1]->IsRange                      = 0
pp_data->cap[1]->IsAlias                      = 0
pp_data->cap[1]->IsStringRange                = 0
pp_data->cap[1]->IsDesignatorRange            = 0
pp_data->cap[1]->Reserved1                    = 0x000000
pp_data->cap[1]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[1]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[1]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[1]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[1]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[1]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[1]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[1]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[1]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[1]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[1]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[1]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[1]->NotRange.Usage                        = 0x0030
pp_data->cap[1]->NotRange.Reserved1                    = 0x0030
pp_data->cap[1]->NotRange.StringIndex                  = 0
pp_data->cap[1]->NotRange.Reserved2                    = 0
pp_data->cap[1]->NotRange.DesignatorIndex              = 0
pp_data->cap[1]->NotRange.Reserved3                    = 0
pp_data->cap[1]->NotRange.DataIndex                    = 1
pp_data->cap[1]->NotRange.Reserved4                    = 1
pp_data->cap[1]->NotButton.HasNull                   = 0
pp_data->cap[1]->NotButton.Reserved4                 = 0x000000
pp_data->cap[1]->NotButton.LogicalMin                = 0
pp_data->cap[1]->NotButton.LogicalMax                = -1
pp_data->cap[1]->NotButton.PhysicalMin               = 0
pp_data->cap[1]->NotButton.PhysicalMax               = -1
pp_data->cap[1]->Units                    = 0
pp_data->cap[1]->UnitsExp                 = 0

pp_data->cap[2]->UsagePage                    = 0x0001
pp_data->cap[2]->ReportID                     = 0x00
pp_data->cap[2]->BitPosition                  = 0
pp_data->cap[2]->BitSize                      = 16
pp_data->cap[2]->ReportCount                  = 1
pp_data->cap[2]->BytePosition                 = 0x0007
pp_data->cap[2]->BitCount                     = 16
pp_data->cap[2]->BitField                     = 0x02
pp_data->cap[2]->NextBytePosition             = 0x0009
pp_data->cap[2]->LinkCollection               = 0x0002
pp_data->cap[2]->LinkUsagePage                = 0x0001
pp_data->cap[2]->LinkUsage                    = 0x0000
pp_data->cap[2]->IsMultipleItemsForArray      = 0
pp_data->cap[2]->IsButtonCap                  = 0
pp_data->cap[2]->IsPadding                    = 0
pp_data->cap[2]->IsAbsolute                   = 1
pp_data->cap[2]->IsRange                      = 0
pp_data->cap[2]->IsAlias                      = 0
pp_data->cap[2]->IsStringRange                = 0
pp_data->cap[2]->IsDesignatorRange            = 0
pp_data->cap[2]->Reserved1                    = 0x000000
pp_data->cap[2]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[2]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[2]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[2]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[2]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[2]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[2]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[2]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[2]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[2]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[2]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[2]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[2]->NotRange.Usage                        = 0x0034
pp_data->cap[2]->NotRange.Reserved1                    = 0x0034
pp_data->cap[2]->NotRange.StringIndex                  = 0
pp_data->cap[2]->NotRange.Reserved2                    = 0
pp_data->cap[2]->NotRange.DesignatorIndex              = 0
pp_data->cap[2]->NotRange.Reserved3                    = 0
pp_data->cap[2]->NotRange.DataIndex                    = 2
pp_data->cap[2]->NotRange.Reserved4                    = 2
pp_data->cap[2]->NotButton.HasNull                   = 0
pp_data->cap[2]->NotButton.Reserved4                 = 0x000000
pp_data->cap[2]->NotButton.LogicalMin                = 0
pp_data->cap[2]->NotButton.LogicalMax                = -1
pp_data->cap[2]->NotButton.PhysicalMin               = 0
pp_data->cap[2]->NotButton.PhysicalMax               = -1
pp_data->cap[2]->Units                    = 0
pp_data->cap[2]->UnitsExp                 = 0

pp_data->cap[3]->UsagePage                    = 0x0001
pp_data->cap[3]->ReportID                     = 0x00
pp_data->cap[3]->BitPosition                  = 0
pp_data->cap[3]->BitSize                      = 16
pp_data->cap[3]->ReportCount                  = 1
pp_data->cap[3]->BytePosition                 = 0x0005
pp_data->cap[3]->BitCount                     = 16
pp_data->cap[3]->BitField                     = 0x02
pp_data->cap[3]->NextBytePosition             = 0x0007
pp_data->cap[3]->LinkCollection               = 0x0002
pp_data->cap[3]->LinkUsagePage                = 0x0001
pp_data->cap[3]->LinkUsage                    = 0x0000
pp_data->cap[3]->IsMultipleItemsForArray      = 0
pp_data->cap[3]->IsButtonCap                  = 0
pp_data->cap[3]->IsPadding                    = 0
pp_data->cap[3]->IsAbsolute                   = 1
pp_data->cap[3]->IsRange                      = 0
pp_data->cap[3]->IsAlias                      = 0
pp_data->cap[3]->IsStringRange                = 0
pp_data->cap[3]->IsDesignatorRange            = 0
pp_data->cap[3]->Reserved1                    = 0x000000
pp_data->cap[3]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[3]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[3]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[3]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[3]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[3]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[3]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[3]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[3]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[3]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[3]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[3]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[3]->NotRange.Usage                        = 0x0033
pp_data->cap[3]->NotRange.Reserved1                    = 0x0033
pp_data->cap[3]->NotRange.StringIndex                  = 0
pp_data->cap[3]->NotRange.Reserved2                    = 0
pp_data->cap[3]->NotRange.DesignatorIndex              = 0
pp_data->cap[3]->NotRange.Reserved3                    = 0
pp_data->cap[3]->NotRange.DataIndex                    = 3
pp_data->cap[3]->NotRange.Reserved4                    = 3
pp_data->cap[3]->NotButton.HasNull                   = 0
pp_data->cap[3]->NotButton.Reserved4                 = 0x000000
pp_data->cap[3]->NotButton.LogicalMin                = 0
pp_data->cap[3]->NotButton.LogicalMax                = -1
pp_data->cap[3]->NotButton.PhysicalMin               = 0
pp_data->cap[3]->NotButton.PhysicalMax               = -1
pp_data->cap[3]->Units                    = 0
pp_data->cap[3]->UnitsExp                 = 0

pp_data->cap[4]->UsagePage                    = 0x0001
pp_data->cap[4]->ReportID                     = 0x00
pp_data->cap[4]->BitPosition                  = 0
pp_data->cap[4]->BitSize                      = 16
pp_data->cap[4]->ReportCount                  = 1
pp_data->cap[4]->BytePosition                 = 0x0009
pp_data->cap[4]->BitCount                     = 16
pp_data->cap[4]->BitField                     = 0x02
pp_data->cap[4]->NextBytePosition             = 0x000B
pp_data->cap[4]->LinkCollection               = 0x0003
pp_data->cap[4]->LinkUsagePage                = 0x0001
pp_data->cap[4]->LinkUsage                    = 0x0000
pp_data->cap[4]->IsMultipleItemsForArray      = 0
pp_data->cap[4]->IsButtonCap                  = 0
pp_data->cap[4]->IsPadding                    = 0
pp_data->cap[4]->IsAbsolute                   = 1
pp_data->cap[4]->IsRange                      = 0
pp_data->cap[4]->IsAlias                      = 0
pp_data->cap[4]->IsStringRange                = 0
pp_data->cap[4]->IsDesignatorRange            = 0
pp_data->cap[4]->Reserved1                    = 0x000000
pp_data->cap[4]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[4]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[4]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[4]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[4]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[4]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[4]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[4]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[4]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[4]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[4]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[4]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[4]->NotRange.Usage                        = 0x0032
pp_data->cap[4]->NotRange.Reserved1                    = 0x0032
pp_data->cap[4]->NotRange.StringIndex                  = 0
pp_data->cap[4]->NotRange.Reserved2                    = 0
pp_data->cap[4]->NotRange.DesignatorIndex              = 0
pp_data->cap[4]->NotRange.Reserved3                    = 0
pp_data->cap[4]->NotRange.DataIndex                    = 4
pp_data->cap[4]->NotRange.Reserved4                    = 4
pp_data->cap[4]->NotButton.HasNull                   = 0
pp_data->cap[4]->NotButton.Reserved4                 = 0x000000
pp_data->cap[4]->NotButton.LogicalMin                = 0
pp_data->cap[4]->NotButton.LogicalMax                = -1
pp_data->cap[4]->NotButton.PhysicalMin               = 0
pp_data->cap[4]->NotButton.PhysicalMax               = -1
pp_data->cap[4]->Units                    = 0
pp_data->cap[4]->UnitsExp                 = 0

pp_data->cap[5]->UsagePage                    = 0x0009
pp_data->cap[5]->ReportID                     = 0x00
pp_data->cap[5]->BitPosition                  = 0
pp_data->cap[5]->BitSize                      = 1
pp_data->cap[5]->ReportCount                  = 16
pp_data->cap[5]->BytePosition                 = 0x000B
pp_data->cap[5]->BitCount                     = 16
pp_data->cap[5]->BitField                     = 0x02
pp_data->cap[5]->NextBytePosition             = 0x000D
pp_data->cap[5]->LinkCollection               = 0x0000
pp_data->cap[5]->LinkUsagePage                = 0x0001
pp_data->cap[5]->LinkUsage                    = 0x0005
pp_data->cap[5]->IsMultipleItemsForArray      = 0
pp_data->cap[5]->IsButtonCap                  = 1
pp_data->cap[5]->IsPadding                    = 0
pp_data->cap[5]->IsAbsolute                   = 1
pp_data->cap[5]->IsRange                      = 1
pp_data->cap[5]->IsAlias                      = 0
pp_data->cap[5]->IsStringRange                = 0
pp_data->cap[5]->IsDesignatorRange            = 0
pp_data->cap[5]->Reserved1                    = 0x000000
pp_data->cap[5]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[5]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[5]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[5]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[5]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[5]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[5]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[5]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[5]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[5]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[5]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[5]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[5]->Range.UsageMin                     = 0x0001
pp_data->cap[5]->Range.UsageMax                     = 0x0010
pp_data->cap[5]->Range.StringMin                    = 0
pp_data->cap[5]->Range.StringMax                    = 0
pp_data->cap[5]->Range.DesignatorMin                = 0
pp_data->cap[5]->Range.DesignatorMax                = 0
pp_data->cap[5]->Range.DataIndexMin                 = 5
pp_data->cap[5]->Range.DataIndexMax                 = 20
pp_data->cap[5]->Button.LogicalMin                   = 0
pp_data->cap[5]->Button.LogicalMax                   = 0
pp_data->cap[5]->Units                    = 0
pp_data->cap[5]->UnitsExp                 = 0

pp_data->cap[6]->UsagePage                    = 0x0001
pp_data->cap[6]->ReportID                     = 0x00
pp_data->cap[6]->BitPosition                  = 0
pp_data->cap[6]->BitSize                      = 4
pp_data->cap[6]->ReportCount                  = 1
pp_data->cap[6]->BytePosition                 = 0x000D
pp_data->cap[6]->BitCount                     = 4
pp_data->cap[6]->BitField                     = 0x42
pp_data->cap[6]->NextBytePosition             = 0x000E
pp_data->cap[6]->LinkCollection               = 0x0000
pp_data->cap[6]->LinkUsagePage                = 0x0001
pp_data->cap[6]->LinkUsage                    = 0x0005
pp_data->cap[6]->IsMultipleItemsForArray      = 0
pp_data->cap[6]->IsButtonCap                  = 0
pp_data->cap[6]->IsPadding                    = 0
pp_data->cap[6]->IsAbsolute                   = 1
pp_data->cap[6]->IsRange                      = 0
pp_data->cap[6]->IsAlias                      = 0
pp_data->cap[6]->IsStringRange                = 0
pp_data->cap[6]->IsDesignatorRange            = 0
pp_data->cap[6]->Reserved1                    = 0x000000
pp_data->cap[6]->pp_cap->UnknownTokens[0].Token    = 0x00
pp_data->cap[6]->pp_cap->UnknownTokens[0].Reserved = 0x000000
pp_data->cap[6]->pp_cap->UnknownTokens[0].BitField = 0x00000000
pp_data->cap[6]->pp_cap->UnknownTokens[1].Token    = 0x00
pp_data->cap[6]->pp_cap->UnknownTokens[1].Reserved = 0x000000
pp_data->cap[6]->pp_cap->UnknownTokens[1].BitField = 0x00000000
pp_data->cap[6]->pp_cap->UnknownTokens[2].Token    = 0x00
pp_data->cap[6]->pp_cap->UnknownTokens[2].Reserved = 0x000000
pp_data->cap[6]->pp_cap->UnknownTokens[2].BitField = 0x00000000
pp_data->cap[6]->pp_cap->UnknownTokens[3].Token    = 0x00
pp_data->cap[6]->pp_cap->UnknownTokens[3].Reserved = 0x000000
pp_data->cap[6]->pp_cap->UnknownTokens[3].BitField = 0x00000000
pp_data->cap[6]->NotRange.Usage                        = 0x0039
pp_data->cap[6]->NotRange.Reserved1                    = 0x0039
pp_data->cap[6]->NotRange.StringIndex                  = 0
pp_data->cap[6]->NotRange.Reserved2                    = 0
pp_data->cap[6]->NotRange.DesignatorIndex              = 0
pp_data->cap[6]->NotRange.Reserved3                    = 0
pp_data->cap[6]->NotRange.DataIndex                    = 21
pp_data->cap[6]->NotRange.Reserved4                    = 21
pp_data->cap[6]->NotButton.HasNull                   = 1
pp_data->cap[6]->NotButton.Reserved4                 = 0x000000
pp_data->cap[6]->NotButton.LogicalMin                = 1
pp_data->cap[6]->NotButton.LogicalMax                = 8
pp_data->cap[6]->NotButton.PhysicalMin               = 0
pp_data->cap[6]->NotButton.PhysicalMax               = 4155
pp_data->cap[6]->Units                    = 14
pp_data->cap[6]->UnitsExp                 = 0

# Output hid_pp_cap struct:
# Feature hid_pp_cap struct:
# Link Collections:
pp_data->LinkCollectionArray[0]->LinkUsage          = 0x0005
pp_data->LinkCollectionArray[0]->LinkUsagePage      = 0x0001
pp_data->LinkCollectionArray[0]->Parent             = 0
pp_data->LinkCollectionArray[0]->NumberOfChildren   = 3
pp_data->LinkCollectionArray[0]->NextSibling        = 0
pp_data->LinkCollectionArray[0]->FirstChild         = 3
pp_data->LinkCollectionArray[0]->CollectionType     = 1
pp_data->LinkCollectionArray[0]->IsAlias            = 0
pp_data->LinkCollectionArray[0]->Reserved           = 0x00000000
pp_data->LinkCollectionArray[1]->LinkUsage          = 0x0000
pp_data->LinkCollectionArray[1]->LinkUsagePage      = 0x0001
pp_data->LinkCollectionArray[1]->Parent             = 0
pp_data->LinkCollectionArray[1]->NumberOfChildren   = 0
pp_data->LinkCollectionArray[1]->NextSibling        = 0
pp_data->LinkCollectionArray[1]->FirstChild         = 0
pp_data->LinkCollectionArray[1]->CollectionType     = 0
pp_data->LinkCollectionArray[1]->IsAlias            = 0
pp_data->LinkCollectionArray[1]->Reserved           = 0x00000000
pp_data->LinkCollectionArray[2]->LinkUsage          = 0x0000
pp_data->LinkCollectionArray[2]->LinkUsagePage      = 0x0001
pp_data->LinkCollectionArray[2]->Parent             = 0
pp_data->LinkCollectionArray[2]->NumberOfChildren   = 0
pp_data->LinkCollectionArray[2]->NextSibling        = 1
pp_data->LinkCollectionArray[2]->FirstChild         = 0
pp_data->LinkCollectionArray[2]->CollectionType     = 0
pp_data->LinkCollectionArray[2]->IsAlias            = 0
pp_data->LinkCollectionArray[2]->Reserved           = 0x00000000
pp_data->LinkCollectionArray[3]->LinkUsage          = 0x0000
pp_data->LinkCollectionArray[3]->LinkUsagePage      = 0x0001
pp_data->LinkCollectionArray[3]->Parent             = 0
pp_data->LinkCollectionArray[3]->NumberOfChildren   = 0
pp_data->LinkCollectionArray[3]->NextSibling        = 2
pp_data->LinkCollectionArray[3]->FirstChild         = 0
pp_data->LinkCollectionArray[3]->CollectionType     = 0
pp_data->LinkCollectionArray[3]->IsAlias            = 0
pp_data->LinkCollectionArray[3]->Reserved           = 0x00000000

@JoergAtGithub
Copy link
Contributor

Thank you! Now the only missing piece is the real HID report descriptor. You can either gather it using another OS like MacOS or Linux. Or by a Windows tool that captures the HID report descriptor using a kernel driver, like the Shareware USBlyzer.

@jkcmusork
Copy link
Author

Sadly for this particular device, the descriptor presents differently on MacOS (and is only readable wirelessly), and under USBlyzer, there is no HID descriptors present only vendor specific USB descriptions that don't really line up. Presumably all of this is because the devices are not actually HID compliant, but drivers translate to support them.

Here's what I get from my code reading the report descriptor provided on MacOS when connecting via BlueTooth:

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x85, 0x01,        //   Report ID (1)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00,  //     Logical Maximum (65534)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x32,        //     Usage (Z)
0x09, 0x35,        //     Usage (Rz)
0x15, 0x00,        //     Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00,  //     Logical Maximum (65534)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x02,        //   Usage Page (Sim Ctrls)
0x09, 0xC5,        //   Usage (Brake)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
0x95, 0x01,        //   Report Count (1)
0x75, 0x0A,        //   Report Size (10)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x02,        //   Usage Page (Sim Ctrls)
0x09, 0xC4,        //   Usage (Accelerator)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
0x95, 0x01,        //   Report Count (1)
0x75, 0x0A,        //   Report Size (10)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x08,        //   Logical Maximum (8)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x01,  //   Physical Maximum (315)
0x66, 0x14, 0x00,  //   Unit (System: English Rotation, Length: Centimeter)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x35, 0x00,        //   Physical Minimum (0)
0x45, 0x00,        //   Physical Maximum (0)
0x65, 0x00,        //   Unit (None)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x0F,        //   Usage Maximum (0x0F)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x0F,        //   Report Count (15)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x01,        //   Report Size (1)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x0C,        //   Usage Page (Consumer)
0x0A, 0x24, 0x02,  //   Usage (AC Back)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x95, 0x01,        //   Report Count (1)
0x75, 0x01,        //   Report Size (1)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x07,        //   Report Size (7)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x0C,        //   Usage Page (Consumer)
0x09, 0x01,        //   Usage (Consumer Control)
0xA1, 0x01,        //   Collection (Application)
0x0A, 0x81, 0x00,  //     Usage (Assign Selection)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x04,        //     Report Size (4)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x95, 0x01,        //     Report Count (1)
0x75, 0x04,        //     Report Size (4)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x84, 0x00,  //     Usage (Enter Channel)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x04,        //     Report Size (4)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x95, 0x01,        //     Report Count (1)
0x75, 0x04,        //     Report Size (4)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x85, 0x00,  //     Usage (Order Movie)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x99, 0x00,  //     Usage (Media Select Security)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x04,        //     Report Size (4)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x95, 0x01,        //     Report Count (1)
0x75, 0x04,        //     Report Size (4)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x9E, 0x00,  //     Usage (Media Select SAP)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xA1, 0x00,  //     Usage (Once)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xA2, 0x00,  //     Usage (Daily)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xA3, 0x00,  //     Usage (Weekly)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xA4, 0x00,  //     Usage (Monthly)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xB9, 0x00,  //     Usage (Random Play)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xBA, 0x00,  //     Usage (Select Disc)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xBB, 0x00,  //     Usage (Enter Disc)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xBE, 0x00,  //     Usage (Track Normal)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC0, 0x00,  //     Usage (Frame Forward)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC1, 0x00,  //     Usage (Frame Back)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC2, 0x00,  //     Usage (Mark)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC3, 0x00,  //     Usage (Clear Mark)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC4, 0x00,  //     Usage (Repeat From Mark)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC5, 0x00,  //     Usage (Return To Mark)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC6, 0x00,  //     Usage (Search Mark Forward)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC7, 0x00,  //     Usage (Search Mark Backwards)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0xC8, 0x00,  //     Usage (Counter Reset)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x0C,        //   Usage Page (Consumer)
0x09, 0x01,        //   Usage (Consumer Control)
0x85, 0x02,        //   Report ID (2)
0xA1, 0x01,        //   Collection (Application)
0x05, 0x0C,        //     Usage Page (Consumer)
0x0A, 0x23, 0x02,  //     Usage (AC Home)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x95, 0x01,        //     Report Count (1)
0x75, 0x01,        //     Report Size (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x75, 0x07,        //     Report Size (7)
0x95, 0x01,        //     Report Count (1)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x0F,        //   Usage Page (PID Page)
0x09, 0x21,        //   Usage (0x21)
0x85, 0x03,        //   Report ID (3)
0xA1, 0x02,        //   Collection (Logical)
0x09, 0x97,        //     Usage (0x97)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x75, 0x04,        //     Report Size (4)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x75, 0x04,        //     Report Size (4)
0x95, 0x01,        //     Report Count (1)
0x91, 0x03,        //     Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x70,        //     Usage (0x70)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x64,        //     Logical Maximum (100)
0x75, 0x08,        //     Report Size (8)
0x95, 0x04,        //     Report Count (4)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x50,        //     Usage (0x50)
0x66, 0x01, 0x10,  //     Unit (System: SI Linear, Time: Seconds)
0x55, 0x0E,        //     Unit Exponent (-2)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0xA7,        //     Usage (0xA7)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x65, 0x00,        //     Unit (None)
0x55, 0x00,        //     Unit Exponent (0)
0x09, 0x7C,        //     Usage (0x7C)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              //   End Collection
0x05, 0x06,        //   Usage Page (Generic Dev Ctrls)
0x09, 0x20,        //   Usage (Battery Strength)
0x85, 0x04,        //   Report ID (4)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x75, 0x08,        //   Report Size (8)
0x95, 0x01,        //   Report Count (1)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0x0F,  //   Usage Page (Reserved 0x0F00)

// 681 bytes

@JoergAtGithub
Copy link
Contributor

USBlyzer might only be able to read descriptors from USB HID devices.
But MacOS delivers always the unmodified original HID report descriptor, which is the reference for the reconstruction.

@JoergAtGithub
Copy link
Contributor

The Report Descriptor from MacOS doesn't match to the data from Windows. It contains ReportIDs from 1...4, while the data from Windows don't contain a ReportID.

@jkcmusork
Copy link
Author

Correct. The windows driver is returning something totally different, AFAICT.

FWIW, The windows driver presents the right joystick axes as Rx and Ry, whereas MacOS presents this as Z and Rz. The windows driver presents the left and right trigger as a combined 16bit Z value, whereas MacOS presents this as individuated 10bit brake and acceleration values, each with 6 bits of padding.

The following files are allegedly dumps the report descriptors contained in and presumably provided by xusb22.sys:
https://gist.github.com/DJm00n/07e1b7bb21643725e53b16f45e0e7022

The preparsed data and reconstructed report descriptor on windows looks awfully close to the following report descriptor, which is what in this circumstance we would hope to reconstruct:
https://gist.github.com/DJm00n/07e1b7bb21643725e53b16f45e0e7022#file-xusbhidgamepaddescriptor2-txt

Side note: it looks like via the registry, one force it to provide the left and right triggers separately that would correspond to this report descriptor:
https://gist.github.com/DJm00n/07e1b7bb21643725e53b16f45e0e7022#file-xusbhidgamepaddescriptor1-txt

@jkcmusork
Copy link
Author

There are some differences, namely in the count on the button page, which has 16 rather than 10 buttons, so likely this uses a different version of the driver. I'll keep poking around on my system to see if I can find it embedded in the appropriate driver.

However, upon further review, I think the collection sorting change submitted is appropriate, but the final padding change is not quite right. The 4bits of padding looks more accurate, and perhaps the correct fix needs to add one to the final bit that we are inserting in the main_item_list as well...i.e.

rd_insert_main_item_node(last_bit_position[rt_idx][reportid_idx] + 1, last_bit_position[rt_idx][reportid_idx] + 1 + padding,...

instead of

rd_insert_main_item_node(last_bit_position[rt_idx][reportid_idx] + 1, last_bit_position[rt_idx][reportid_idx] + padding,...

@jkcmusork
Copy link
Author

jkcmusork commented Feb 3, 2023

Okay, I searched through xinputhid.sys, which AFAICT is the driver being used for this device, and I found the following report description embedded in it. As best as I can tell, this is what the driver is providing as the report description we are looking at above.

The two main differences are both in the final bit padding. 1) the additional off by one issue I mentioned in my previous note (correction now checked in), and 2) an extra 2 bytes of padding.

I hope this is helpful to confirm the recommended changes. Let me know if you spot anything amiss, or if you'd like to schedule time to chat via some other medium. Apologies for a bit of noise here as I worked to figure out the windows driver details.

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0xFF,  //     Logical Maximum (-1)
0x35, 0x00,        //     Physical Minimum (0)
0x46, 0xFF, 0xFF,  //     Physical Maximum (-1)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x33,        //     Usage (Rx)
0x09, 0x34,        //     Usage (Ry)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0xFF,  //     Logical Maximum (-1)
0x35, 0x00,        //     Physical Minimum (0)
0x46, 0xFF, 0xFF,  //     Physical Maximum (-1)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x32,        //     Usage (Z)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0xFF,  //     Logical Maximum (-1)
0x35, 0x00,        //     Physical Minimum (0)
0x46, 0xFF, 0xFF,  //     Physical Maximum (-1)
0x95, 0x01,        //     Report Count (1)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x10,        //   Usage Maximum (0x10)
0x95, 0x10,        //   Report Count (16)
0x75, 0x01,        //   Report Size (1)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x08,        //   Logical Maximum (8)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x10,  //   Physical Maximum (4155)
0x66, 0x0E, 0x00,  //   Unit (None)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08,        //   Report Size (8)
0x95, 0x02,        //   Report Count (2)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

// 120 bytes

@jkcmusork
Copy link
Author

For completeness, below is the reconstructed HID descriptor with the latest fix:

0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x35, 0x00,        //     Physical Minimum (0)
0x45, 0xFF,        //     Physical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x33,        //     Usage (Rx)
0x09, 0x34,        //     Usage (Ry)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x00,        //   Usage (Undefined)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x32,        //     Usage (Z)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0xFF,        //     Logical Maximum (-1)
0x75, 0x10,        //     Report Size (16)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x10,        //   Usage Maximum (0x10)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x10,        //   Report Count (16)
0x45, 0x00,        //   Physical Maximum (0)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x08,        //   Logical Maximum (8)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x10,  //   Physical Maximum (4155)
0x65, 0x0E,        //   Unit (None)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

// 111 bytes

@Youw
Copy link
Member

Youw commented Feb 3, 2023

Sadly, I don't really undestand this implementation, so it is mostly for you @JoergAtGithub to check the PR.

@mcuee mcuee added the Windows Related to Windows backend label Feb 4, 2023
… the size of the padding is considered anyway
@JoergAtGithub
Copy link
Contributor

I created jkcmusork#1 with a correction for the padding code.

Corrected padding of by one issue - no change on output
@JoergAtGithub
Copy link
Contributor

These changes in this PR are correct:

  • The order of sub-collections had indeed a bug, but until now we had no device with such a descriptor (Therefore I added this device to the testcase Added testcase for: Controller (Xbox One For Windows) jkcmusork/hidapi#2 )
  • The additional nullptr is a correct safety measure - could be also an assert statement, as this must never happen
  • The padding code always generated the correct padding, but in the internal variables, was a shift by one bit, which compensated it self

@Youw
Copy link
Member

Youw commented Feb 19, 2023

@jkcmusork, lets merge jkcmusork#2 and rebase to latest get-descriptor branch.
If the CI is happy after update - we're good to merge.

@JoergAtGithub
Copy link
Contributor

JoergAtGithub commented Feb 21, 2023

@Youw You can merge this PR as it is. Than I will open a PR, which adds the missing unit test, to be merged in libusb:get-descriptor afterwards.

@Youw Youw merged commit 32ea0a2 into libusb:get-descriptor Feb 22, 2023
@JoergAtGithub JoergAtGithub mentioned this pull request Feb 22, 2023
Youw pushed a commit that referenced this pull request Feb 22, 2023
Add a Testcase, using the data of the "Xbox One For Windows" controller provided: #501 (comment)
@jkcmusork
Copy link
Author

Perfect. Thank you both!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Windows Related to Windows backend
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants