Game Controller Collective Wiki
Advertisement


HID Interface[]

Report Summaries[]

USB[]

ReportID Size Type Note
0x01 1 63 Input Get Controller State
0x02 2 47 Output Set Controller State
0x05 5 40 Feature Get Calibration
0x08 8 47 Feature Get bluetooth control (please document)
0x09 9 19 Feature Get Controller and Host MAC
0x0A 10 26 Feature Set bluetooth pairing (please document)
0x20 32 63 Feature Get Controller Version/Date (Firmware Info)
0x21 33 4 Feature Set audio control (please document)
0x22 34 63 Feature Get Hardware Info
0x80 128 63 Feature Set test command (please document)
0x81 129 63 Feature Get test result (please document)
0x82 130 9 Feature Set calibration command (please document)
0x83 131 63 Feature Get calibration data (please document)
0x84 132 63 Feature Set individual data (please document)
0x85 133 2 Feature Get individual data result (please document)
0xA0 160 1 Feature Set DFU enable (please document)
0xE0 224 63 Feature Get system profile (please document)
0xF0 240 63 Feature Flash command (please document)
0xF1 241 63 Feature Get flash cmd status (please document)
0xF2 242 15 Feature
0xF4 244 63 Feature User update command (please document)
0xF5 245 3 Feature User get update status (please document)

Bluetooth[]

ReportID Size Type Note
0x01 1 62 Input Get Controller State (simplified)
0x31 49 77 Input Get Controller State
0x31 49 77 Output Set Controller State or Audio (Audio theoretical)
0x32 50 141 Output Set Controller State and/or Audio (unconfirmed)
0x33 51 205 Output Set Controller State and/or Audio (unconfirmed)
0x34 52 269 Output Set Controller State and/or Audio (unconfirmed)
0x35 53 333 Output Set Controller State and/or Audio (unconfirmed)
0x36 54 397 Output Set Controller State and/or Audio (unconfirmed)
0x37 55 461 Output Set Controller State and/or Audio (unconfirmed)
0x38 56 525 Output Set Controller State and/or Audio (unconfirmed)
0x39 57 546 Output Set Controller State and/or Audio (unconfirmed)
0x05 5 40 Feature Get Calibration
0x08 8 47 Feature
0x09 9 19 Feature Get Controller and Host MAC
0x20 32 63 Feature Get Controller Version/Date (Firmware Info)
0x22 34 63 Feature Get Hardware Info
0x80 128 63 Feature
0x81 129 63 Feature
0x82 130 9 Feature
0x83 131 63 Feature
0xF0 240 63 Feature
0xF1 241 63 Feature
0xF2 242 15 Feature

Descriptor[]

USB[]

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x85, 0x01,        //   Report ID (1)
0x09, 0x30,        //   Usage (X)
0x09, 0x31,        //   Usage (Y)
0x09, 0x32,        //   Usage (Z)
0x09, 0x35,        //   Usage (Rz)
0x09, 0x33,        //   Usage (Rx)
0x09, 0x34,        //   Usage (Ry)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x75, 0x08,        //   Report Size (8)
0x95, 0x06,        //   Report Count (6)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF,  //   Usage Page (Vendor Defined 0xFF00)
0x09, 0x20,        //   Usage (0x20)
0x95, 0x01,        //   Report Count (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, 0x00,        //   Logical Minimum (0)
0x25, 0x07,        //   Logical Maximum (7)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x01,  //   Physical Maximum (315)
0x65, 0x14,        //   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)
0x65, 0x00,        //   Unit (None)
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)
0x06, 0x00, 0xFF,  //   Usage Page (Vendor Defined 0xFF00)
0x09, 0x21,        //   Usage (0x21)
0x95, 0x0D,        //   Report Count (13)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF,  //   Usage Page (Vendor Defined 0xFF00)
0x09, 0x22,        //   Usage (0x22)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x75, 0x08,        //   Report Size (8)
0x95, 0x34,        //   Report Count (52)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0x02,        //   Report ID (2)
0x09, 0x23,        //   Usage (0x23)
0x95, 0x2F,        //   Report Count (47)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x05,        //   Report ID (5)
0x09, 0x33,        //   Usage (0x33)
0x95, 0x28,        //   Report Count (40)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x08,        //   Report ID (8)
0x09, 0x34,        //   Usage (0x34)
0x95, 0x2F,        //   Report Count (47)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x09,        //   Report ID (9)
0x09, 0x24,        //   Usage (0x24)
0x95, 0x13,        //   Report Count (19)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x0A,        //   Report ID (10)
0x09, 0x25,        //   Usage (0x25)
0x95, 0x1A,        //   Report Count (26)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x20,        //   Report ID (32)
0x09, 0x26,        //   Usage (0x26)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x21,        //   Report ID (33)
0x09, 0x27,        //   Usage (0x27)
0x95, 0x04,        //   Report Count (4)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x22,        //   Report ID (34)
0x09, 0x40,        //   Usage (0x40)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x80,        //   Report ID (128)
0x09, 0x28,        //   Usage (0x28)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x81,        //   Report ID (129)
0x09, 0x29,        //   Usage (0x29)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x82,        //   Report ID (130)
0x09, 0x2A,        //   Usage (0x2A)
0x95, 0x09,        //   Report Count (9)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x83,        //   Report ID (131)
0x09, 0x2B,        //   Usage (0x2B)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x84,        //   Report ID (132)
0x09, 0x2C,        //   Usage (0x2C)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x85,        //   Report ID (133)
0x09, 0x2D,        //   Usage (0x2D)
0x95, 0x02,        //   Report Count (2)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xA0,        //   Report ID (160)
0x09, 0x2E,        //   Usage (0x2E)
0x95, 0x01,        //   Report Count (1)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xE0,        //   Report ID (224)
0x09, 0x2F,        //   Usage (0x2F)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF0,        //   Report ID (240)
0x09, 0x30,        //   Usage (0x30)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF1,        //   Report ID (241)
0x09, 0x31,        //   Usage (0x31)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF2,        //   Report ID (242)
0x09, 0x32,        //   Usage (0x32)
0x95, 0x0F,        //   Report Count (15)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF4,        //   Report ID (244)
0x09, 0x35,        //   Usage (0x35)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF5,        //   Report ID (245)
0x09, 0x36,        //   Usage (0x36)
0x95, 0x03,        //   Report Count (3)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              // End Collection

// 273 bytes

BT[]

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x85, 0x01,        //   Report ID (1)
0x09, 0x30,        //   Usage (X)
0x09, 0x31,        //   Usage (Y)
0x09, 0x32,        //   Usage (Z)
0x09, 0x35,        //   Usage (Rz)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x75, 0x08,        //   Report Size (8)
0x95, 0x04,        //   Report Count (4)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x07,        //   Logical Maximum (7)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x01,  //   Physical Maximum (315)
0x65, 0x14,        //   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)
0x65, 0x00,        //   Unit (None)
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x0E,        //   Usage Maximum (0x0E)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x0E,        //   Report Count (14)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x33,        //   Usage (Rx)
0x09, 0x34,        //   Usage (Ry)
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,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF,  //   Usage Page (Vendor Defined 0xFF00)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x75, 0x08,        //   Report Size (8)
0x95, 0x4D,        //   Report Count (77)
0x85, 0x31,        //   Report ID (49)
0x09, 0x31,        //   Usage (0x31)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x3B,        //   Usage (0x3B)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0x32,        //   Report ID (50)
0x09, 0x32,        //   Usage (0x32)
0x95, 0x8D,        //   Report Count (141)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x33,        //   Report ID (51)
0x09, 0x33,        //   Usage (0x33)
0x95, 0xCD,        //   Report Count (205)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x34,        //   Report ID (52)
0x09, 0x34,        //   Usage (0x34)
0x96, 0x0D, 0x01,  //   Report Count (269)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x35,        //   Report ID (53)
0x09, 0x35,        //   Usage (0x35)
0x96, 0x4D, 0x01,  //   Report Count (333)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x36,        //   Report ID (54)
0x09, 0x36,        //   Usage (0x36)
0x96, 0x8D, 0x01,  //   Report Count (397)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x37,        //   Report ID (55)
0x09, 0x37,        //   Usage (0x37)
0x96, 0xCD, 0x01,  //   Report Count (461)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x38,        //   Report ID (56)
0x09, 0x38,        //   Usage (0x38)
0x96, 0x0D, 0x02,  //   Report Count (525)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x39,        //   Report ID (57)
0x09, 0x39,        //   Usage (0x39)
0x96, 0x22, 0x02,  //   Report Count (546)
0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x06, 0x80, 0xFF,  //   Usage Page (Vendor Defined 0xFF80)
0x85, 0x05,        //   Report ID (5)
0x09, 0x33,        //   Usage (0x33)
0x95, 0x28,        //   Report Count (40)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x08,        //   Report ID (8)
0x09, 0x34,        //   Usage (0x34)
0x95, 0x2F,        //   Report Count (47)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x09,        //   Report ID (9)
0x09, 0x24,        //   Usage (0x24)
0x95, 0x13,        //   Report Count (19)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x20,        //   Report ID (32)
0x09, 0x26,        //   Usage (0x26)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x22,        //   Report ID (34)
0x09, 0x40,        //   Usage (0x40)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x80,        //   Report ID (128)
0x09, 0x28,        //   Usage (0x28)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x81,        //   Report ID (129)
0x09, 0x29,        //   Usage (0x29)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x82,        //   Report ID (130)
0x09, 0x2A,        //   Usage (0x2A)
0x95, 0x09,        //   Report Count (9)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0x83,        //   Report ID (131)
0x09, 0x2B,        //   Usage (0x2B)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF1,        //   Report ID (241)
0x09, 0x31,        //   Usage (0x31)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF2,        //   Report ID (242)
0x09, 0x32,        //   Usage (0x32)
0x95, 0x0F,        //   Report Count (15)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF0,        //   Report ID (240)
0x09, 0x30,        //   Usage (0x30)
0x95, 0x3F,        //   Report Count (63)
0xB1, 0x02,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              // End Collection

