Games like Risk of Rain 2 need this with certain controllers.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Andrew Eikum helped me test this out with a PS4 Controller on Windows 10. Using the test here, I took all the input reports from the Windows log and sent them through this function in a simulation, to compare the outputs with the dumped log from Windows. It was the same, so same input resulted in same output.
dlls/hid/hid.spec | 2 +- dlls/hid/hidp.c | 65 +++++++++++++++++++++++++++++++++++++++++ dlls/hid/tests/device.c | 37 +++++++++++++++++++---- include/ddk/hidpi.h | 1 + 4 files changed, 99 insertions(+), 6 deletions(-)
diff --git a/dlls/hid/hid.spec b/dlls/hid/hid.spec index b8e29fe..98508a4 100644 --- a/dlls/hid/hid.spec +++ b/dlls/hid/hid.spec @@ -27,7 +27,7 @@ @ stdcall HidP_GetSpecificButtonCaps(long long long long ptr ptr ptr) @ stdcall HidP_GetSpecificValueCaps(long long long long ptr ptr ptr) @ stdcall HidP_GetUsageValue(long long long long ptr ptr ptr long) -@ stub HidP_GetUsageValueArray +@ stdcall HidP_GetUsageValueArray(long long long long ptr long ptr ptr long) @ stdcall HidP_GetUsages(long long long ptr ptr ptr ptr long) @ stdcall HidP_GetUsagesEx(long long ptr ptr ptr ptr long) @ stdcall HidP_GetValueCaps(long ptr ptr ptr) diff --git a/dlls/hid/hidp.c b/dlls/hid/hidp.c index 817e021..c4d1626 100644 --- a/dlls/hid/hidp.c +++ b/dlls/hid/hidp.c @@ -117,6 +117,46 @@ static NTSTATUS set_report_data(BYTE *report, INT reportLength, INT startBit, IN return HIDP_STATUS_SUCCESS; }
+static NTSTATUS get_report_data_array(BYTE *report, UINT reportLength, UINT startBit, UINT elemSize, + UINT numElements, PCHAR values, UINT valuesSize) +{ + BYTE byte, *end, *p = report + startBit / 8; + ULONG size = elemSize * numElements; + ULONG m, bit_index = startBit % 8; + BYTE *data = (BYTE*)values; + + if ((startBit + size) / 8 > reportLength) + return HIDP_STATUS_INVALID_REPORT_LENGTH; + + if (valuesSize < (size + 7) / 8) + return HIDP_STATUS_BUFFER_TOO_SMALL; + + end = report + (startBit + size + 7) / 8; + + data--; + byte = *p++; + while (p != end) + { + *(++data) = byte >> bit_index; + byte = *p++; + *data |= byte << (8 - bit_index); + } + + /* Handle the end and mask out bits beyond */ + m = (startBit + size) % 8; + m = m ? m : 8; + + if (m > bit_index) + *(++data) = (byte >> bit_index) & ((1 << (m - bit_index)) - 1); + else + *data &= (1 << (m + 8 - bit_index)) - 1; + + if (++data < (BYTE*)values + valuesSize) + memset(data, 0, (BYTE*)values + valuesSize - data); + + return HIDP_STATUS_SUCCESS; +} +
NTSTATUS WINAPI HidP_GetButtonCaps(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData) @@ -325,6 +365,31 @@ NTSTATUS WINAPI HidP_GetUsageValue(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, }
+NTSTATUS WINAPI HidP_GetUsageValueArray(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, + USAGE Usage, PCHAR UsageValue, USHORT UsageValueByteLength, + PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength) +{ + WINE_HID_ELEMENT element; + NTSTATUS rc; + + TRACE("(%i, %x, %i, %i, %p, %u, %p, %p, %i)\n", ReportType, UsagePage, LinkCollection, Usage, UsageValue, + UsageValueByteLength, PreparsedData, Report, ReportLength); + + rc = find_usage(ReportType, UsagePage, LinkCollection, Usage, PreparsedData, Report, ValueElement, &element); + + if (rc == HIDP_STATUS_SUCCESS) + { + if (element.caps.value.IsRange || element.caps.value.ReportCount <= 1 || !element.bitCount) + return HIDP_STATUS_NOT_VALUE_ARRAY; + + return get_report_data_array((BYTE*)Report, ReportLength, element.valueStartBit, element.bitCount, + element.caps.value.ReportCount, UsageValue, UsageValueByteLength); + } + + return rc; +} + + NTSTATUS WINAPI HidP_GetUsages(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, PUSAGE UsageList, PULONG UsageLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength) diff --git a/dlls/hid/tests/device.c b/dlls/hid/tests/device.c index 24c3077..3dd22b7 100644 --- a/dlls/hid/tests/device.c +++ b/dlls/hid/tests/device.c @@ -253,11 +253,38 @@ static void process_data(HIDP_CAPS Caps, PHIDP_PREPARSED_DATA ppd, CHAR *data, D trace("\tValues:\n"); for (i = 0; i < length; i++) { - status = HidP_GetUsageValue(HidP_Input, values[i].UsagePage, 0, - values[i].Range.UsageMin, &value, ppd, data, data_length); - ok(status == HIDP_STATUS_SUCCESS, "Failed to get value [%i,%i] (%x)\n", - values[i].UsagePage, values[i].Range.UsageMin, status); - trace("[%02x, %02x]: %u\n",values[i].UsagePage, values[i].Range.UsageMin, value); + ok(values[i].ReportCount, "Zero ReportCount for [%i,%i]\n", values[i].UsagePage, values[i].NotRange.Usage); + if (values[i].IsRange || values[i].ReportCount <= 1) + { + status = HidP_GetUsageValue(HidP_Input, values[i].UsagePage, 0, + values[i].Range.UsageMin, &value, ppd, data, data_length); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get value [%i,%i] (%x)\n", + values[i].UsagePage, values[i].Range.UsageMin, status); + trace("[%02x, %02x]: %u\n", values[i].UsagePage, values[i].Range.UsageMin, value); + } + else + { + USHORT k, array_size = (values[i].BitSize * values[i].ReportCount + 7) / 8; + PCHAR array = HeapAlloc(GetProcessHeap(), 0, array_size); + char *dump = HeapAlloc(GetProcessHeap(), 0, array_size * 3 + 1); + + status = HidP_GetUsageValueArray(HidP_Input, values[i].UsagePage, 0, + values[i].NotRange.Usage, array, array_size, ppd, data, data_length); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get value array [%i,%i] (%x)\n", + values[i].UsagePage, values[i].NotRange.Usage, status); + dump[0] = 0; + for (k = 0; k < array_size; k++) + { + char bytestr[5]; + sprintf(bytestr, " %02x", (BYTE)array[k]); + strcat(dump, bytestr); + } + trace("[%02x, %02x] element bit size %u num elements %u:%s\n", values[i].UsagePage, + values[i].NotRange.Usage, values[i].BitSize, values[i].ReportCount, dump); + + HeapFree(GetProcessHeap(), 0, dump); + HeapFree(GetProcessHeap(), 0, array); + } }
HeapFree(GetProcessHeap(), 0, values); diff --git a/include/ddk/hidpi.h b/include/ddk/hidpi.h index 51d61ea..fb497f3 100644 --- a/include/ddk/hidpi.h +++ b/include/ddk/hidpi.h @@ -195,6 +195,7 @@ NTSTATUS WINAPI HidP_GetButtonCaps(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAP NTSTATUS WINAPI HidP_GetCaps(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities); NTSTATUS WINAPI HidP_GetUsages(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, PUSAGE UsageList, PULONG UsageLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength); NTSTATUS WINAPI HidP_GetUsageValue(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, USAGE Usage, PULONG UsageValue, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength); +NTSTATUS WINAPI HidP_GetUsageValueArray(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, USAGE Usage, PCHAR UsageValue, USHORT UsageValueByteLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength); NTSTATUS WINAPI HidP_GetValueCaps(HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData); NTSTATUS WINAPI HidP_InitializeReportForID(HIDP_REPORT_TYPE ReportType, UCHAR ReportID, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength); ULONG WINAPI HidP_MaxUsageListLength(HIDP_REPORT_TYPE ReportType, USAGE UsagePage, PHIDP_PREPARSED_DATA PreparsedData);
Hi Gabriel,
On 26 Feb 2020, at 14:03, Gabriel Ivăncescu gabrielopcode@gmail.com wrote:
if (element.caps.value.IsRange || element.caps.value.ReportCount <= 1 || !element.bitCount)
return HIDP_STATUS_NOT_VALUE_ARRAY;
Do you know what happens when usages range and reports count doesn’t match?
I’m asking about a HID descriptor that looks like that: USAGE_MINIMUM 1 USAGE_MAXIMUM 2 REPORT_SIZE 8 REPORT_COUNT 20 INPUT … I wonder if the remaining reports are used as padding or if USAGE 2 is a value array.
Thanks, Piotr
Hi Piotr,
On 27/02/2020 12:01, Piotr Caban wrote:
Hi Gabriel,
On 26 Feb 2020, at 14:03, Gabriel Ivăncescu gabrielopcode@gmail.com wrote:
if (element.caps.value.IsRange || element.caps.value.ReportCount <= 1 || !element.bitCount)
return HIDP_STATUS_NOT_VALUE_ARRAY;
Do you know what happens when usages range and reports count doesn’t match?
I’m asking about a HID descriptor that looks like that: USAGE_MINIMUM 1 USAGE_MAXIMUM 2 REPORT_SIZE 8 REPORT_COUNT 20 INPUT … I wonder if the remaining reports are used as padding or if USAGE 2 is a value array.
Thanks, Piotr
As far as I understand from MSDN, if IsRange is TRUE, then it cannot be a value array at all. IsRange has to be FALSE and then ReportCount has an overloaded meaning: the number of elements in the array.
Unfortunately I haven't been able to test such corner cases because no controller reported such values (to give an error). We'd need some virtual device driver to be able to see what Windows does in these cases. So I had to go by what MSDN states and use the error codes appropriately.
Thanks, Gabriel
On 2/27/20 2:24 PM, Gabriel Ivăncescu wrote:
Unfortunately I haven't been able to test such corner cases because no controller reported such values (to give an error). We'd need some virtual device driver to be able to see what Windows does in these cases. So I had to go by what MSDN states and use the error codes appropriately.
I have tested it by modifying https://github.com/djpnewton/vmulti project. The results are matching with your implementation.
Thanks, Piotr