diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index 10485e33d6..cbcc2402c2 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -415,6 +415,79 @@ public void Devices_CanParseHIDDescriptor_WithSignedLogicalMinAndMaxValues(byte } } + // Regression test for https://jira.unity3d.com/browse/UUM-143659. + // + // HID devices whose report descriptor declares a hat switch with Report Size 8 + // (rather than the standard 4 bits, e.g. ESP32-BLE-Gamepad) caused an + // IndexOutOfRangeException inside InputDeviceBuilder.InsertControlBitRangeNode + // while constructing the control tree. InputManager caught the exception and + // logged "Could not create a device for ...", so the device never showed up. + [Test] + [Category("Devices")] + [Description("Regression test for case UUM-143659")] + public void Devices_CanCreateGenericHID_FromGamepadWithEightBitHatSwitch() + { + // Minimal Gamepad collection that contains a hat switch whose Report Size + // is declared as 8 bits (instead of the usual 4). Modelled on the + // descriptor reported in UUM-143659 (ESP32-BLE-Gamepad library). + var reportDescriptor = new byte[] + { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + + // Two 8-bit axes so the control tree has more than one leaf. + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs) + + // Hat switch with the problematic 8-bit Report Size. + 0xA1, 0x00, // Collection (Physical) + 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) + 0x65, 0x12, // Unit (SI Rotation, Length: Centimeter) + 0x75, 0x08, // Report Size (8) <-- bug: 8 instead of 4 + 0x95, 0x01, // Report Count (1) + 0x81, 0x42, // Input (Data,Var,Abs,Null State) + 0xC0, // End Collection + + 0xC0, // End Collection + }; + + var deviceId = runtime.AllocateDeviceId(); + SetDeviceCommandCallbackToReturnReportDescriptor(deviceId, reportDescriptor); + + runtime.ReportNewInputDevice( + new InputDeviceDescription + { + interfaceName = HID.kHIDInterface, + manufacturer = "TestVendor", + product = "TestEightBitHatGamepad", + capabilities = new HID.HIDDeviceDescriptor + { + vendorId = 0xE502, + productId = 0xBBAB + }.ToJson() + }.ToJson(), deviceId); + + // Device construction runs through InputDeviceBuilder. Before the fix + // this raises IndexOutOfRangeException in InsertControlBitRangeNode, + // which InputManager catches and turns into a LogType.Error + // ("Could not create a device for ...") — that error fails the test. + InputSystem.Update(); + + Assert.That(InputSystem.GetDeviceById(deviceId), Is.Not.Null, + "Device should be created without throwing IndexOutOfRangeException."); + } + [Test] [Category("Devices")] public void Devices_CanCreateGenericHID_FromDeviceWithParsedReportDescriptor() diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index f64be23383..4dc383736f 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed InputSystem.onAnyButtonPress fails to trigger when the device receives a touch [UUM-137930](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-137930). - Fixed an incorrect ArraysHelper.HaveDuplicateReferences implementation that didn't use its arguments right [ISXB-1792] (https://github.com/Unity-Technologies/InputSystem/pull/2376) - Fixed `InputAction.IsPressed`, `WasPressedThisFrame`, and `WasReleasedThisFrame` using a `ButtonControl`'s `pressPoint` when a binding also had an explicit `PressInteraction` with its own `pressPoint`, which could make those APIs disagree with the interaction's press and release behavior. Action-level press APIs now follow the interaction threshold when both are set explicitly. +- Fixed `IndexOutOfRangeException` in `InputDeviceBuilder` when connecting an HID gamepad whose report descriptor declares a hat switch with Report Size 8 (e.g. ESP32-BLE-Gamepad). The HID layer now anchors the hat's directional sub-controls to the hat's own byte instead of letting the layout system auto-allocate a fresh byte for each [UUM-143659](https://jira.unity3d.com/browse/UUM-143659). ### Changed - Action-level `IsPressed`, `WasPressedThisFrame`, and `WasReleasedThisFrame` for bindings to `Vector2Control` / `StickControl` no longer consult a per-control `pressPoint` on the vector (that field was removed). Use a `Press` interaction to set a custom threshold, or rely on `defaultButtonPressPoint`. diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/HID/HID.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/HID/HID.cs index 1be75e6844..961ff46811 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/HID/HID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/HID/HID.cs @@ -871,12 +871,21 @@ internal void AddChildControls(ref HIDElementDescriptor element, string controlN ////REVIEW: this probably only works with hatswitches that have their null value at logicalMax+1 + // All four sub-controls overlap the hat itself — they read the same value + // and only differ in the DiscreteButton range parameters above. + // WithByteOffset(0) anchors them to the hat parent's byte (FinalizeControlHierarchy + // adds the parent's byteOffset afterwards). Without it, the layout system + // auto-allocates a fresh byte for each — fine for 4-bit hats (bit-addressing, + // they pack into one byte) but for 8-bit hats each sub-control claims its own + // byte, pushes them past the device's report size, and InputDeviceBuilder's + // bit-range tree builder infinite-loops on the dangling controls. See UUM-143659. builder.AddControl(controlName + "/up") .WithFormat(InputStateBlock.FormatBit) .WithLayout("DiscreteButton") .WithParameters(string.Format(CultureInfo.InvariantCulture, "minValue={0},maxValue={1},nullValue={2},wrapAtValue={3}", logicalMax, logicalMin + 1, nullValue.ToString(), logicalMax)) + .WithByteOffset(0) .WithBitOffset((uint)element.reportOffsetInBits % 8) .WithSizeInBits((uint)reportSizeInBits); @@ -886,6 +895,7 @@ internal void AddChildControls(ref HIDElementDescriptor element, string controlN .WithParameters(string.Format(CultureInfo.InvariantCulture, "minValue={0},maxValue={1}", logicalMin + 1, logicalMin + 3)) + .WithByteOffset(0) .WithBitOffset((uint)element.reportOffsetInBits % 8) .WithSizeInBits((uint)reportSizeInBits); @@ -895,6 +905,7 @@ internal void AddChildControls(ref HIDElementDescriptor element, string controlN .WithParameters(string.Format(CultureInfo.InvariantCulture, "minValue={0},maxValue={1}", logicalMin + 3, logicalMin + 5)) + .WithByteOffset(0) .WithBitOffset((uint)element.reportOffsetInBits % 8) .WithSizeInBits((uint)reportSizeInBits); @@ -904,6 +915,7 @@ internal void AddChildControls(ref HIDElementDescriptor element, string controlN .WithParameters(string.Format(CultureInfo.InvariantCulture, "minValue={0},maxValue={1}", logicalMin + 5, logicalMin + 7)) + .WithByteOffset(0) .WithBitOffset((uint)element.reportOffsetInBits % 8) .WithSizeInBits((uint)reportSizeInBits); }