// 279 bytes

Data Structures[]

Common Structures[]

// C++11 allows setting enum base type. If enum sizes cannot be assured please use the indicated base types instead of the enum types.
enum Direction : uint8_t {
    North = 0,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest,
    None = 8
};

enum PowerState : uint8_t {
    Discharging         = 0x00, // Use PowerPercent
    Charging            = 0x01, // Use PowerPercent
    Complete            = 0x02, // PowerPercent not valid? assume 100%?
    AbnormalVoltage     = 0x0A, // PowerPercent not valid?
    AbnormalTemperature = 0x0B, // PowerPercent not valid?
    ChargingError       = 0x0F  // PowerPercent not valid?
};

enum MuteLight : uint8_t {
    Off = 0,
    On,
    Breathing,
    DoNothing, // literally nothing, this input is ignored,
               // though it might be a faster blink in other versions
    NoAction4,
    NoAction5,
    NoAction6,
    NoAction7= 7
};

enum LightBrightness : uint8_t {
    Bright = 0,
    Mid,
    Dim,
    NoAction3,
    NoAction4,
    NoAction5,
    NoAction6,
    NoAction7= 7
};

enum LightFadeAnimation : uint8_t {
    Nothing = 0,
    FadeIn, // from black to blue
    FadeOut // from blue to black
};

template<int N> struct BTCRC {
    uint8_t[N-4] Buff;
    uint32_t CRC;
};

FFB Trigger Effect Factories[]

Code hidden due to length: expand to view code in place. See: Github Gist for latest version of this code.

