Sony DualSense/Data 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
 }
 }
};

```