Sony DualSense

## Input

* 13× Digital Buttons
	+ Face: , , ,
	+ Shoulder: L1, R1
	+ Other: "PS", CREATE, OPTIONS, touchpad click, L3, R3, Mute
* Digital Directional Pad
* Touch Pad
	+ 2× Point
	+ Capacitive
* 2× Analog Sticks
* 2× Analog Triggers
	+ L2, R2
* Motion
	+ 3 Axis Accelerometer
	+ 3 Axis Gyroscope
* Mono? Body Mic with Back Mic sound canceling
* Mono Microphone via TRRS jack

## Output

* Rumble Emulation via LRA
	+ Right: Light (Simulated)
	+ Left: Heavy (Simulated)
* Haptic Feedback via LRA
	+ Right
	+ Left
* Adaptive Trigger
* RGB LED (24 bit color)
* Indicator Lights (5, white, capable of fade)
* Mute Indicator (pulsing or solid amber)
* Mono Speaker
* Stereo Audio via TRRS jack

## Ports

* USB-C  ⚡ 🖥️
* 3.5mm Phone TRRS 🎤 🔈
* Proprietary EXT Port
	+ data (suspected)
	+ charging

# HID Interface

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))

 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))

 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))

 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 ButtonLeftFunction : 1; // DualSense Edge
/\* 9.4\*/ uint8\_t ButtonRightFunction : 1; // DualSense Edge
/\* 9.5\*/ uint8\_t ButtonLeftPaddle : 1; // DualSense Edge
/\* 9.6\*/ uint8\_t ButtonRightPaddle : 1; // DualSense Edge
/\* 9.7\*/ uint8\_t UNK1 : 1; // appears unused
/\*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;
 }
};

```

##### 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.