/*
 * MIT License
 * 
 * Copyright (c) 2021-2022 John "Nielk1" Klein
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

using System;
using System.Linq;

namespace ExtendInput.DataTools.DualSense
{
    /// <remarks>
    /// Actual effect byte values sent to the controller. More complex effects may be build through the combination of these
    /// values and specific paramaters.
    /// </remarks>
    public enum TriggerEffectType : byte
    {
        // Offically recognized modes
        // These are 100% safe and are the only effects that modify the trigger status nybble
        Off       = 0x05, // 00 00 0 101
        Feedback  = 0x21, // 00 10 0 001
        Weapon    = 0x25, // 00 10 0 101
        Vibration = 0x26, // 00 10 0 110

        // Unofficial but unique effects left in the firmware
        // These might be removed in the future
        Bow       = 0x22, // 00 10 0 010
        Galloping = 0x23, // 00 10 0 011
        Machine   = 0x27, // 00 10 0 111

        // Leftover versions of offical modes with simpler logic and no paramater protections
        // These should not be used
        Simple_Feedback  = 0x01, // 00 00 0 001
        Simple_Weapon    = 0x02, // 00 00 0 010
        Simple_Vibration = 0x06, // 00 00 0 110

        // Leftover versions of offical modes with limited paramater ranges
        // These should not be used
        Limited_Feedback = 0x11, // 00 01 0 001
        Limited_Weapon   = 0x12, // 00 01 0 010

        // Debug or Calibration functions
        // Don't use these as they will courrupt the trigger state until the reset button is pressed
        DebugFC = 0xFC, // 11 11 1 100
        DebugFD = 0xFD, // 11 11 1 101
        DebugFE = 0xFE, // 11 11 1 110
    }

    /**
     * Changelog
     * Revision 1: Initial
     * Revision 2: Added Apple approximated adapter factories. (This may not be correct, please test if you have access to Apple APIs.)
     *             Added Sony factories that use Sony's names.
     *             Added Raw factories for Resistance and AutomaticGun that give direct access to bit-packed region data.
     *             Added ReWASD factories that replicate reWASD effects, warts and all.
     *             Trigger enumerations now public and wrapper classes static.
     *             Minor documentation fixes.
     * Revision 3: Corrected Apple factories based on new capture log tests that show only simple rounding was needed.
     * Revision 4: Added 3 new Apple factories based on documentation and capture logs.
     *             These effects are not fully confirmed and are poorly documented even in Apple's docs.
     *             Two of these new effects are similar to our existing raw effect functions.
     * Revision 5: Reorganized and renamed functions and paramaters to be more inline with Sony's API.
     *             Information on the API was exposed by Apple and now further Steamworks version 1.55.
     *             Information is offically source from Apple documentation and Steamworks via logging
     *             HID writes to device based in inputs to new Steamworks functions. Interestingly, my
     *             Raw factories now have equivilents in Sony's offical API and will also be renamed.
     *             Full change list:
     *               TriggerEffectType Enum is re-organized for clarity and comment typoes corrected
     *               TriggerEffectType.Reset is now TriggerEffectType.Off
     *               TriggerEffectType.Resistance is now TriggerEffectType.Feedback
     *               TriggerEffectType.SemiAutomaticGun is now TriggerEffectType.Weapon
     *               TriggerEffectType.AutomaticGun is now TriggerEffectType.Vibration
     *               TriggerEffectType.SimpleResistance is now TriggerEffectType.Simple_Feedback
     *               TriggerEffectType.SimpleSemiAutomaticGun is now TriggerEffectType.Simple_Weapon
     *               TriggerEffectType.SimpleAutomaticGun is now TriggerEffectType.Simple_Vibration
     *               TriggerEffectType.LimitedResistance is now TriggerEffectType.Limited_Feedback
     *               TriggerEffectType.LimitedSemiAutomaticGun is now TriggerEffectType.Limited_Weapon
     *               -----------------------------------------------------------------------------------
     *               TriggerEffectGenerator.Reset(byte[] destinationArray, int destinationIndex) is now TriggerEffectGenerator.Off(byte[] destinationArray, int destinationIndex)
     *               TriggerEffectGenerator.Resistance(byte[] destinationArray, int destinationIndex, byte start, byte force) is now TriggerEffectGenerator.Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
     *               TriggerEffectGenerator.SemiAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force) is now TriggerEffectGenerator.Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
     *               TriggerEffectGenerator.AutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte strength, byte frequency) is now TriggerEffectGenerator.Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
     *               -----------------------------------------------------------------------------------
     *               TriggerEffectGenerator.Bow(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force, byte snapForce) is now TriggerEffectGenerator.
     *               TriggerEffectGenerator.Galloping(byte[] destinationArray, int destinationIndex, byte start, byte end, byte firstFoot, byte secondFoot, byte frequency) is now TriggerEffectGenerator.Galloping(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte firstFoot, byte secondFoot, byte frequency)
     *               TriggerEffectGenerator.Machine(byte[] destinationArray, int destinationIndex, byte start, byte end, byte strengthA, byte strengthB, byte frequency, byte period) is now TriggerEffectGenerator.Machine(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte amplitudeA, byte amplitudeB, byte frequency, byte period)
     *               -----------------------------------------------------------------------------------
     *               TriggerEffectGenerator.SimpleResistance(byte[] destinationArray, int destinationIndex, byte start, byte force) is now TriggerEffectGenerator.Simple_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
     *               TriggerEffectGenerator.SimpleSemiAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force) is now TriggerEffectGenerator.Simple_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
     *               TriggerEffectGenerator.SimpleAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte strength, byte frequency) is now TriggerEffectGenerator.Simple_Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
     *               -----------------------------------------------------------------------------------
     *               TriggerEffectGenerator.LimitedResistance(byte[] destinationArray, int destinationIndex, byte start, byte force) is now TriggerEffectGenerator.Limited_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
     *               TriggerEffectGenerator.LimitedSemiAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force) is now TriggerEffectGenerator.Limited_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
     *               -----------------------------------------------------------------------------------
     *               TriggerEffectGenerator.Raw.ResistanceRaw(byte[] destinationArray, int destinationIndex, byte[] force) is now TriggerEffectGenerator.MultiplePositionFeedback(byte[] destinationArray, int destinationIndex, byte[] strength)
     *               TriggerEffectGenerator.Raw.AutomaticGunRaw(byte[] destinationArray, int destinationIndex, byte[] strength, byte frequency) is now TriggerEffectGenerator.MultiplePositionVibration(byte[] destinationArray, int destinationIndex, byte frequency, byte[] amplitude)
     * Revision 6: Fixed MultiplePositionVibration not using frequency paramater.
     */

    /// <summary>
    /// DualSense controller trigger effect generators.
    /// Revision: 6
    /// 
    /// If you are converting from offical Sony code you will need to convert your chosen effect enum to its chosen factory
    /// function and your paramater struct to paramaters for that function. Please also note that you will need to track the
    /// controller's currently set effect yourself. Note that all effect factories will return false and not modify the
    /// destinationArray if invalid paramaters are used. If paramaters that would result in zero effect are used, the
    /// <see cref="TriggerEffectType.Off">Off</see> effect is applied instead in line with Sony's offical behavior.
    /// All Unofficial, simple, and limited effects are defined as close to the offical effect implementations as possible.
    /// </summary>
    public static class TriggerEffectGenerator
    {
        #region Offical Effects
        /// <summary>
        /// Turn the trigger effect off and return the trigger stop to the neutral position.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Off(byte[] destinationArray, int destinationIndex)
        {
            destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Off;
            destinationArray[destinationIndex +  1] = 0x00;
            destinationArray[destinationIndex +  2] = 0x00;
            destinationArray[destinationIndex +  3] = 0x00;
            destinationArray[destinationIndex +  4] = 0x00;
            destinationArray[destinationIndex +  5] = 0x00;
            destinationArray[destinationIndex +  6] = 0x00;
            destinationArray[destinationIndex +  7] = 0x00;
            destinationArray[destinationIndex +  8] = 0x00;
            destinationArray[destinationIndex +  9] = 0x00;
            destinationArray[destinationIndex + 10] = 0x00;
            return true;
        }

        /// <summary>
        /// Trigger will resist movement beyond the start position.
        /// The trigger status nybble will report 0 before the effect and 1 when in the effect.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="position">The starting zone of the trigger effect. Must be between 0 and 9 inclusive.</param>
        /// <param name="strength">The force of the resistance. Must be between 0 and 8 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
        {
            if (position > 9)
                return false;
            if (strength > 8)
                return false;
            if (strength > 0)
            {
                byte forceValue = (byte)((strength - 1) & 0x07);
                UInt32 forceZones  = 0;
                UInt16 activeZones = 0;
                for (int i = position; i < 10; i++)
                {
                    forceZones  |= (UInt32)(forceValue << (3 * i));
                    activeZones |= (UInt16)(1 << i);
                }

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Feedback;
                destinationArray[destinationIndex +  1] = (byte)((activeZones >> 0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((activeZones >> 8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)((forceZones >>  0) & 0xff);
                destinationArray[destinationIndex +  4] = (byte)((forceZones >>  8) & 0xff);
                destinationArray[destinationIndex +  5] = (byte)((forceZones >> 16) & 0xff);
                destinationArray[destinationIndex +  6] = (byte)((forceZones >> 24) & 0xff);
                destinationArray[destinationIndex +  7] = 0x00; // (byte)((forceZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex +  8] = 0x00; // (byte)((forceZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }

        /// <summary>
        /// Trigger will resist movement beyond the start position until the end position.
        /// The trigger status nybble will report 0 before the effect and 1 when in the effect,
        /// and 2 after until again before the start position.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect. Must be between 2 and 7 inclusive.</param>
        /// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 8 inclusive.</param>
        /// <param name="strength">The force of the resistance. Must be between 0 and 8 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
        {
            if (startPosition > 7 || startPosition < 2)
                return false;
            if (endPosition > 8)
                return false;
            if (endPosition <= startPosition)
                return false;
            if (strength > 8)
                return false;
            if (strength > 0)
            {
                UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Weapon;
                destinationArray[destinationIndex +  1] = (byte)((startAndStopZones >> 0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((startAndStopZones >> 8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)(strength - 1); // this is actually packed into 3 bits, but since it's only one why bother with the fancy code?
                destinationArray[destinationIndex +  4] = 0x00;
                destinationArray[destinationIndex +  5] = 0x00;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }
        /// <summary>
        /// Trigger will vibrate with the input amplitude and frequency beyond the start position.
        /// The trigger status nybble will report 0 before the effect and 1 when in the effect.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="position">The starting zone of the trigger effect. Must be between 0 and 9 inclusive.</param>
        /// <param name="amplitude">Strength of the automatic cycling action. Must be between 0 and 8 inclusive.</param>
        /// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
        {
            if (position > 9)
                return false;
            if (amplitude > 8)
                return false;
            if (amplitude > 0 && frequency > 0)
            {
                byte strengthValue = (byte)((amplitude - 1) & 0x07);
                UInt32 amplitudeZones = 0;
                UInt16 activeZones    = 0;
                for (int i = position; i < 10; i++)
                {
                    amplitudeZones |= (UInt32)(strengthValue << (3 * i));
                    activeZones   |= (UInt16)(1 << i);
                }

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Vibration;
                destinationArray[destinationIndex +  1] = (byte)((activeZones    >>  0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((activeZones    >>  8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)((amplitudeZones >>  0) & 0xff);
                destinationArray[destinationIndex +  4] = (byte)((amplitudeZones >>  8) & 0xff);
                destinationArray[destinationIndex +  5] = (byte)((amplitudeZones >> 16) & 0xff);
                destinationArray[destinationIndex +  6] = (byte)((amplitudeZones >> 24) & 0xff);
                destinationArray[destinationIndex +  7] = 0x00; // (byte)((strengthZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex +  8] = 0x00; // (byte)((strengthZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex +  9] = frequency;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }

        /// <summary>
        /// Trigger will resist movement at varrying strengths in 10 regions.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="strength">Array of 10 resistance values for zones 0 through 9. Must be between 0 and 8 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool MultiplePositionFeedback(byte[] destinationArray, int destinationIndex, byte[] strength)
        {
            if (strength.Length != 10) return false;

            if (strength.Any(dr => dr > 0))
            {
                UInt32 forceZones  = 0;
                UInt16 activeZones = 0;
                for (int i = 0; i < 10; i++)
                {
                    if (strength[i] > 0)
                    {
                        byte forceValue = (byte)((strength[i] - 1) & 0x07);
                        forceZones  |= (UInt32)(forceValue << (3 * i));
                        activeZones |= (UInt16)(1 << i);
                    }
                }

                destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Feedback;
                destinationArray[destinationIndex + 1] = (byte)((activeZones >> 0) & 0xff);
                destinationArray[destinationIndex + 2] = (byte)((activeZones >> 8) & 0xff);
                destinationArray[destinationIndex + 3] = (byte)((forceZones >>  0) & 0xff);
                destinationArray[destinationIndex + 4] = (byte)((forceZones >>  8) & 0xff);
                destinationArray[destinationIndex + 5] = (byte)((forceZones >> 16) & 0xff);
                destinationArray[destinationIndex + 6] = (byte)((forceZones >> 24) & 0xff);
                destinationArray[destinationIndex + 7] = 0x00; // (byte)((forceZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex + 8] = 0x00; // (byte)((forceZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex + 9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }

        /// <summary>
        /// Trigger will resist movement at a linear range of strengths.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
        /// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 9 inclusive.</param>
        /// <param name="startStrength">The force of the resistance at the start. Must be between 1 and 8 inclusive.</param>
        /// <param name="endStrength">The force of the resistance at the end. Must be between 1 and 8 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool SlopeFeedback(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte startStrength, byte endStrength)
        {
            if (startPosition > 8 || startPosition < 0)
                return false;
            if (endPosition > 9)
                return false;
            if (endPosition <= startPosition)
                return false;
            if (startStrength > 8)
                return false;
            if (startStrength < 1)
                return false;
            if (endStrength > 8)
                return false;
            if (endStrength < 1)
                return false;

            byte[] strength = new byte[10];
            float slope = 1.0f * (endStrength - startStrength) / (endPosition - startPosition);
            for (int i = (int)startPosition; i < 10; i++)
                if (i <= endPosition)
                    strength[i] = (byte)Math.Round(startStrength + slope * (i - startPosition));
                else
                    strength[i] = endStrength;

            return MultiplePositionFeedback(destinationArray, destinationIndex, strength);
        }

        /// <summary>
        /// Trigger will vibrate movement at varrying amplitudes and one frequency in 10 regions.
        /// This is an offical effect and is expected to be present in future DualSense firmware.
        /// </summary>
        /// <remarks>
        /// Note this factory's results may not perform as expected.
        /// </remarks>
        /// <seealso cref="Vibration(byte[], int, byte, byte, byte)"/>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="amplitude">Array of 10 strength values for zones 0 through 9. Must be between 0 and 8 inclusive.</param>
        /// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool MultiplePositionVibration(byte[] destinationArray, int destinationIndex, byte frequency, byte[] amplitude)
        {
            if (amplitude.Length != 10) return false;

            if (frequency > 0 && amplitude.Any(dr => dr > 0))
            {
                UInt32 strengthZones = 0;
                UInt16 activeZones   = 0;
                for (int i = 0; i < 10; i++)
                {
                    if (amplitude[i] > 0)
                    {
                        byte strengthValue = (byte)((amplitude[i] - 1) & 0x07);
                        strengthZones |= (UInt32)(strengthValue << (3 * i));
                        activeZones   |= (UInt16)(1 << i);
                    }
                }

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Vibration;
                destinationArray[destinationIndex +  1] = (byte)((activeZones >> 0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((activeZones >> 8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)((strengthZones >>  0) & 0xff);
                destinationArray[destinationIndex +  4] = (byte)((strengthZones >>  8) & 0xff);
                destinationArray[destinationIndex +  5] = (byte)((strengthZones >> 16) & 0xff);
                destinationArray[destinationIndex +  6] = (byte)((strengthZones >> 24) & 0xff);
                destinationArray[destinationIndex +  7] = 0x00; // (byte)((forceZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex +  8] = 0x00; // (byte)((forceZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
                destinationArray[destinationIndex +  9] = frequency;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }
        #endregion Offical Effects

        #region Unofficial but Unique Effects
        /// <summary>
        /// The effect resembles the <see cref="Weapon(byte[], int, byte, byte, byte)">Weapon</see>
        /// effect, however there is a snap-back force that attempts to reset the trigger.
        /// This is not an offical effect and may be removed in a future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
        /// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 8 inclusive.</param>
        /// <param name="strength">The force of the resistance. Must be between 0 and 8 inclusive.</param>
        /// <param name="snapForce">The force of the snap-back. Must be between 0 and 8 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Bow(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength, byte snapForce)
        {
            if (startPosition > 8)
                return false;
            if (endPosition > 8)
                return false;
            if (startPosition >= endPosition)
                return false;
            if (strength > 8)
                return false;
            if (snapForce > 8)
                return false;
            if (endPosition > 0 && strength > 0 && snapForce > 0)
            {
                UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
                UInt32 forcePair = (UInt32)((((strength  - 1) & 0x07) << (3 * 0))
                                          | (((snapForce - 1) & 0x07) << (3 * 1)));

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Bow;
                destinationArray[destinationIndex +  1] = (byte)((startAndStopZones >> 0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((startAndStopZones >> 8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)((forcePair >> 0) & 0xff);
                destinationArray[destinationIndex +  4] = (byte)((forcePair >> 8) & 0xff);
                destinationArray[destinationIndex +  5] = 0x00;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }

        /// <summary>
        /// Trigger will oscillate in a rythmic pattern resembling galloping. Note that the
        /// effect is only discernable at low frequency values.
        /// This is not an offical effect and may be removed in a future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
        /// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 9 inclusive.</param>
        /// <param name="firstFoot">Position of second foot in cycle. Must be between 0 and 6 inclusive.</param>
        /// <param name="secondFoot">Position of second foot in cycle. Must be between <paramref name="firstFoot"/>+1 and 7 inclusive.</param>
        /// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Galloping(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte firstFoot, byte secondFoot, byte frequency)
        {
            if (startPosition > 8)
                return false;
            if (endPosition > 9)
                return false;
            if (startPosition >= endPosition)
                return false;
            if (secondFoot > 7)
                return false;
            if (firstFoot > 6)
                return false;
            if (firstFoot >= secondFoot)
                return false;
            if (frequency > 0)
            {
                UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
                UInt32 timeAndRatio = (UInt32)(((secondFoot & 0x07) << (3 * 0))
                                             | ((firstFoot  & 0x07) << (3 * 1)));

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Galloping;
                destinationArray[destinationIndex +  1] = (byte)((startAndStopZones >> 0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((startAndStopZones >> 8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)((timeAndRatio >> 0) & 0xff);
                destinationArray[destinationIndex +  4] = frequency; // this is actually packed into 3 bits, but since it's only one why bother with the fancy code?
                destinationArray[destinationIndex +  5] = 0x00;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }


        /// <summary>
        /// This effect resembles <see cref="Vibration(byte[], int, byte, byte, byte)">Vibration</see>
        /// but will oscilate between two amplitudes.
        /// This is not an offical effect and may be removed in a future DualSense firmware.
        /// </summary>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
        /// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/> and 9 inclusive.</param>
        /// <param name="amplitudeA">Primary strength of cycling action. Must be between 0 and 7 inclusive.</param>
        /// <param name="amplitudeB">Secondary strength of cycling action. Must be between 0 and 7 inclusive.</param>
        /// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
        /// <param name="period">Period of the oscillation between <paramref name="amplitudeA"/> and <paramref name="amplitudeB"/> in tenths of a second.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Machine(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte amplitudeA, byte amplitudeB, byte frequency, byte period)
        {
            if (startPosition > 8)
                return false;
            if (endPosition > 9)
                return false;
            if (endPosition <= startPosition)
                return false;
            if (amplitudeA > 7)
                return false;
            if (amplitudeB > 7)
                return false;
            if (frequency > 0)
            {
                UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
                UInt32 strengthPair = (UInt32)(((amplitudeA & 0x07) << (3 * 0))
                                             | ((amplitudeB & 0x07) << (3 * 1)));

                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Machine;
                destinationArray[destinationIndex +  1] = (byte)((startAndStopZones >> 0) & 0xff);
                destinationArray[destinationIndex +  2] = (byte)((startAndStopZones >> 8) & 0xff);
                destinationArray[destinationIndex +  3] = (byte)((strengthPair >> 0) & 0xff);
                destinationArray[destinationIndex +  4] = frequency;
                destinationArray[destinationIndex +  5] = period;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }
        #endregion Unofficial but Unique Effects

        #region Simple Effects
        /// <summary>
        /// Simplistic Feedback effect data generator.
        /// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
        /// </summary>
        /// <remarks>
        /// Use <see cref="Feedback(byte[], int, byte, byte)"/> instead.
        /// </remarks>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="position">The starting zone of the trigger effect.</param>
        /// <param name="strength">The force of the resistance.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Simple_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
        {
            destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Simple_Feedback;
            destinationArray[destinationIndex +  1] = position;
            destinationArray[destinationIndex +  2] = strength;
            destinationArray[destinationIndex +  3] = 0x00;
            destinationArray[destinationIndex +  4] = 0x00;
            destinationArray[destinationIndex +  5] = 0x00;
            destinationArray[destinationIndex +  6] = 0x00;
            destinationArray[destinationIndex +  7] = 0x00;
            destinationArray[destinationIndex +  8] = 0x00;
            destinationArray[destinationIndex +  9] = 0x00;
            destinationArray[destinationIndex + 10] = 0x00;
            return true;
        }

        /// <summary>
        /// Simplistic Weapon effect data generator.
        /// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
        /// </summary>
        /// <remarks>
        /// Use <see cref="Weapon(byte[], int, byte, byte, byte)"/> instead.
        /// </remarks>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect.</param>
        /// <param name="endPosition">The ending zone of the trigger effect.</param>
        /// <param name="strength">The force of the resistance.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Simple_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
        {
            destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Simple_Weapon;
            destinationArray[destinationIndex +  1] = startPosition;
            destinationArray[destinationIndex +  2] = endPosition;
            destinationArray[destinationIndex +  3] = strength;
            destinationArray[destinationIndex +  4] = 0x00;
            destinationArray[destinationIndex +  5] = 0x00;
            destinationArray[destinationIndex +  6] = 0x00;
            destinationArray[destinationIndex +  7] = 0x00;
            destinationArray[destinationIndex +  8] = 0x00;
            destinationArray[destinationIndex +  9] = 0x00;
            destinationArray[destinationIndex + 10] = 0x00;
            return true;
        }

        /// <summary>
        /// Simplistic Vibration effect data generator.
        /// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
        /// </summary>
        /// <remarks>
        /// Use <see cref="Vibration(byte[], int, byte, byte, byte)"/> instead.
        /// </remarks>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="position">The starting zone of the trigger effect.</param>
        /// <param name="amplitude">Strength of the automatic cycling action.</param>
        /// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Simple_Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
        {
            if (frequency > 0 && amplitude > 0)
            {
                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Simple_Vibration;
                destinationArray[destinationIndex +  1] = frequency;
                destinationArray[destinationIndex +  2] = amplitude;
                destinationArray[destinationIndex +  3] = position;
                destinationArray[destinationIndex +  4] = 0x00;
                destinationArray[destinationIndex +  5] = 0x00;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }
        #endregion Simple Effects

        #region Limited Effects
        /// <summary>
        /// Simplistic Feedback effect data generator with stricter paramater limits.
        /// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
        /// </summary>
        /// <remarks>
        /// Use <see cref="Feedback(byte[], int, byte, byte)"/> instead.
        /// </remarks>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="position">The starting zone of the trigger effect.</param>
        /// <param name="strength">The force of the resistance. Must be between 0 and 10 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Limited_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
        {
            if (strength > 10)
                return false;
            if (strength > 0)
            {
                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Limited_Feedback;
                destinationArray[destinationIndex +  1] = position;
                destinationArray[destinationIndex +  2] = strength;
                destinationArray[destinationIndex +  3] = 0x00;
                destinationArray[destinationIndex +  4] = 0x00;
                destinationArray[destinationIndex +  5] = 0x00;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }

        /// <summary>
        /// Simplistic Weapon effect data generator with stricter paramater limits.
        /// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
        /// </summary>
        /// <remarks>
        /// Use <see cref="Weapon(byte[], int, byte, byte, byte)"/> instead.
        /// </remarks>
        /// <param name="destinationArray">The byte[] that receives the data.</param>
        /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
        /// <param name="startPosition">The starting zone of the trigger effect. Must be 16 or higher.</param>
        /// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/> and <paramref name="startPosition"/>+100 inclusive.</param>
        /// <param name="strength">The force of the resistance. Must be between 0 and 10 inclusive.</param>
        /// <returns>The success of the effect write.</returns>
        static public bool Limited_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
        {
            if (startPosition < 0x10)
                return false;
            if (endPosition < startPosition || (startPosition + 100) < endPosition)
                return false;
            if (strength > 10)
                return false;
            if (strength > 0)
            {
                destinationArray[destinationIndex +  0] = (byte)TriggerEffectType.Limited_Weapon;
                destinationArray[destinationIndex +  1] = startPosition;
                destinationArray[destinationIndex +  2] = endPosition;
                destinationArray[destinationIndex +  3] = strength;
                destinationArray[destinationIndex +  4] = 0x00;
                destinationArray[destinationIndex +  5] = 0x00;
                destinationArray[destinationIndex +  6] = 0x00;
                destinationArray[destinationIndex +  7] = 0x00;
                destinationArray[destinationIndex +  8] = 0x00;
                destinationArray[destinationIndex +  9] = 0x00;
                destinationArray[destinationIndex + 10] = 0x00;
                return true;
            }
            return Off(destinationArray, destinationIndex);
        }
        #endregion Limited Effects

        /// <summary>
        /// Interface adapaters patterned after Apple's GCDualSenseAdaptiveTrigger classs.
        /// </summary>
        public static class Apple
        {
            /// <summary>
            /// Sets the adaptive trigger to feedback mode. The start position and strength of the effect can be set arbitrarily. The trigger arm will continue to provide a
            /// constant degree of feedback whenever it is depressed further than the start position.
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="Off(byte[], int)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SetModeOff(byte[] destinationArray, int destinationIndex) =>
                Off(destinationArray, destinationIndex);

            /// <summary>
            /// Sets the adaptive trigger to feedback mode. The start position and strength of the effect can be set arbitrarily. The trigger arm will continue to provide a
            /// constant degree of feedback whenever it is depressed further than the start position.
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible trigger depression and 1 representing the maximum trigger depression.</param>
            /// <param name="resistiveStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SetModeFeedbackWithStartPosition(byte[] destinationArray, int destinationIndex, float startPosition, float resistiveStrength)
            {
                startPosition = (float)Math.Round(startPosition * 9.0f);
                resistiveStrength = (float)Math.Round(resistiveStrength * 8.0f);
                return Feedback(destinationArray, destinationIndex, (byte)startPosition, (byte)resistiveStrength);
            }

            /// <summary>
            /// Sets the adaptive trigger to weapon mode. The start position, end position, and strength of the effect can be set arbitrarily; however the end position must be larger than the start position.
            /// The trigger arm will continue to provide a constant degree of feedback whenever it is depressed further than the start position. Once the trigger arm has been depressed past the end
            /// position, the strength of the effect will immediately fall to zero, providing a "sense of release" similar to that provided by pulling the trigger of a weapon.
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. The effect will begin once the trigger is depressed beyond this point.</param>
            /// <param name="endPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. Must be greater than startPosition. The effect will end once the trigger is depressed beyond this point.</param>
            /// <param name="resistiveStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SetModeWeaponWithStartPosition(byte[] destinationArray, int destinationIndex, float startPosition, float endPosition, float resistiveStrength)
            {
                startPosition = (float)Math.Round(startPosition * 9.0f);
                endPosition = (float)Math.Round(endPosition * 9.0f);
                resistiveStrength = (float)Math.Round(resistiveStrength * 8.0f);
                return Weapon(destinationArray, destinationIndex, (byte)startPosition, (byte)endPosition, (byte)resistiveStrength);
            }

            /// <summary>
            /// Sets the adaptive trigger to vibration mode. The start position, amplitude, and frequency of the effect can be set arbitrarily. The trigger arm will continue to strike against
            /// the trigger whenever it is depressed further than the start position, providing a "sense of vibration".
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="Vibration(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. The effect will begin once the trigger is depressed beyond this point.</param>
            /// <param name="amplitude">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <param name="frequency">A normalized float from [0-1], with 0 representing the minimum frequency and 1 representing the maximum frequency of the vibration effect.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SetModeVibrationWithStartPosition(byte[] destinationArray, int destinationIndex, float startPosition, float amplitude, float frequency)
            {
                startPosition = (float)Math.Round(startPosition * 9.0f);
                amplitude = (float)Math.Round(amplitude * 8.0f);
                frequency = (float)Math.Round(frequency * 255.0f);
                return Vibration(destinationArray, destinationIndex, (byte)startPosition, (byte)amplitude, (byte)frequency);
            }

            /// <summary>
            /// Sets the adaptive trigger to feedback mode. The strength of the effect can be set arbitrarily per zone.
            /// This implementation is not confirmed.
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="MultiplePositionFeedback(byte[], int, byte[])"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="positionalResistiveStrengths">An array of 10 normalized floats from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SetModeFeedback(byte[] destinationArray, int destinationIndex, float[] positionalResistiveStrengths)
            {
                if (positionalResistiveStrengths.Length != 10) return false;

                byte[] force = new byte[10];
                for (int i = 0; i < 10; i++)
                    force[i] = (byte)Math.Round(positionalResistiveStrengths[i] * 8.0f);

                return MultiplePositionFeedback(destinationArray, destinationIndex, force);
            }

            /// <summary>
            /// Sets the adaptive trigger to feedback mode. The strength of the effect will change across zones based on a slope.
            /// This implementation is not confirmed.
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="MultiplePositionFeedback(byte[], int, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. The effect will begin once the trigger is depressed beyond this point.</param>
            /// <param name="endPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. Must be greater than startPosition. The effect will end once the trigger is depressed beyond this point.</param>
            /// <param name="startStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <param name="endStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool setModeSlopeFeedback(byte[] destinationArray, int destinationIndex, float startPosition, float endPosition, float startStrength, float endStrength)
            {
                startPosition = (float)Math.Round(startPosition * 9.0f);
                endPosition = (float)Math.Round(endPosition * 9.0f);
                startStrength = (float)Math.Round(startStrength * 8.0f);
                endStrength = (float)Math.Round(endStrength * 8.0f);

                return SlopeFeedback(destinationArray, destinationIndex, (byte)startPosition, (byte)endPosition, (byte)startStrength, (byte)endStrength);
            }

            /// <summary>
            /// Sets the adaptive trigger to vibration mode. The frequency of the effect can be set arbitrarily and the amplitude arbitrarily per zone.
            /// This implementation is not confirmed.
            /// </summary>
            /// <remarks>
            /// Documentation ported from Apple's API Docs.
            /// </remarks>
            /// <seealso cref="MultiplePositionVibration(byte[], int, byte, byte[])"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="positionalAmplitudes">An array of 10 normalized floats from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
            /// <param name="frequency">A normalized float from [0-1], with 0 representing the minimum frequency and 1 representing the maximum frequency of the vibration effect.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool setModeVibration(byte[] destinationArray, int destinationIndex, float[] positionalAmplitudes, float frequency)
            {
                if (positionalAmplitudes.Length != 10) return false;

                frequency = (float)Math.Round(frequency * 255.0f);

                byte[] strength = new byte[10];
                for (int i = 0; i < 10; i++)
                    strength[i] = (byte)Math.Round(positionalAmplitudes[i] * 8.0f);

                return MultiplePositionVibration(destinationArray, destinationIndex, (byte)frequency, strength);
            }
        }

        /// <summary>
        /// Interface adapaters patterned after reWASD's actual interface.
        /// </summary>
        /// <remarks>
        /// This information is based on sniffing the USB traffic from reWASD. Broken implementations are kept though immaterial inaccuracies are corrected.
        /// </remarks>
        public static class ReWASD
        {
            /// <summary>
            /// Full Press trigger stop effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Weapon with a start value of 0x90, end value of 0xa0, and a force of 0xff.
            /// </remarks>
            /// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
            /// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool FullPress(byte[] destinationArray, int destinationIndex) =>
                Simple_Weapon(destinationArray, destinationIndex, 0x90, 0xa0, 0xff);

            /// <summary>
            /// Soft Press trigger stop effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Weapon with a start value of 0x70, end value of 0xa0, and a force of 0xff.
            /// </remarks>
            /// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
            /// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SoftPress(byte[] destinationArray, int destinationIndex) =>
                Simple_Weapon(destinationArray, destinationIndex, 0x70, 0xa0, 0xff);

            /// <summary>
            /// Medium Press trigger stop effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Weapon with a start value of 0x45, end value of 0xa0, and a force of 0xff.
            /// </remarks>
            /// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
            /// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool MediumPress(byte[] destinationArray, int destinationIndex) =>
                Simple_Weapon(destinationArray, destinationIndex, 0x45, 0xa0, 0xff);

            /// <summary>
            /// Hard Press trigger stop effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Weapon with a start value of 0x20, end value of 0xa0, and a force of 0xff.
            /// </remarks>
            /// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
            /// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool HardPress(byte[] destinationArray, int destinationIndex) =>
                Simple_Weapon(destinationArray, destinationIndex, 0x20, 0xa0, 0xff);

            /// <summary>
            /// Pulse trigger stop effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Weapon with a start value of 0x00, end value of 0x00, and a force of 0x00.
            /// </remarks>
            /// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
            /// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool Pulse(byte[] destinationArray, int destinationIndex) =>
                Simple_Weapon(destinationArray, destinationIndex, 0x00, 0x00, 0x00);

            /// <summary>
            /// Choppy resistance effect data generator.
            /// </summary>
            /// <remarks>
            /// Abuses Feedback effect to set a resistance in 3 of 10 trigger regions.
            /// </remarks>
            /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool Choppy(byte[] destinationArray, int destinationIndex)
            {
                destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Feedback;
                destinationArray[destinationIndex + 1] = (byte)0x02; // region enables
                destinationArray[destinationIndex + 2] = (byte)0x27; // region enables
                destinationArray[destinationIndex + 3] = (byte)0x18; // reWASD uses 0x1f here, but some bits apply to regions not enabled above
                destinationArray[destinationIndex + 4] = (byte)0x00;
                destinationArray[destinationIndex + 5] = (byte)0x00; // reWASD uses 0x27 here, but some bits apply to regions not enabled above
                destinationArray[destinationIndex + 6] = (byte)0x26;
                destinationArray[destinationIndex + 7] = (byte)0x00;
                destinationArray[destinationIndex + 8] = (byte)0x00;
                destinationArray[destinationIndex + 9] = (byte)0x00;
                destinationArray[destinationIndex + 10] = (byte)0x00;
                return true;
            }

            /// <summary>
            /// Soft Rigidity feedback effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Feedback with a start value of 0x00 and a force of 0x00.
            /// </remarks>
            /// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
            /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool SoftRigidity(byte[] destinationArray, int destinationIndex) =>
                Simple_Feedback(destinationArray, destinationIndex, 0x00, 0x00);

            /// <summary>
            /// Medium Rigidity feedback effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Feedback with a start value of 0x00 and a force of 0x64.
            /// </remarks>
            /// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
            /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool MediumRigidity(byte[] destinationArray, int destinationIndex) =>
                Simple_Feedback(destinationArray, destinationIndex, 0x00, 0x64);

            /// <summary>
            /// Max Rigidity feedback effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Feedback with a start value of 0x00 and a force of 0xdc.
            /// </remarks>
            /// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
            /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool MaxRigidity(byte[] destinationArray, int destinationIndex) =>
                Simple_Feedback(destinationArray, destinationIndex, 0x00, 0xdc);

            /// <summary>
            /// Half Press feedback effect data generator.
            /// </summary>
            /// <remarks>
            /// Uses Simple_Feedback with a start value of 0x55 and a force of 0x64.
            /// </remarks>
            /// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
            /// <seealso cref="Feedback(byte[], int, byte, byte)"/>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool HalfPress(byte[] destinationArray, int destinationIndex) =>
                Simple_Feedback(destinationArray, destinationIndex, 0x55, 0x64);

            /// <summary>
            /// Rifle vibration effect data generator with some wasted bits.
            /// Bad coding from reWASD was faithfully replicated.
            /// </summary>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="frequency">Frequency of the automatic cycling action in hertz. Must be between 2 and 20 inclusive.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool Rifle(byte[] destinationArray, int destinationIndex, byte frequency = 10)
            {
                if (frequency < 2)
                    return false;
                if (frequency > 20)
                    return false;

                destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Vibration;
                destinationArray[destinationIndex + 1] = (byte)0x00;
                destinationArray[destinationIndex + 2] = (byte)0x03; // reWASD uses 0xFF here but the top 6 bits are unused
                destinationArray[destinationIndex + 3] = (byte)0x00;
                destinationArray[destinationIndex + 4] = (byte)0x00;
                destinationArray[destinationIndex + 5] = (byte)0x00;
                destinationArray[destinationIndex + 6] = (byte)0x3F; // reWASD uses 0xFF here but the top 2 bits are unused
                destinationArray[destinationIndex + 7] = (byte)0x00;
                destinationArray[destinationIndex + 8] = (byte)0x00;
                destinationArray[destinationIndex + 9] = frequency;
                destinationArray[destinationIndex + 10] = (byte)0x00;
                return true;
            }

            /// <summary>
            /// Vibration vibration effect with incorrect strength handling.
            /// Bad coding from reWASD was faithfully replicated.
            /// </summary>
            /// <param name="destinationArray">The byte[] that receives the data.</param>
            /// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
            /// <param name="strength">Strength of the automatic cycling action. Must be between 1 and 255 inclusive. This is two 3 bit numbers with the remaining 2 high bits unused. Yes, reWASD uses this value incorrectly.</param>
            /// <param name="frequency">Frequency of the automatic cycling action in hertz. Must be between 1 and 255 inclusive.</param>
            /// <returns>The success of the effect write.</returns>
            static public bool Vibration(byte[] destinationArray, int destinationIndex, byte strength = 220, byte frequency = 30)
            {
                if (strength < 1)
                    return false;
                if (frequency < 1)
                    return false;

                destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Vibration;
                destinationArray[destinationIndex + 1] = (byte)0x00; // reWASD uses 0x1E here but this is invalid and is ignored save for minor glitches
                destinationArray[destinationIndex + 2] = (byte)0x03; // reWASD uses 0xFF here but the top 6 bits are unused
                destinationArray[destinationIndex + 3] = (byte)0x00;
                destinationArray[destinationIndex + 4] = (byte)0x00;
                destinationArray[destinationIndex + 5] = (byte)0x00;
                destinationArray[destinationIndex + 6] = strength; // reWASD maxes at 0xFF here but the top 2 bits are unused
                destinationArray[destinationIndex + 7] = (byte)0x00;
                destinationArray[destinationIndex + 8] = (byte)0x00;
                destinationArray[destinationIndex + 9] = frequency;
                destinationArray[destinationIndex + 10] = (byte)0x00;
                return true;
            }
        }
    }
}

Input Reports[]

struct TouchFingerData { // 4
/*0.0*/ uint32_t Index : 7;
/*0.7*/ uint32_t NotTouching : 1;
/*1.0*/ uint32_t FingerX : 12;
/*2.4*/ uint32_t FingerY : 12;
};

struct TouchData { // 9
/*0*/ TouchFingerData[2] Finger;
/*8*/ uint8_t Timestamp;
};

struct BTSimpleGetStateData { // 9
/*0  */ uint8_t LeftStickX;
/*1  */ uint8_t LeftStickY;
/*2  */ uint8_t RightStickX;
/*3  */ uint8_t RightStickY;
/*4.0*/ Direction DPad : 4;
/*4.4*/ uint8_t ButtonSquare : 1;
/*4.5*/ uint8_t ButtonCross : 1;
/*4.6*/ uint8_t ButtonCircle : 1;
/*4.7*/ uint8_t ButtonTriangle : 1;
/*5.0*/ uint8_t ButtonL1 : 1;
/*5.1*/ uint8_t ButtonR1 : 1;
/*5.2*/ uint8_t ButtonL2 : 1;
/*5.3*/ uint8_t ButtonR2 : 1;
/*5.4*/ uint8_t ButtonShare : 1;
/*5.5*/ uint8_t ButtonOptions : 1;
/*5.6*/ uint8_t ButtonL3 : 1;
/*5.7*/ uint8_t ButtonR3 : 1;
/*6.1*/ uint8_t ButtonHome : 1;
/*6.2*/ uint8_t ButtonPad : 1;
/*6.3*/ uint8_t Counter : 6;
/*7  */ uint8_t TriggerLeft;
/*8  */ uint8_t TriggerRight;
// anything beyond this point, if set, is invalid junk data that was not cleared
};

struct USBGetStateData { // 63
/* 0  */ uint8_t LeftStickX;
/* 1  */ uint8_t LeftStickY;
/* 2  */ uint8_t RightStickX;
/* 3  */ uint8_t RightStickY;
/* 4  */ uint8_t TriggerLeft;
/* 5  */ uint8_t TriggerRight;
/* 6  */ uint8_t SeqNo; // always 0x01 on BT
/* 7.0*/ Direction DPad : 4;
/* 7.4*/ uint8_t ButtonSquare : 1;
/* 7.5*/ uint8_t ButtonCross : 1;
/* 7.6*/ uint8_t ButtonCircle : 1;
/* 7.7*/ uint8_t ButtonTriangle : 1;
/* 8.0*/ uint8_t ButtonL1 : 1;
/* 8.1*/ uint8_t ButtonR1 : 1;
/* 8.2*/ uint8_t ButtonL2 : 1;
/* 8.3*/ uint8_t ButtonR2 : 1;
/* 8.4*/ uint8_t ButtonCreate : 1;
/* 8.5*/ uint8_t ButtonOptions : 1;
/* 8.6*/ uint8_t ButtonL3 : 1;
/* 8.7*/ uint8_t ButtonR3 : 1;
/* 9.0*/ uint8_t ButtonHome : 1;
/* 9.1*/ uint8_t ButtonPad : 1;
/* 9.2*/ uint8_t ButtonMute : 1;
/* 9.3*/ uint8_t UNK1 : 1; // appears unused
/* 9.4*/ uint8_t ButtonLeftFunction : 1; // DualSense Edge
/* 9.5*/ uint8_t ButtonRightFunction : 1; // DualSense Edge
/* 9.6*/ uint8_t ButtonLeftPaddle : 1; // DualSense Edge
/* 9.7*/ uint8_t ButtonRightPaddle : 1; // DualSense Edge
/*10  */ uint8_t UNK2; // appears unused
/*11  */ uint32_t UNK_COUNTER; // Linux driver calls this reserved, tools leak calls the 2 high bytes "random"
/*15  */ int16_t AngularVelocityX;
/*17  */ int16_t AngularVelocityZ;
/*19  */ int16_t AngularVelocityY;
/*21  */ int16_t AccelerometerX;
/*23  */ int16_t AccelerometerY;
/*25  */ int16_t AccelerometerZ;
/*27  */ uint32_t SensorTimestamp;
/*31  */ int8_t Temperature; // reserved2 in Linux driver
/*32  */ TouchData TouchData;
/*41.0*/ uint8_t TriggerRightStopLocation: 4; // trigger stop can be a range from 0 to 9 (F/9.0 for Apple interface)
/*41.4*/ uint8_t TriggerRightStatus: 4;
/*42.0*/ uint8_t TriggerLeftStopLocation: 4;
/*42.4*/ uint8_t TriggerLeftStatus: 4; // 0 feedbackNoLoad
                                       // 1 feedbackLoadApplied
                                       // 0 weaponReady
                                       // 1 weaponFiring
                                       // 2 weaponFired
                                       // 0 vibrationNotVibrating
                                       // 1 vibrationIsVibrating
/*43  */ uint32_t HostTimestamp; // mirrors data from report write
/*47.0*/ uint8_t TriggerRightEffect: 4; // Active trigger effect, previously we thought this was status max
/*47.4*/ uint8_t TriggerLeftEffect: 4;  // 0 for reset and all other effects
                                        // 1 for feedback effect
                                        // 2 for weapon effect
                                        // 3 for vibration
/*48  */ uint32_t DeviceTimeStamp;
/*52.0*/ uint8_t PowerPercent : 4; // 0x00-0x0A
/*52.4*/ PowerState PowerState : 4;
/*53.0*/ uint8_t PluggedHeadphones : 1;
/*53.1*/ uint8_t PluggedMic : 1;
/*53.2*/ uint8_t MicMuted: 1; // Mic muted by powersave/mute command
/*53.3*/ uint8_t PluggedUsbData : 1;
/*53.4*/ uint8_t PluggedUsbPower : 1;
/*53.5*/ uint8_t PluggedUnk1 : 3;
/*54.0*/ uint8_t PluggedExternalMic : 1; // Is external mic active (automatic in mic auto mode)
/*54.1*/ uint8_t HapticLowPassFilter : 1; // Is the Haptic Low-Pass-Filter active?
/*54.2*/ uint8_t PluggedUnk3 : 6;
/*55  */ uint8_t[8] AesCmac;
};

struct BTGetStateData { // 77
/* 0*/ USBGetStateData StateData;
/*63*/ uint8_t UNK1; // Oscillates between 00101100 and 00101101 when rumbling
                     // Not affected by rumble volume or enhanced vs normal rumble
                     // Audio rumble not yet tested as this is only on BT
/*64*/ uint8_t BtCrcFailCount;
/*65*/ uint8_t[11] Pad;
};

HID Report 0x01 Input USB[]

struct ReportIn01USB {
    uint8_t ReportID; // 0x01
    USBGetStateData State;
};

HID Report 0x01 Input BT[]

struct ReportIn01BT {
    uint8_t ReportID; // 0x01
    BTSimpleGetStateData State;
};

HID Report 0x31 Input BT[]

struct ReportIn31 {
    union {
        BTCRC<78> CRC;
        struct {
            uint8_t ReportID; // 0x31
            uint8_t HasHID : 1; // Present for packets with state data
            uint8_t HasMic : 1; // Looks mutually exclusive, possible mic data
            uint8_t Unk1 : 2;
            uint8_t SeqNo : 4; // unclear progression
            BTGetStateData State;
        } Data;
    }
};

Output Reports[]

struct SetStateData { // 47
/*    */ // Report Set Flags
/*    */ // These flags are used to indicate what contents from this report should be processed
/* 0.0*/ uint8_t EnableRumbleEmulation: 1; // Suggest halving rumble strength
/* 0.1*/ uint8_t UseRumbleNotHaptics:   1; // 
/*    */
/* 0.2*/ uint8_t AllowRightTriggerFFB: 1; // Enable setting RightTriggerFFB
/* 0.3*/ uint8_t AllowLeftTriggerFFB: 1;  // Enable setting LeftTriggerFFB
/*    */
/* 0.4*/ uint8_t AllowHeadphoneVolume: 1; // Enable setting VolumeHeadphones
/* 0.5*/ uint8_t AllowSpeakerVolume: 1;   // Enable setting VolumeSpeaker
/* 0.6*/ uint8_t AllowMicVolume: 1;       // Enable setting VolumeMic
/*    */
/* 0.7*/ uint8_t AllowAudioControl: 1; // Enable setting AudioControl section
/* 1.0*/ uint8_t AllowMuteLight: 1;    // Enable setting MuteLightMode
/* 1.1*/ uint8_t AllowAudioMute: 1;    // Enable setting MuteControl section
/*    */
/* 1.2*/ uint8_t AllowLedColor: 1; // Enable RGB LED section
/*    */
/* 1.3*/ uint8_t ResetLights: 1; // Release the LEDs from Wireless firmware control
/*    */                         // When in wireless mode this must be signaled to control LEDs
/*    */                         // This cannot be applied during the BT pair animation.
/*    */                         // SDL2 waits until the SensorTimestamp value is >= 10200000
/*    */                         // before pulsing this bit once.
/*    */
/* 1.4*/ uint8_t AllowPlayerIndicators: 1; // Enable setting PlayerIndicators section
/* 1.5*/ uint8_t AllowHapticLowPassFilter: 1; // Enable HapticLowPassFilter
/* 1.6*/ uint8_t AllowMotorPowerLevel: 1; // MotorPowerLevel reductions for trigger/haptic
/* 1.7*/ uint8_t AllowAudioControl2: 1; // Enable setting AudioControl2 section
/*    */
/* 2  */ uint8_t RumbleEmulationRight; // emulates the light weight
/* 3  */ uint8_t RumbleEmulationLeft; // emulated the heavy weight
/*    */
/* 4  */ uint8_t VolumeHeadphones; // max 0x7f
/* 5  */ uint8_t VolumeSpeaker; // PS5 appears to only use the range 0x3d-0x64
/* 6  */ uint8_t VolumeMic; // not linier, seems to max at 64, 0 is not fully muted
/*    */
/*    */ // AudioControl
/* 7.0*/ uint8_t MicSelect: 2; // 0 Auto
/*    */                       // 1 Internal Only
/*    */                       // 2 External Only
/*    */                       // 3 Unclear, sets external mic flag but might use internal mic, do test
/* 7.2*/ uint8_t EchoCancelEnable: 1;
/* 7.3*/ uint8_t NoiseCancelEnable: 1;
/* 7.4*/ uint8_t OutputPathSelect: 2; // 0 L_R_X
/*    */                              // 1 L_L_X
/*    */                              // 2 L_L_R
/*    */                              // 3 X_X_R
/* 7.6*/ uint8_t InputPathSelect: 2;  // 0 CHAT_ASR
/*    */                              // 1 CHAT_CHAT
/*    */                              // 2 ASR_ASR
/*    */                              // 3 Does Nothing, invalid
/*    */
/* 8  */ MuteLight MuteLightMode;
/*    */
/*    */ // MuteControl
/* 9.0*/ uint8_t TouchPowerSave: 1;
/* 9.1*/ uint8_t MotionPowerSave: 1;
/* 9.2*/ uint8_t HapticPowerSave: 1; // AKA BulletPowerSave
/* 9.3*/ uint8_t AudioPowerSave: 1;
/* 9.4*/ uint8_t MicMute: 1;
/* 9.5*/ uint8_t SpeakerMute: 1;
/* 9.6*/ uint8_t HeadphoneMute: 1;
/* 9.7*/ uint8_t HapticMute: 1; // AKA BulletMute
/*    */
/*10  */ uint8_t[11] RightTriggerFFB;
/*21  */ uint8_t[11] LeftTriggerFFB;
/*32  */ uint32_t HostTimestamp; // mirrored into report read
/*    */
/*    */ // MotorPowerLevel
/*36.0*/ uint8_t TriggerMotorPowerReduction : 4; // 0x0-0x7 (no 0x8?) Applied in 12.5% reductions
/*36.4*/ uint8_t RumbleMotorPowerReduction : 4;  // 0x0-0x7 (no 0x8?) Applied in 12.5% reductions
/*    */
/*    */ // AudioControl2
/*37.0*/ uint8_t SpeakerCompPreGain: 3; // additional speaker volume boost
/*37.3*/ uint8_t BeamformingEnable: 1; // Probably for MIC given there's 2, might be more bits, can't find what it does
/*37.4*/ uint8_t UnkAudioControl2: 4; // some of these bits might apply to the above
/*    */
/*38.0*/ uint8_t AllowLightBrightnessChange: 1; // LED_BRIHTNESS_CONTROL
/*38.1*/ uint8_t AllowColorLightFadeAnimation: 1; // LIGHTBAR_SETUP_CONTROL
/*38.2*/ uint8_t EnableImprovedRumbleEmulation: 1; // Use instead of EnableRumbleEmulation
                                                   // requires FW >= 0x0224
                                                   // No need to halve rumble strength
/*38.3*/ uint8_t UNKBITC: 5; // unused
/*    */
/*39.0*/ uint8_t HapticLowPassFilter: 1;
/*39.1*/ uint8_t UNKBIT: 7;
/*    */
/*40  */ uint8_t UNKBYTE; // previous notes suggested this was HLPF, was probably off by 1
/*    */
/*41  */ LightFadeAnimation LightFadeAnimation;
/*42  */ LightBrightness LightBrightness;
/*    */
/*    */ // PlayerIndicators
/*    */ // These bits control the white LEDs under the touch pad.
/*    */ // Note the reduction in functionality for later revisions.
/*    */ // Generation 0x03 - Full Functionality
/*    */ // Generation 0x04 - Mirrored Only
/*    */ // Suggested detection: (HardwareInfo & 0x00FFFF00) == 0X00000400
/*    */ //
/*    */ // Layout used by PS5:
/*    */ // 0x04 - -x- -  Player 1
/*    */ // 0x06 - x-x -  Player 2
/*    */ // 0x15 x -x- x  Player 3
/*    */ // 0x1B x x-x x  Player 4
/*    */ // 0x1F x xxx x  Player 5* (Unconfirmed)
/*    */ //
/*    */ //                        // HW 0x03 // HW 0x04
/*43.0*/ uint8_t PlayerLight1 : 1; // x --- - // x --- x
/*43.1*/ uint8_t PlayerLight2 : 1; // - x-- - // - x-x -
/*43.2*/ uint8_t PlayerLight3 : 1; // - -x- - // - -x- -
/*43.3*/ uint8_t PlayerLight4 : 1; // - --x - // - x-x -
/*43.4*/ uint8_t PlayerLight5 : 1; // - --- x // x --- x
/*43.5*/ uint8_t PlayerLightFade: 1; // if low player lights fade in, if high player lights instantly change
/*43.6*/ uint8_t PlayerLightUNK : 2;
/*    */
/*    */ // RGB LED
/*44  */ uint8_t LedRed;
/*45  */ uint8_t LedGreen
/*46  */ uint8_t LedBlue;
// Structure ends here though on BT there is padding and a CRC, see ReportOut31
};

HID Report 0x02 Output USB[]

struct ReportOut02 {
    uint8_t ReportID; // 0x02
    USBSetStateData State;
};

HID Report 0x31 Output BT[]

struct ReportOut31 {
    union {
        BTCRC<78> CRC;
        struct {
            uint8_t ReportID; // 0x31
            uint8_t UNK1 : 1;      // -+
            uint8_t EnableHID : 1; //  | - these 3 bits seem to act as an enum
            uint8_t UNK2 : 1;      // -+
            uint8_t UNK3 : 1;
            uint8_t SeqNo : 4; // increment for every write // we have no proof of this, need to see some PS5 captures
            SetStateData State;
        } Data;
    }
};

Feature Reports[]

Calibration[]

Reading calibration is required to switch BT input reports from the truncated 0x01 report to the expanded 0x31 report.

Linux hid-sony.c (does this apply to the DualSense?)

/* Set gyroscope calibration and normalization parameters.
 * Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
 */
speed_2x = (gyro_speed_plus + gyro_speed_minus);
sc->ds4_calib_data[0].abs_code = ABS_RX;
sc->ds4_calib_data[0].bias = gyro_pitch_bias;
sc->ds4_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
sc->ds4_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;

sc->ds4_calib_data[1].abs_code = ABS_RY;
sc->ds4_calib_data[1].bias = gyro_yaw_bias;
sc->ds4_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
sc->ds4_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;

sc->ds4_calib_data[2].abs_code = ABS_RZ;
sc->ds4_calib_data[2].bias = gyro_roll_bias;
sc->ds4_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
sc->ds4_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;

/* Set accelerometer calibration and normalization parameters.
 * Data values will be normalized to 1/DS4_ACC_RES_PER_G G.
 */
range_2g = acc_x_plus - acc_x_minus;
sc->ds4_calib_data[3].abs_code = ABS_X;
sc->ds4_calib_data[3].bias = acc_x_plus - range_2g / 2;
sc->ds4_calib_data[3].sens_numer = 2*DS4_ACC_RES_PER_G;
sc->ds4_calib_data[3].sens_denom = range_2g;

range_2g = acc_y_plus - acc_y_minus;
sc->ds4_calib_data[4].abs_code = ABS_Y;
sc->ds4_calib_data[4].bias = acc_y_plus - range_2g / 2;
sc->ds4_calib_data[4].sens_numer = 2*DS4_ACC_RES_PER_G;
sc->ds4_calib_data[4].sens_denom = range_2g;

range_2g = acc_z_plus - acc_z_minus;
sc->ds4_calib_data[5].abs_code = ABS_Z;
sc->ds4_calib_data[5].bias = acc_z_plus - range_2g / 2;
sc->ds4_calib_data[5].sens_numer = 2*DS4_ACC_RES_PER_G;
sc->ds4_calib_data[5].sens_denom = range_2g;
Bluetooth 0x05[]
struct ReportFeatureInCalibrateBT {
    union {
        BTCRC<41> CRC;
        struct {
            uint8_t ReportID; // 0x05 // does this exist on USB? confirm
            int16_t GyroPitchBias;
            int16_t GyroYawBias;
            int16_t GyroRollBias;
            int16_t GyroPitchPlus;
            int16_t GyroPitchMinus;
            int16_t GyroYawPlus;
            int16_t GyroYawMinus;
            int16_t GyroRollPlus;
            int16_t GyroRollMinus;
            int16_t GyroSpeedPlus;
            int16_t GyroSpeedMinus;
            int16_t AccelXPlus;
            int16_t AccelXMinus;
            int16_t AccelYPlus;
            int16_t AccelYMinus;
            int16_t AccelZPlus;
            int16_t AccelZMinus;
            int16_t Unknown;
        } Data;
    }
};

MAC[]

Need to confirm these on BT

Get All MAC USB[]
struct ReportFeatureInMacAll {
    uint8_t ReportID; // 0x09
    uint8_t[6] ClientMac; // Right to Left
    uint8_t Hard08;
    uint8_t Hard25;
    uint8_t Hard00;
    uint8_t[6] HostMac; // Right to Left
    uint8_t[3] Pad; // Size according to Linux driver
};

Date and Version[]

Date/Version 0x20 USB/BT[]
struct ReportFeatureInVersion {
    union {
        BTCRC<64> CRC;
        struct {
            uint8_t ReportID; // 0x20
            char[11] BuildDate; // string
            char[8] BuildTime; // string
            uint16_t FwType;
            uint16_t SwSeries;
            uint32_t HardwareInfo; // 0x00FF0000 - Variation
                                   // 0x0000FF00 - Generation
                                   // 0x0000003F - Trial?
                                   // ^ Values tied to enumerations
            uint32_t FirmwareVersion; // 0xAABBCCCC AA.BB.CCCC
            char[12] DeviceInfo;
            ////
            uint16_t UpdateVersion;
            char UpdateImageInfo;
            char UpdateUnk;
            ////
            uint32_t FwVersion1; // AKA SblFwVersion
                                 // 0xAABBCCCC AA.BB.CCCC
                                 // Ignored for FwType 0
                                 // HardwareVersion used for FwType 1
                                 // Unknown behavior if HardwareVersion < 0.1.38 for FwType 2 & 3
                                 // If HardwareVersion >= 0.1.38 for FwType 2 & 3
            uint32_t FwVersion2; // AKA VenomFwVersion
            uint32_t FwVersion3; // AKA SpiderDspFwVersion AKA BettyFwVer
                                 // May be Memory Control Unit for Non Volatile Storage
        }
    }
};

Official Interface[]

The official interface for the DualSense controller is not public and likely confidential. This static library is likely a descendant of the original libPad library used for the DualShock 4 controller.

Limitations[]

  • Three trigger effects (and reset)
  • USB only, no Bluetooth support

Likely Interface[]

Apple's GCDualSenseAdaptiveTrigger interface reveals some aspects of Sony's API. We can assume the following:

  • The controller is handled through an instance, be it class or indexed static, which stores at least some of the state data written to the controller. This can be determined because while there is no way to read the full parameters of the current trigger effect back from the controller the API somehow knows not to send the same parameters to the controller again when provided with the same input.
  • The names of effects can be determined as follows:
    • Valid effects with their custom construct enumeration values that match Trigger*Effect from the input:
      • off = 0
      • feedback = 1
      • weapon = 2
      • vibration = 3
    • Valid trigger statuses are derived from the combination of Trigger*Status and Trigger*Status that do not match Trigger*Status from the input report:
      • unknown = -1, adaptive trigger status cannot be determined.
      • feedbackNoLoad = 0, adaptive trigger is in feedback mode, and a resistive load has not been applied yet.
      • feedbackLoadApplied = 1, adaptive trigger is in feedback mode, and a resistive load is applied.
      • weaponReady = 2, adaptive trigger is in weapon mode, the trigger is ready to fire, and a resistive load has not been applied yet.
      • weaponFiring = 3, adaptive trigger is in weapon mode, the trigger is firing, and a resistive load is currently being applied.
      • weaponFired = 4, adaptive trigger is in weapon mode, the trigger has fired, and a resistive load is no longer being applied.
      • vibrationNotVibrating = 5, adaptive trigger is in vibration mode, and the trigger is not vibrating.
      • vibrationIsVibrating = 6, adaptive trigger is in vibration mode, and the trigger is currently vibrating.

Hardware Revisions[]

There are at this point at least 3 hardware revisions that have been seen in the wild. The first is the original hardware, or Mass Production model, and the second and third are Revision 1 models. When processing the `ReportFeatureInVersion` structure, note the HardwareInfo's Generation and Trial values.

Generation 0x03, Trial 0x13[]

This hardware revision operates as expected.

Generation 0x04, Trial 0x13/0x14[]

This hardware revision has made a 'breaking change' to the player LED handling. Now the two pairs PlayerLight1+PlayerLight5 and PlayerLight2+PlayerLight4 are "wired" together. This change makes it impossible to separately control these LEDs but does not disrupt the intended player light configurations. This change was likely made to allow the use of 3 larger LEDs with light guides which may even have a lower part cost instead of 5 small ones. If interfacing with a controller of this hardware revision, note that only symmetrical player LED configurations are possible.

Advertisement