Common Structures[]
// C++11 allows setting enum base type. If enum sizes cannot be assured please use the indicated base types instead of the enum types.
enum Direction : uint8_t {
North = 0,
NorthEast,
East,
SouthEast,
South,
SouthWest,
West,
NorthWest,
None = 8
};
enum PowerState : uint8_t {
Discharging = 0x00, // Use PowerPercent
Charging = 0x01, // Use PowerPercent
Complete = 0x02, // PowerPercent not valid? assume 100%?
AbnormalVoltage = 0x0A, // PowerPercent not valid?
AbnormalTemperature = 0x0B, // PowerPercent not valid?
ChargingError = 0x0F // PowerPercent not valid?
};
enum MuteLight : uint8_t {
Off = 0,
On,
Breathing,
DoNothing, // literally nothing, this input is ignored,
// though it might be a faster blink in other versions
NoAction4,
NoAction5,
NoAction6,
NoAction7= 7
};
enum LightBrightness : uint8_t {
Bright = 0,
Mid,
Dim,
NoAction3,
NoAction4,
NoAction5,
NoAction6,
NoAction7= 7
};
enum LightFadeAnimation : uint8_t {
Nothing = 0,
FadeIn, // from black to blue
FadeOut // from blue to black
};
template<int N> struct BTCRC {
uint8_t[N-4] Buff;
uint32_t CRC;
};
FFB Trigger Effect Factories[]
Code hidden due to length: expand to view code in place. See: Github Gist for latest version of this code.
/*
* MIT License
*
* Copyright (c) 2021-2022 John "Nielk1" Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Linq;
namespace ExtendInput.DataTools.DualSense
{
/// <remarks>
/// Actual effect byte values sent to the controller. More complex effects may be build through the combination of these
/// values and specific paramaters.
/// </remarks>
public enum TriggerEffectType : byte
{
// Offically recognized modes
// These are 100% safe and are the only effects that modify the trigger status nybble
Off = 0x05, // 00 00 0 101
Feedback = 0x21, // 00 10 0 001
Weapon = 0x25, // 00 10 0 101
Vibration = 0x26, // 00 10 0 110
// Unofficial but unique effects left in the firmware
// These might be removed in the future
Bow = 0x22, // 00 10 0 010
Galloping = 0x23, // 00 10 0 011
Machine = 0x27, // 00 10 0 111
// Leftover versions of offical modes with simpler logic and no paramater protections
// These should not be used
Simple_Feedback = 0x01, // 00 00 0 001
Simple_Weapon = 0x02, // 00 00 0 010
Simple_Vibration = 0x06, // 00 00 0 110
// Leftover versions of offical modes with limited paramater ranges
// These should not be used
Limited_Feedback = 0x11, // 00 01 0 001
Limited_Weapon = 0x12, // 00 01 0 010
// Debug or Calibration functions
// Don't use these as they will courrupt the trigger state until the reset button is pressed
DebugFC = 0xFC, // 11 11 1 100
DebugFD = 0xFD, // 11 11 1 101
DebugFE = 0xFE, // 11 11 1 110
}
/**
* Changelog
* Revision 1: Initial
* Revision 2: Added Apple approximated adapter factories. (This may not be correct, please test if you have access to Apple APIs.)
* Added Sony factories that use Sony's names.
* Added Raw factories for Resistance and AutomaticGun that give direct access to bit-packed region data.
* Added ReWASD factories that replicate reWASD effects, warts and all.
* Trigger enumerations now public and wrapper classes static.
* Minor documentation fixes.
* Revision 3: Corrected Apple factories based on new capture log tests that show only simple rounding was needed.
* Revision 4: Added 3 new Apple factories based on documentation and capture logs.
* These effects are not fully confirmed and are poorly documented even in Apple's docs.
* Two of these new effects are similar to our existing raw effect functions.
* Revision 5: Reorganized and renamed functions and paramaters to be more inline with Sony's API.
* Information on the API was exposed by Apple and now further Steamworks version 1.55.
* Information is offically source from Apple documentation and Steamworks via logging
* HID writes to device based in inputs to new Steamworks functions. Interestingly, my
* Raw factories now have equivilents in Sony's offical API and will also be renamed.
* Full change list:
* TriggerEffectType Enum is re-organized for clarity and comment typoes corrected
* TriggerEffectType.Reset is now TriggerEffectType.Off
* TriggerEffectType.Resistance is now TriggerEffectType.Feedback
* TriggerEffectType.SemiAutomaticGun is now TriggerEffectType.Weapon
* TriggerEffectType.AutomaticGun is now TriggerEffectType.Vibration
* TriggerEffectType.SimpleResistance is now TriggerEffectType.Simple_Feedback
* TriggerEffectType.SimpleSemiAutomaticGun is now TriggerEffectType.Simple_Weapon
* TriggerEffectType.SimpleAutomaticGun is now TriggerEffectType.Simple_Vibration
* TriggerEffectType.LimitedResistance is now TriggerEffectType.Limited_Feedback
* TriggerEffectType.LimitedSemiAutomaticGun is now TriggerEffectType.Limited_Weapon
* -----------------------------------------------------------------------------------
* TriggerEffectGenerator.Reset(byte[] destinationArray, int destinationIndex) is now TriggerEffectGenerator.Off(byte[] destinationArray, int destinationIndex)
* TriggerEffectGenerator.Resistance(byte[] destinationArray, int destinationIndex, byte start, byte force) is now TriggerEffectGenerator.Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
* TriggerEffectGenerator.SemiAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force) is now TriggerEffectGenerator.Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
* TriggerEffectGenerator.AutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte strength, byte frequency) is now TriggerEffectGenerator.Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
* -----------------------------------------------------------------------------------
* TriggerEffectGenerator.Bow(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force, byte snapForce) is now TriggerEffectGenerator.
* TriggerEffectGenerator.Galloping(byte[] destinationArray, int destinationIndex, byte start, byte end, byte firstFoot, byte secondFoot, byte frequency) is now TriggerEffectGenerator.Galloping(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte firstFoot, byte secondFoot, byte frequency)
* TriggerEffectGenerator.Machine(byte[] destinationArray, int destinationIndex, byte start, byte end, byte strengthA, byte strengthB, byte frequency, byte period) is now TriggerEffectGenerator.Machine(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte amplitudeA, byte amplitudeB, byte frequency, byte period)
* -----------------------------------------------------------------------------------
* TriggerEffectGenerator.SimpleResistance(byte[] destinationArray, int destinationIndex, byte start, byte force) is now TriggerEffectGenerator.Simple_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
* TriggerEffectGenerator.SimpleSemiAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force) is now TriggerEffectGenerator.Simple_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
* TriggerEffectGenerator.SimpleAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte strength, byte frequency) is now TriggerEffectGenerator.Simple_Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
* -----------------------------------------------------------------------------------
* TriggerEffectGenerator.LimitedResistance(byte[] destinationArray, int destinationIndex, byte start, byte force) is now TriggerEffectGenerator.Limited_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
* TriggerEffectGenerator.LimitedSemiAutomaticGun(byte[] destinationArray, int destinationIndex, byte start, byte end, byte force) is now TriggerEffectGenerator.Limited_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
* -----------------------------------------------------------------------------------
* TriggerEffectGenerator.Raw.ResistanceRaw(byte[] destinationArray, int destinationIndex, byte[] force) is now TriggerEffectGenerator.MultiplePositionFeedback(byte[] destinationArray, int destinationIndex, byte[] strength)
* TriggerEffectGenerator.Raw.AutomaticGunRaw(byte[] destinationArray, int destinationIndex, byte[] strength, byte frequency) is now TriggerEffectGenerator.MultiplePositionVibration(byte[] destinationArray, int destinationIndex, byte frequency, byte[] amplitude)
* Revision 6: Fixed MultiplePositionVibration not using frequency paramater.
*/
/// <summary>
/// DualSense controller trigger effect generators.
/// Revision: 6
///
/// If you are converting from offical Sony code you will need to convert your chosen effect enum to its chosen factory
/// function and your paramater struct to paramaters for that function. Please also note that you will need to track the
/// controller's currently set effect yourself. Note that all effect factories will return false and not modify the
/// destinationArray if invalid paramaters are used. If paramaters that would result in zero effect are used, the
/// <see cref="TriggerEffectType.Off">Off</see> effect is applied instead in line with Sony's offical behavior.
/// All Unofficial, simple, and limited effects are defined as close to the offical effect implementations as possible.
/// </summary>
public static class TriggerEffectGenerator
{
#region Offical Effects
/// <summary>
/// Turn the trigger effect off and return the trigger stop to the neutral position.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool Off(byte[] destinationArray, int destinationIndex)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Off;
destinationArray[destinationIndex + 1] = 0x00;
destinationArray[destinationIndex + 2] = 0x00;
destinationArray[destinationIndex + 3] = 0x00;
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
/// <summary>
/// Trigger will resist movement beyond the start position.
/// The trigger status nybble will report 0 before the effect and 1 when in the effect.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="position">The starting zone of the trigger effect. Must be between 0 and 9 inclusive.</param>
/// <param name="strength">The force of the resistance. Must be between 0 and 8 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
{
if (position > 9)
return false;
if (strength > 8)
return false;
if (strength > 0)
{
byte forceValue = (byte)((strength - 1) & 0x07);
UInt32 forceZones = 0;
UInt16 activeZones = 0;
for (int i = position; i < 10; i++)
{
forceZones |= (UInt32)(forceValue << (3 * i));
activeZones |= (UInt16)(1 << i);
}
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Feedback;
destinationArray[destinationIndex + 1] = (byte)((activeZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((activeZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((forceZones >> 0) & 0xff);
destinationArray[destinationIndex + 4] = (byte)((forceZones >> 8) & 0xff);
destinationArray[destinationIndex + 5] = (byte)((forceZones >> 16) & 0xff);
destinationArray[destinationIndex + 6] = (byte)((forceZones >> 24) & 0xff);
destinationArray[destinationIndex + 7] = 0x00; // (byte)((forceZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 8] = 0x00; // (byte)((forceZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// Trigger will resist movement beyond the start position until the end position.
/// The trigger status nybble will report 0 before the effect and 1 when in the effect,
/// and 2 after until again before the start position.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect. Must be between 2 and 7 inclusive.</param>
/// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 8 inclusive.</param>
/// <param name="strength">The force of the resistance. Must be between 0 and 8 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
{
if (startPosition > 7 || startPosition < 2)
return false;
if (endPosition > 8)
return false;
if (endPosition <= startPosition)
return false;
if (strength > 8)
return false;
if (strength > 0)
{
UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Weapon;
destinationArray[destinationIndex + 1] = (byte)((startAndStopZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((startAndStopZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)(strength - 1); // this is actually packed into 3 bits, but since it's only one why bother with the fancy code?
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// Trigger will vibrate with the input amplitude and frequency beyond the start position.
/// The trigger status nybble will report 0 before the effect and 1 when in the effect.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="position">The starting zone of the trigger effect. Must be between 0 and 9 inclusive.</param>
/// <param name="amplitude">Strength of the automatic cycling action. Must be between 0 and 8 inclusive.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
/// <returns>The success of the effect write.</returns>
static public bool Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
{
if (position > 9)
return false;
if (amplitude > 8)
return false;
if (amplitude > 0 && frequency > 0)
{
byte strengthValue = (byte)((amplitude - 1) & 0x07);
UInt32 amplitudeZones = 0;
UInt16 activeZones = 0;
for (int i = position; i < 10; i++)
{
amplitudeZones |= (UInt32)(strengthValue << (3 * i));
activeZones |= (UInt16)(1 << i);
}
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Vibration;
destinationArray[destinationIndex + 1] = (byte)((activeZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((activeZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((amplitudeZones >> 0) & 0xff);
destinationArray[destinationIndex + 4] = (byte)((amplitudeZones >> 8) & 0xff);
destinationArray[destinationIndex + 5] = (byte)((amplitudeZones >> 16) & 0xff);
destinationArray[destinationIndex + 6] = (byte)((amplitudeZones >> 24) & 0xff);
destinationArray[destinationIndex + 7] = 0x00; // (byte)((strengthZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 8] = 0x00; // (byte)((strengthZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 9] = frequency;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// Trigger will resist movement at varrying strengths in 10 regions.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="strength">Array of 10 resistance values for zones 0 through 9. Must be between 0 and 8 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool MultiplePositionFeedback(byte[] destinationArray, int destinationIndex, byte[] strength)
{
if (strength.Length != 10) return false;
if (strength.Any(dr => dr > 0))
{
UInt32 forceZones = 0;
UInt16 activeZones = 0;
for (int i = 0; i < 10; i++)
{
if (strength[i] > 0)
{
byte forceValue = (byte)((strength[i] - 1) & 0x07);
forceZones |= (UInt32)(forceValue << (3 * i));
activeZones |= (UInt16)(1 << i);
}
}
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Feedback;
destinationArray[destinationIndex + 1] = (byte)((activeZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((activeZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((forceZones >> 0) & 0xff);
destinationArray[destinationIndex + 4] = (byte)((forceZones >> 8) & 0xff);
destinationArray[destinationIndex + 5] = (byte)((forceZones >> 16) & 0xff);
destinationArray[destinationIndex + 6] = (byte)((forceZones >> 24) & 0xff);
destinationArray[destinationIndex + 7] = 0x00; // (byte)((forceZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 8] = 0x00; // (byte)((forceZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// Trigger will resist movement at a linear range of strengths.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
/// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 9 inclusive.</param>
/// <param name="startStrength">The force of the resistance at the start. Must be between 1 and 8 inclusive.</param>
/// <param name="endStrength">The force of the resistance at the end. Must be between 1 and 8 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool SlopeFeedback(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte startStrength, byte endStrength)
{
if (startPosition > 8 || startPosition < 0)
return false;
if (endPosition > 9)
return false;
if (endPosition <= startPosition)
return false;
if (startStrength > 8)
return false;
if (startStrength < 1)
return false;
if (endStrength > 8)
return false;
if (endStrength < 1)
return false;
byte[] strength = new byte[10];
float slope = 1.0f * (endStrength - startStrength) / (endPosition - startPosition);
for (int i = (int)startPosition; i < 10; i++)
if (i <= endPosition)
strength[i] = (byte)Math.Round(startStrength + slope * (i - startPosition));
else
strength[i] = endStrength;
return MultiplePositionFeedback(destinationArray, destinationIndex, strength);
}
/// <summary>
/// Trigger will vibrate movement at varrying amplitudes and one frequency in 10 regions.
/// This is an offical effect and is expected to be present in future DualSense firmware.
/// </summary>
/// <remarks>
/// Note this factory's results may not perform as expected.
/// </remarks>
/// <seealso cref="Vibration(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="amplitude">Array of 10 strength values for zones 0 through 9. Must be between 0 and 8 inclusive.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
/// <returns>The success of the effect write.</returns>
static public bool MultiplePositionVibration(byte[] destinationArray, int destinationIndex, byte frequency, byte[] amplitude)
{
if (amplitude.Length != 10) return false;
if (frequency > 0 && amplitude.Any(dr => dr > 0))
{
UInt32 strengthZones = 0;
UInt16 activeZones = 0;
for (int i = 0; i < 10; i++)
{
if (amplitude[i] > 0)
{
byte strengthValue = (byte)((amplitude[i] - 1) & 0x07);
strengthZones |= (UInt32)(strengthValue << (3 * i));
activeZones |= (UInt16)(1 << i);
}
}
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Vibration;
destinationArray[destinationIndex + 1] = (byte)((activeZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((activeZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((strengthZones >> 0) & 0xff);
destinationArray[destinationIndex + 4] = (byte)((strengthZones >> 8) & 0xff);
destinationArray[destinationIndex + 5] = (byte)((strengthZones >> 16) & 0xff);
destinationArray[destinationIndex + 6] = (byte)((strengthZones >> 24) & 0xff);
destinationArray[destinationIndex + 7] = 0x00; // (byte)((forceZones >> 32) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 8] = 0x00; // (byte)((forceZones >> 40) & 0xff); // need 64bit for this, but we already have enough space
destinationArray[destinationIndex + 9] = frequency;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
#endregion Offical Effects
#region Unofficial but Unique Effects
/// <summary>
/// The effect resembles the <see cref="Weapon(byte[], int, byte, byte, byte)">Weapon</see>
/// effect, however there is a snap-back force that attempts to reset the trigger.
/// This is not an offical effect and may be removed in a future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
/// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 8 inclusive.</param>
/// <param name="strength">The force of the resistance. Must be between 0 and 8 inclusive.</param>
/// <param name="snapForce">The force of the snap-back. Must be between 0 and 8 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Bow(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength, byte snapForce)
{
if (startPosition > 8)
return false;
if (endPosition > 8)
return false;
if (startPosition >= endPosition)
return false;
if (strength > 8)
return false;
if (snapForce > 8)
return false;
if (endPosition > 0 && strength > 0 && snapForce > 0)
{
UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
UInt32 forcePair = (UInt32)((((strength - 1) & 0x07) << (3 * 0))
| (((snapForce - 1) & 0x07) << (3 * 1)));
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Bow;
destinationArray[destinationIndex + 1] = (byte)((startAndStopZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((startAndStopZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((forcePair >> 0) & 0xff);
destinationArray[destinationIndex + 4] = (byte)((forcePair >> 8) & 0xff);
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// Trigger will oscillate in a rythmic pattern resembling galloping. Note that the
/// effect is only discernable at low frequency values.
/// This is not an offical effect and may be removed in a future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
/// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/>+1 and 9 inclusive.</param>
/// <param name="firstFoot">Position of second foot in cycle. Must be between 0 and 6 inclusive.</param>
/// <param name="secondFoot">Position of second foot in cycle. Must be between <paramref name="firstFoot"/>+1 and 7 inclusive.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
/// <returns>The success of the effect write.</returns>
static public bool Galloping(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte firstFoot, byte secondFoot, byte frequency)
{
if (startPosition > 8)
return false;
if (endPosition > 9)
return false;
if (startPosition >= endPosition)
return false;
if (secondFoot > 7)
return false;
if (firstFoot > 6)
return false;
if (firstFoot >= secondFoot)
return false;
if (frequency > 0)
{
UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
UInt32 timeAndRatio = (UInt32)(((secondFoot & 0x07) << (3 * 0))
| ((firstFoot & 0x07) << (3 * 1)));
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Galloping;
destinationArray[destinationIndex + 1] = (byte)((startAndStopZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((startAndStopZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((timeAndRatio >> 0) & 0xff);
destinationArray[destinationIndex + 4] = frequency; // this is actually packed into 3 bits, but since it's only one why bother with the fancy code?
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// This effect resembles <see cref="Vibration(byte[], int, byte, byte, byte)">Vibration</see>
/// but will oscilate between two amplitudes.
/// This is not an offical effect and may be removed in a future DualSense firmware.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect. Must be between 0 and 8 inclusive.</param>
/// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/> and 9 inclusive.</param>
/// <param name="amplitudeA">Primary strength of cycling action. Must be between 0 and 7 inclusive.</param>
/// <param name="amplitudeB">Secondary strength of cycling action. Must be between 0 and 7 inclusive.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
/// <param name="period">Period of the oscillation between <paramref name="amplitudeA"/> and <paramref name="amplitudeB"/> in tenths of a second.</param>
/// <returns>The success of the effect write.</returns>
static public bool Machine(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte amplitudeA, byte amplitudeB, byte frequency, byte period)
{
if (startPosition > 8)
return false;
if (endPosition > 9)
return false;
if (endPosition <= startPosition)
return false;
if (amplitudeA > 7)
return false;
if (amplitudeB > 7)
return false;
if (frequency > 0)
{
UInt16 startAndStopZones = (UInt16)((1 << startPosition) | (1 << endPosition));
UInt32 strengthPair = (UInt32)(((amplitudeA & 0x07) << (3 * 0))
| ((amplitudeB & 0x07) << (3 * 1)));
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Machine;
destinationArray[destinationIndex + 1] = (byte)((startAndStopZones >> 0) & 0xff);
destinationArray[destinationIndex + 2] = (byte)((startAndStopZones >> 8) & 0xff);
destinationArray[destinationIndex + 3] = (byte)((strengthPair >> 0) & 0xff);
destinationArray[destinationIndex + 4] = frequency;
destinationArray[destinationIndex + 5] = period;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
#endregion Unofficial but Unique Effects
#region Simple Effects
/// <summary>
/// Simplistic Feedback effect data generator.
/// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
/// </summary>
/// <remarks>
/// Use <see cref="Feedback(byte[], int, byte, byte)"/> instead.
/// </remarks>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="position">The starting zone of the trigger effect.</param>
/// <param name="strength">The force of the resistance.</param>
/// <returns>The success of the effect write.</returns>
static public bool Simple_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Simple_Feedback;
destinationArray[destinationIndex + 1] = position;
destinationArray[destinationIndex + 2] = strength;
destinationArray[destinationIndex + 3] = 0x00;
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
/// <summary>
/// Simplistic Weapon effect data generator.
/// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
/// </summary>
/// <remarks>
/// Use <see cref="Weapon(byte[], int, byte, byte, byte)"/> instead.
/// </remarks>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect.</param>
/// <param name="endPosition">The ending zone of the trigger effect.</param>
/// <param name="strength">The force of the resistance.</param>
/// <returns>The success of the effect write.</returns>
static public bool Simple_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Simple_Weapon;
destinationArray[destinationIndex + 1] = startPosition;
destinationArray[destinationIndex + 2] = endPosition;
destinationArray[destinationIndex + 3] = strength;
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
/// <summary>
/// Simplistic Vibration effect data generator.
/// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
/// </summary>
/// <remarks>
/// Use <see cref="Vibration(byte[], int, byte, byte, byte)"/> instead.
/// </remarks>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="position">The starting zone of the trigger effect.</param>
/// <param name="amplitude">Strength of the automatic cycling action.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz.</param>
/// <returns>The success of the effect write.</returns>
static public bool Simple_Vibration(byte[] destinationArray, int destinationIndex, byte position, byte amplitude, byte frequency)
{
if (frequency > 0 && amplitude > 0)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Simple_Vibration;
destinationArray[destinationIndex + 1] = frequency;
destinationArray[destinationIndex + 2] = amplitude;
destinationArray[destinationIndex + 3] = position;
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
#endregion Simple Effects
#region Limited Effects
/// <summary>
/// Simplistic Feedback effect data generator with stricter paramater limits.
/// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
/// </summary>
/// <remarks>
/// Use <see cref="Feedback(byte[], int, byte, byte)"/> instead.
/// </remarks>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="position">The starting zone of the trigger effect.</param>
/// <param name="strength">The force of the resistance. Must be between 0 and 10 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Limited_Feedback(byte[] destinationArray, int destinationIndex, byte position, byte strength)
{
if (strength > 10)
return false;
if (strength > 0)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Limited_Feedback;
destinationArray[destinationIndex + 1] = position;
destinationArray[destinationIndex + 2] = strength;
destinationArray[destinationIndex + 3] = 0x00;
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
/// <summary>
/// Simplistic Weapon effect data generator with stricter paramater limits.
/// This is not an offical effect and has an offical alternative. It may be removed in a future DualSense firmware.
/// </summary>
/// <remarks>
/// Use <see cref="Weapon(byte[], int, byte, byte, byte)"/> instead.
/// </remarks>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">The starting zone of the trigger effect. Must be 16 or higher.</param>
/// <param name="endPosition">The ending zone of the trigger effect. Must be between <paramref name="startPosition"/> and <paramref name="startPosition"/>+100 inclusive.</param>
/// <param name="strength">The force of the resistance. Must be between 0 and 10 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Limited_Weapon(byte[] destinationArray, int destinationIndex, byte startPosition, byte endPosition, byte strength)
{
if (startPosition < 0x10)
return false;
if (endPosition < startPosition || (startPosition + 100) < endPosition)
return false;
if (strength > 10)
return false;
if (strength > 0)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Limited_Weapon;
destinationArray[destinationIndex + 1] = startPosition;
destinationArray[destinationIndex + 2] = endPosition;
destinationArray[destinationIndex + 3] = strength;
destinationArray[destinationIndex + 4] = 0x00;
destinationArray[destinationIndex + 5] = 0x00;
destinationArray[destinationIndex + 6] = 0x00;
destinationArray[destinationIndex + 7] = 0x00;
destinationArray[destinationIndex + 8] = 0x00;
destinationArray[destinationIndex + 9] = 0x00;
destinationArray[destinationIndex + 10] = 0x00;
return true;
}
return Off(destinationArray, destinationIndex);
}
#endregion Limited Effects
/// <summary>
/// Interface adapaters patterned after Apple's GCDualSenseAdaptiveTrigger classs.
/// </summary>
public static class Apple
{
/// <summary>
/// Sets the adaptive trigger to feedback mode. The start position and strength of the effect can be set arbitrarily. The trigger arm will continue to provide a
/// constant degree of feedback whenever it is depressed further than the start position.
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="Off(byte[], int)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool SetModeOff(byte[] destinationArray, int destinationIndex) =>
Off(destinationArray, destinationIndex);
/// <summary>
/// Sets the adaptive trigger to feedback mode. The start position and strength of the effect can be set arbitrarily. The trigger arm will continue to provide a
/// constant degree of feedback whenever it is depressed further than the start position.
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible trigger depression and 1 representing the maximum trigger depression.</param>
/// <param name="resistiveStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <returns>The success of the effect write.</returns>
static public bool SetModeFeedbackWithStartPosition(byte[] destinationArray, int destinationIndex, float startPosition, float resistiveStrength)
{
startPosition = (float)Math.Round(startPosition * 9.0f);
resistiveStrength = (float)Math.Round(resistiveStrength * 8.0f);
return Feedback(destinationArray, destinationIndex, (byte)startPosition, (byte)resistiveStrength);
}
/// <summary>
/// Sets the adaptive trigger to weapon mode. The start position, end position, and strength of the effect can be set arbitrarily; however the end position must be larger than the start position.
/// The trigger arm will continue to provide a constant degree of feedback whenever it is depressed further than the start position. Once the trigger arm has been depressed past the end
/// position, the strength of the effect will immediately fall to zero, providing a "sense of release" similar to that provided by pulling the trigger of a weapon.
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. The effect will begin once the trigger is depressed beyond this point.</param>
/// <param name="endPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. Must be greater than startPosition. The effect will end once the trigger is depressed beyond this point.</param>
/// <param name="resistiveStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <returns>The success of the effect write.</returns>
static public bool SetModeWeaponWithStartPosition(byte[] destinationArray, int destinationIndex, float startPosition, float endPosition, float resistiveStrength)
{
startPosition = (float)Math.Round(startPosition * 9.0f);
endPosition = (float)Math.Round(endPosition * 9.0f);
resistiveStrength = (float)Math.Round(resistiveStrength * 8.0f);
return Weapon(destinationArray, destinationIndex, (byte)startPosition, (byte)endPosition, (byte)resistiveStrength);
}
/// <summary>
/// Sets the adaptive trigger to vibration mode. The start position, amplitude, and frequency of the effect can be set arbitrarily. The trigger arm will continue to strike against
/// the trigger whenever it is depressed further than the start position, providing a "sense of vibration".
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="Vibration(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. The effect will begin once the trigger is depressed beyond this point.</param>
/// <param name="amplitude">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <param name="frequency">A normalized float from [0-1], with 0 representing the minimum frequency and 1 representing the maximum frequency of the vibration effect.</param>
/// <returns>The success of the effect write.</returns>
static public bool SetModeVibrationWithStartPosition(byte[] destinationArray, int destinationIndex, float startPosition, float amplitude, float frequency)
{
startPosition = (float)Math.Round(startPosition * 9.0f);
amplitude = (float)Math.Round(amplitude * 8.0f);
frequency = (float)Math.Round(frequency * 255.0f);
return Vibration(destinationArray, destinationIndex, (byte)startPosition, (byte)amplitude, (byte)frequency);
}
/// <summary>
/// Sets the adaptive trigger to feedback mode. The strength of the effect can be set arbitrarily per zone.
/// This implementation is not confirmed.
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="MultiplePositionFeedback(byte[], int, byte[])"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="positionalResistiveStrengths">An array of 10 normalized floats from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <returns>The success of the effect write.</returns>
static public bool SetModeFeedback(byte[] destinationArray, int destinationIndex, float[] positionalResistiveStrengths)
{
if (positionalResistiveStrengths.Length != 10) return false;
byte[] force = new byte[10];
for (int i = 0; i < 10; i++)
force[i] = (byte)Math.Round(positionalResistiveStrengths[i] * 8.0f);
return MultiplePositionFeedback(destinationArray, destinationIndex, force);
}
/// <summary>
/// Sets the adaptive trigger to feedback mode. The strength of the effect will change across zones based on a slope.
/// This implementation is not confirmed.
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="MultiplePositionFeedback(byte[], int, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="startPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. The effect will begin once the trigger is depressed beyond this point.</param>
/// <param name="endPosition">A normalized float from [0-1], with 0 representing the smallest possible depression and 1 representing the maximum trigger depression. Must be greater than startPosition. The effect will end once the trigger is depressed beyond this point.</param>
/// <param name="startStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <param name="endStrength">A normalized float from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <returns>The success of the effect write.</returns>
static public bool setModeSlopeFeedback(byte[] destinationArray, int destinationIndex, float startPosition, float endPosition, float startStrength, float endStrength)
{
startPosition = (float)Math.Round(startPosition * 9.0f);
endPosition = (float)Math.Round(endPosition * 9.0f);
startStrength = (float)Math.Round(startStrength * 8.0f);
endStrength = (float)Math.Round(endStrength * 8.0f);
return SlopeFeedback(destinationArray, destinationIndex, (byte)startPosition, (byte)endPosition, (byte)startStrength, (byte)endStrength);
}
/// <summary>
/// Sets the adaptive trigger to vibration mode. The frequency of the effect can be set arbitrarily and the amplitude arbitrarily per zone.
/// This implementation is not confirmed.
/// </summary>
/// <remarks>
/// Documentation ported from Apple's API Docs.
/// </remarks>
/// <seealso cref="MultiplePositionVibration(byte[], int, byte, byte[])"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="positionalAmplitudes">An array of 10 normalized floats from [0-1], with 0 representing the minimum effect strength (off entirely) and 1 representing the maximum effect strength.</param>
/// <param name="frequency">A normalized float from [0-1], with 0 representing the minimum frequency and 1 representing the maximum frequency of the vibration effect.</param>
/// <returns>The success of the effect write.</returns>
static public bool setModeVibration(byte[] destinationArray, int destinationIndex, float[] positionalAmplitudes, float frequency)
{
if (positionalAmplitudes.Length != 10) return false;
frequency = (float)Math.Round(frequency * 255.0f);
byte[] strength = new byte[10];
for (int i = 0; i < 10; i++)
strength[i] = (byte)Math.Round(positionalAmplitudes[i] * 8.0f);
return MultiplePositionVibration(destinationArray, destinationIndex, (byte)frequency, strength);
}
}
/// <summary>
/// Interface adapaters patterned after reWASD's actual interface.
/// </summary>
/// <remarks>
/// This information is based on sniffing the USB traffic from reWASD. Broken implementations are kept though immaterial inaccuracies are corrected.
/// </remarks>
public static class ReWASD
{
/// <summary>
/// Full Press trigger stop effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Weapon with a start value of 0x90, end value of 0xa0, and a force of 0xff.
/// </remarks>
/// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
/// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool FullPress(byte[] destinationArray, int destinationIndex) =>
Simple_Weapon(destinationArray, destinationIndex, 0x90, 0xa0, 0xff);
/// <summary>
/// Soft Press trigger stop effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Weapon with a start value of 0x70, end value of 0xa0, and a force of 0xff.
/// </remarks>
/// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
/// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool SoftPress(byte[] destinationArray, int destinationIndex) =>
Simple_Weapon(destinationArray, destinationIndex, 0x70, 0xa0, 0xff);
/// <summary>
/// Medium Press trigger stop effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Weapon with a start value of 0x45, end value of 0xa0, and a force of 0xff.
/// </remarks>
/// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
/// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool MediumPress(byte[] destinationArray, int destinationIndex) =>
Simple_Weapon(destinationArray, destinationIndex, 0x45, 0xa0, 0xff);
/// <summary>
/// Hard Press trigger stop effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Weapon with a start value of 0x20, end value of 0xa0, and a force of 0xff.
/// </remarks>
/// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
/// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool HardPress(byte[] destinationArray, int destinationIndex) =>
Simple_Weapon(destinationArray, destinationIndex, 0x20, 0xa0, 0xff);
/// <summary>
/// Pulse trigger stop effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Weapon with a start value of 0x00, end value of 0x00, and a force of 0x00.
/// </remarks>
/// <seealso cref="Simple_Weapon(byte[], int, byte, byte, byte)"/>
/// <seealso cref="Weapon(byte[], int, byte, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool Pulse(byte[] destinationArray, int destinationIndex) =>
Simple_Weapon(destinationArray, destinationIndex, 0x00, 0x00, 0x00);
/// <summary>
/// Choppy resistance effect data generator.
/// </summary>
/// <remarks>
/// Abuses Feedback effect to set a resistance in 3 of 10 trigger regions.
/// </remarks>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool Choppy(byte[] destinationArray, int destinationIndex)
{
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Feedback;
destinationArray[destinationIndex + 1] = (byte)0x02; // region enables
destinationArray[destinationIndex + 2] = (byte)0x27; // region enables
destinationArray[destinationIndex + 3] = (byte)0x18; // reWASD uses 0x1f here, but some bits apply to regions not enabled above
destinationArray[destinationIndex + 4] = (byte)0x00;
destinationArray[destinationIndex + 5] = (byte)0x00; // reWASD uses 0x27 here, but some bits apply to regions not enabled above
destinationArray[destinationIndex + 6] = (byte)0x26;
destinationArray[destinationIndex + 7] = (byte)0x00;
destinationArray[destinationIndex + 8] = (byte)0x00;
destinationArray[destinationIndex + 9] = (byte)0x00;
destinationArray[destinationIndex + 10] = (byte)0x00;
return true;
}
/// <summary>
/// Soft Rigidity feedback effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Feedback with a start value of 0x00 and a force of 0x00.
/// </remarks>
/// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool SoftRigidity(byte[] destinationArray, int destinationIndex) =>
Simple_Feedback(destinationArray, destinationIndex, 0x00, 0x00);
/// <summary>
/// Medium Rigidity feedback effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Feedback with a start value of 0x00 and a force of 0x64.
/// </remarks>
/// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool MediumRigidity(byte[] destinationArray, int destinationIndex) =>
Simple_Feedback(destinationArray, destinationIndex, 0x00, 0x64);
/// <summary>
/// Max Rigidity feedback effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Feedback with a start value of 0x00 and a force of 0xdc.
/// </remarks>
/// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool MaxRigidity(byte[] destinationArray, int destinationIndex) =>
Simple_Feedback(destinationArray, destinationIndex, 0x00, 0xdc);
/// <summary>
/// Half Press feedback effect data generator.
/// </summary>
/// <remarks>
/// Uses Simple_Feedback with a start value of 0x55 and a force of 0x64.
/// </remarks>
/// <seealso cref="Simple_Feedback(byte[], int, byte, byte)"/>
/// <seealso cref="Feedback(byte[], int, byte, byte)"/>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <returns>The success of the effect write.</returns>
static public bool HalfPress(byte[] destinationArray, int destinationIndex) =>
Simple_Feedback(destinationArray, destinationIndex, 0x55, 0x64);
/// <summary>
/// Rifle vibration effect data generator with some wasted bits.
/// Bad coding from reWASD was faithfully replicated.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz. Must be between 2 and 20 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Rifle(byte[] destinationArray, int destinationIndex, byte frequency = 10)
{
if (frequency < 2)
return false;
if (frequency > 20)
return false;
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Vibration;
destinationArray[destinationIndex + 1] = (byte)0x00;
destinationArray[destinationIndex + 2] = (byte)0x03; // reWASD uses 0xFF here but the top 6 bits are unused
destinationArray[destinationIndex + 3] = (byte)0x00;
destinationArray[destinationIndex + 4] = (byte)0x00;
destinationArray[destinationIndex + 5] = (byte)0x00;
destinationArray[destinationIndex + 6] = (byte)0x3F; // reWASD uses 0xFF here but the top 2 bits are unused
destinationArray[destinationIndex + 7] = (byte)0x00;
destinationArray[destinationIndex + 8] = (byte)0x00;
destinationArray[destinationIndex + 9] = frequency;
destinationArray[destinationIndex + 10] = (byte)0x00;
return true;
}
/// <summary>
/// Vibration vibration effect with incorrect strength handling.
/// Bad coding from reWASD was faithfully replicated.
/// </summary>
/// <param name="destinationArray">The byte[] that receives the data.</param>
/// <param name="destinationIndex">A 32-bit integer that represents the index in the destinationArray at which storing begins.</param>
/// <param name="strength">Strength of the automatic cycling action. Must be between 1 and 255 inclusive. This is two 3 bit numbers with the remaining 2 high bits unused. Yes, reWASD uses this value incorrectly.</param>
/// <param name="frequency">Frequency of the automatic cycling action in hertz. Must be between 1 and 255 inclusive.</param>
/// <returns>The success of the effect write.</returns>
static public bool Vibration(byte[] destinationArray, int destinationIndex, byte strength = 220, byte frequency = 30)
{
if (strength < 1)
return false;
if (frequency < 1)
return false;
destinationArray[destinationIndex + 0] = (byte)TriggerEffectType.Vibration;
destinationArray[destinationIndex + 1] = (byte)0x00; // reWASD uses 0x1E here but this is invalid and is ignored save for minor glitches
destinationArray[destinationIndex + 2] = (byte)0x03; // reWASD uses 0xFF here but the top 6 bits are unused
destinationArray[destinationIndex + 3] = (byte)0x00;
destinationArray[destinationIndex + 4] = (byte)0x00;
destinationArray[destinationIndex + 5] = (byte)0x00;
destinationArray[destinationIndex + 6] = strength; // reWASD maxes at 0xFF here but the top 2 bits are unused
destinationArray[destinationIndex + 7] = (byte)0x00;
destinationArray[destinationIndex + 8] = (byte)0x00;
destinationArray[destinationIndex + 9] = frequency;
destinationArray[destinationIndex + 10] = (byte)0x00;
return true;
}
}
}
}
Input Reports[]
struct TouchFingerData { // 4
/*0.0*/ uint32_t Index : 7;
/*0.7*/ uint32_t NotTouching : 1;
/*1.0*/ uint32_t FingerX : 12;
/*2.4*/ uint32_t FingerY : 12;
};
struct TouchData { // 9
/*0*/ TouchFingerData[2] Finger;
/*8*/ uint8_t Timestamp;
};
struct BTSimpleGetStateData { // 9
/*0 */ uint8_t LeftStickX;
/*1 */ uint8_t LeftStickY;
/*2 */ uint8_t RightStickX;
/*3 */ uint8_t RightStickY;
/*4.0*/ Direction DPad : 4;
/*4.4*/ uint8_t ButtonSquare : 1;
/*4.5*/ uint8_t ButtonCross : 1;
/*4.6*/ uint8_t ButtonCircle : 1;
/*4.7*/ uint8_t ButtonTriangle : 1;
/*5.0*/ uint8_t ButtonL1 : 1;
/*5.1*/ uint8_t ButtonR1 : 1;
/*5.2*/ uint8_t ButtonL2 : 1;
/*5.3*/ uint8_t ButtonR2 : 1;
/*5.4*/ uint8_t ButtonShare : 1;
/*5.5*/ uint8_t ButtonOptions : 1;
/*5.6*/ uint8_t ButtonL3 : 1;
/*5.7*/ uint8_t ButtonR3 : 1;
/*6.1*/ uint8_t ButtonHome : 1;
/*6.2*/ uint8_t ButtonPad : 1;
/*6.3*/ uint8_t Counter : 6;
/*7 */ uint8_t TriggerLeft;
/*8 */ uint8_t TriggerRight;
// anything beyond this point, if set, is invalid junk data that was not cleared
};
struct USBGetStateData { // 63
/* 0 */ uint8_t LeftStickX;
/* 1 */ uint8_t LeftStickY;
/* 2 */ uint8_t RightStickX;
/* 3 */ uint8_t RightStickY;
/* 4 */ uint8_t TriggerLeft;
/* 5 */ uint8_t TriggerRight;
/* 6 */ uint8_t SeqNo; // always 0x01 on BT
/* 7.0*/ Direction DPad : 4;
/* 7.4*/ uint8_t ButtonSquare : 1;
/* 7.5*/ uint8_t ButtonCross : 1;
/* 7.6*/ uint8_t ButtonCircle : 1;
/* 7.7*/ uint8_t ButtonTriangle : 1;
/* 8.0*/ uint8_t ButtonL1 : 1;
/* 8.1*/ uint8_t ButtonR1 : 1;
/* 8.2*/ uint8_t ButtonL2 : 1;
/* 8.3*/ uint8_t ButtonR2 : 1;
/* 8.4*/ uint8_t ButtonCreate : 1;
/* 8.5*/ uint8_t ButtonOptions : 1;
/* 8.6*/ uint8_t ButtonL3 : 1;
/* 8.7*/ uint8_t ButtonR3 : 1;
/* 9.0*/ uint8_t ButtonHome : 1;
/* 9.1*/ uint8_t ButtonPad : 1;
/* 9.2*/ uint8_t ButtonMute : 1;
/* 9.3*/ uint8_t UNK1 : 1; // appears unused
/* 9.4*/ uint8_t ButtonLeftFunction : 1; // DualSense Edge
/* 9.5*/ uint8_t ButtonRightFunction : 1; // DualSense Edge
/* 9.6*/ uint8_t ButtonLeftPaddle : 1; // DualSense Edge
/* 9.7*/ uint8_t ButtonRightPaddle : 1; // DualSense Edge
/*10 */ uint8_t UNK2; // appears unused
/*11 */ uint32_t UNK_COUNTER; // Linux driver calls this reserved, tools leak calls the 2 high bytes "random"
/*15 */ int16_t AngularVelocityX;
/*17 */ int16_t AngularVelocityZ;
/*19 */ int16_t AngularVelocityY;
/*21 */ int16_t AccelerometerX;
/*23 */ int16_t AccelerometerY;
/*25 */ int16_t AccelerometerZ;
/*27 */ uint32_t SensorTimestamp;
/*31 */ int8_t Temperature; // reserved2 in Linux driver
/*32 */ TouchData TouchData;
/*41.0*/ uint8_t TriggerRightStopLocation: 4; // trigger stop can be a range from 0 to 9 (F/9.0 for Apple interface)
/*41.4*/ uint8_t TriggerRightStatus: 4;
/*42.0*/ uint8_t TriggerLeftStopLocation: 4;
/*42.4*/ uint8_t TriggerLeftStatus: 4; // 0 feedbackNoLoad
// 1 feedbackLoadApplied
// 0 weaponReady
// 1 weaponFiring
// 2 weaponFired
// 0 vibrationNotVibrating
// 1 vibrationIsVibrating
/*43 */ uint32_t HostTimestamp; // mirrors data from report write
/*47.0*/ uint8_t TriggerRightEffect: 4; // Active trigger effect, previously we thought this was status max
/*47.4*/ uint8_t TriggerLeftEffect: 4; // 0 for reset and all other effects
// 1 for feedback effect
// 2 for weapon effect
// 3 for vibration
/*48 */ uint32_t DeviceTimeStamp;
/*52.0*/ uint8_t PowerPercent : 4; // 0x00-0x0A
/*52.4*/ PowerState PowerState : 4;
/*53.0*/ uint8_t PluggedHeadphones : 1;
/*53.1*/ uint8_t PluggedMic : 1;
/*53.2*/ uint8_t MicMuted: 1; // Mic muted by powersave/mute command
/*53.3*/ uint8_t PluggedUsbData : 1;
/*53.4*/ uint8_t PluggedUsbPower : 1;
/*53.5*/ uint8_t PluggedUnk1 : 3;
/*54.0*/ uint8_t PluggedExternalMic : 1; // Is external mic active (automatic in mic auto mode)
/*54.1*/ uint8_t HapticLowPassFilter : 1; // Is the Haptic Low-Pass-Filter active?
/*54.2*/ uint8_t PluggedUnk3 : 6;
/*55 */ uint8_t[8] AesCmac;
};
struct BTGetStateData { // 77
/* 0*/ USBGetStateData StateData;
/*63*/ uint8_t UNK1; // Oscillates between 00101100 and 00101101 when rumbling
// Not affected by rumble volume or enhanced vs normal rumble
// Audio rumble not yet tested as this is only on BT
/*64*/ uint8_t BtCrcFailCount;
/*65*/ uint8_t[11] Pad;
};
HID Report 0x01 Input USB[]
struct ReportIn01USB {
uint8_t ReportID; // 0x01
USBGetStateData State;
};
HID Report 0x01 Input BT[]
struct ReportIn01BT {
uint8_t ReportID; // 0x01
BTSimpleGetStateData State;
};
HID Report 0x31 Input BT[]
struct ReportIn31 {
union {
BTCRC<78> CRC;
struct {
uint8_t ReportID; // 0x31
uint8_t HasHID : 1; // Present for packets with state data
uint8_t HasMic : 1; // Looks mutually exclusive, possible mic data
uint8_t Unk1 : 2;
uint8_t SeqNo : 4; // unclear progression
BTGetStateData State;
} Data;
}
};
Output Reports[]
struct SetStateData { // 47
/* */ // Report Set Flags
/* */ // These flags are used to indicate what contents from this report should be processed
/* 0.0*/ uint8_t EnableRumbleEmulation: 1; // Suggest halving rumble strength
/* 0.1*/ uint8_t UseRumbleNotHaptics: 1; //
/* */
/* 0.2*/ uint8_t AllowRightTriggerFFB: 1; // Enable setting RightTriggerFFB
/* 0.3*/ uint8_t AllowLeftTriggerFFB: 1; // Enable setting LeftTriggerFFB
/* */
/* 0.4*/ uint8_t AllowHeadphoneVolume: 1; // Enable setting VolumeHeadphones
/* 0.5*/ uint8_t AllowSpeakerVolume: 1; // Enable setting VolumeSpeaker
/* 0.6*/ uint8_t AllowMicVolume: 1; // Enable setting VolumeMic
/* */
/* 0.7*/ uint8_t AllowAudioControl: 1; // Enable setting AudioControl section
/* 1.0*/ uint8_t AllowMuteLight: 1; // Enable setting MuteLightMode
/* 1.1*/ uint8_t AllowAudioMute: 1; // Enable setting MuteControl section
/* */
/* 1.2*/ uint8_t AllowLedColor: 1; // Enable RGB LED section
/* */
/* 1.3*/ uint8_t ResetLights: 1; // Release the LEDs from Wireless firmware control
/* */ // When in wireless mode this must be signaled to control LEDs
/* */ // This cannot be applied during the BT pair animation.
/* */ // SDL2 waits until the SensorTimestamp value is >= 10200000
/* */ // before pulsing this bit once.
/* */
/* 1.4*/ uint8_t AllowPlayerIndicators: 1; // Enable setting PlayerIndicators section
/* 1.5*/ uint8_t AllowHapticLowPassFilter: 1; // Enable HapticLowPassFilter
/* 1.6*/ uint8_t AllowMotorPowerLevel: 1; // MotorPowerLevel reductions for trigger/haptic
/* 1.7*/ uint8_t AllowAudioControl2: 1; // Enable setting AudioControl2 section
/* */
/* 2 */ uint8_t RumbleEmulationRight; // emulates the light weight
/* 3 */ uint8_t RumbleEmulationLeft; // emulated the heavy weight
/* */
/* 4 */ uint8_t VolumeHeadphones; // max 0x7f
/* 5 */ uint8_t VolumeSpeaker; // PS5 appears to only use the range 0x3d-0x64
/* 6 */ uint8_t VolumeMic; // not linier, seems to max at 64, 0 is not fully muted
/* */
/* */ // AudioControl
/* 7.0*/ uint8_t MicSelect: 2; // 0 Auto
/* */ // 1 Internal Only
/* */ // 2 External Only
/* */ // 3 Unclear, sets external mic flag but might use internal mic, do test
/* 7.2*/ uint8_t EchoCancelEnable: 1;
/* 7.3*/ uint8_t NoiseCancelEnable: 1;
/* 7.4*/ uint8_t OutputPathSelect: 2; // 0 L_R_X
/* */ // 1 L_L_X
/* */ // 2 L_L_R
/* */ // 3 X_X_R
/* 7.6*/ uint8_t InputPathSelect: 2; // 0 CHAT_ASR
/* */ // 1 CHAT_CHAT
/* */ // 2 ASR_ASR
/* */ // 3 Does Nothing, invalid
/* */
/* 8 */ MuteLight MuteLightMode;
/* */
/* */ // MuteControl
/* 9.0*/ uint8_t TouchPowerSave: 1;
/* 9.1*/ uint8_t MotionPowerSave: 1;
/* 9.2*/ uint8_t HapticPowerSave: 1; // AKA BulletPowerSave
/* 9.3*/ uint8_t AudioPowerSave: 1;
/* 9.4*/ uint8_t MicMute: 1;
/* 9.5*/ uint8_t SpeakerMute: 1;
/* 9.6*/ uint8_t HeadphoneMute: 1;
/* 9.7*/ uint8_t HapticMute: 1; // AKA BulletMute
/* */
/*10 */ uint8_t[11] RightTriggerFFB;
/*21 */ uint8_t[11] LeftTriggerFFB;
/*32 */ uint32_t HostTimestamp; // mirrored into report read
/* */
/* */ // MotorPowerLevel
/*36.0*/ uint8_t TriggerMotorPowerReduction : 4; // 0x0-0x7 (no 0x8?) Applied in 12.5% reductions
/*36.4*/ uint8_t RumbleMotorPowerReduction : 4; // 0x0-0x7 (no 0x8?) Applied in 12.5% reductions
/* */
/* */ // AudioControl2
/*37.0*/ uint8_t SpeakerCompPreGain: 3; // additional speaker volume boost
/*37.3*/ uint8_t BeamformingEnable: 1; // Probably for MIC given there's 2, might be more bits, can't find what it does
/*37.4*/ uint8_t UnkAudioControl2: 4; // some of these bits might apply to the above
/* */
/*38.0*/ uint8_t AllowLightBrightnessChange: 1; // LED_BRIHTNESS_CONTROL
/*38.1*/ uint8_t AllowColorLightFadeAnimation: 1; // LIGHTBAR_SETUP_CONTROL
/*38.2*/ uint8_t EnableImprovedRumbleEmulation: 1; // Use instead of EnableRumbleEmulation
// requires FW >= 0x0224
// No need to halve rumble strength
/*38.3*/ uint8_t UNKBITC: 5; // unused
/* */
/*39.0*/ uint8_t HapticLowPassFilter: 1;
/*39.1*/ uint8_t UNKBIT: 7;
/* */
/*40 */ uint8_t UNKBYTE; // previous notes suggested this was HLPF, was probably off by 1
/* */
/*41 */ LightFadeAnimation LightFadeAnimation;
/*42 */ LightBrightness LightBrightness;
/* */
/* */ // PlayerIndicators
/* */ // These bits control the white LEDs under the touch pad.
/* */ // Note the reduction in functionality for later revisions.
/* */ // Generation 0x03 - Full Functionality
/* */ // Generation 0x04 - Mirrored Only
/* */ // Suggested detection: (HardwareInfo & 0x00FFFF00) == 0X00000400
/* */ //
/* */ // Layout used by PS5:
/* */ // 0x04 - -x- - Player 1
/* */ // 0x06 - x-x - Player 2
/* */ // 0x15 x -x- x Player 3
/* */ // 0x1B x x-x x Player 4
/* */ // 0x1F x xxx x Player 5* (Unconfirmed)
/* */ //
/* */ // // HW 0x03 // HW 0x04
/*43.0*/ uint8_t PlayerLight1 : 1; // x --- - // x --- x
/*43.1*/ uint8_t PlayerLight2 : 1; // - x-- - // - x-x -
/*43.2*/ uint8_t PlayerLight3 : 1; // - -x- - // - -x- -
/*43.3*/ uint8_t PlayerLight4 : 1; // - --x - // - x-x -
/*43.4*/ uint8_t PlayerLight5 : 1; // - --- x // x --- x
/*43.5*/ uint8_t PlayerLightFade: 1; // if low player lights fade in, if high player lights instantly change
/*43.6*/ uint8_t PlayerLightUNK : 2;
/* */
/* */ // RGB LED
/*44 */ uint8_t LedRed;
/*45 */ uint8_t LedGreen
/*46 */ uint8_t LedBlue;
// Structure ends here though on BT there is padding and a CRC, see ReportOut31
};
HID Report 0x02 Output USB[]
struct ReportOut02 {
uint8_t ReportID; // 0x02
USBSetStateData State;
};
HID Report 0x31 Output BT[]
struct ReportOut31 {
union {
BTCRC<78> CRC;
struct {
uint8_t ReportID; // 0x31
uint8_t UNK1 : 1; // -+
uint8_t EnableHID : 1; // | - these 3 bits seem to act as an enum
uint8_t UNK2 : 1; // -+
uint8_t UNK3 : 1;
uint8_t SeqNo : 4; // increment for every write // we have no proof of this, need to see some PS5 captures
SetStateData State;
} Data;
}
};
Feature Reports[]
Calibration[]
Reading calibration is required to switch BT input reports from the truncated 0x01 report to the expanded 0x31 report.
Linux hid-sony.c (does this apply to the DualSense?)
/* Set gyroscope calibration and normalization parameters.
* Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
*/
speed_2x = (gyro_speed_plus + gyro_speed_minus);
sc->ds4_calib_data[0].abs_code = ABS_RX;
sc->ds4_calib_data[0].bias = gyro_pitch_bias;
sc->ds4_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
sc->ds4_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;
sc->ds4_calib_data[1].abs_code = ABS_RY;
sc->ds4_calib_data[1].bias = gyro_yaw_bias;
sc->ds4_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
sc->ds4_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;
sc->ds4_calib_data[2].abs_code = ABS_RZ;
sc->ds4_calib_data[2].bias = gyro_roll_bias;
sc->ds4_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
sc->ds4_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;
/* Set accelerometer calibration and normalization parameters.
* Data values will be normalized to 1/DS4_ACC_RES_PER_G G.
*/
range_2g = acc_x_plus - acc_x_minus;
sc->ds4_calib_data[3].abs_code = ABS_X;
sc->ds4_calib_data[3].bias = acc_x_plus - range_2g / 2;
sc->ds4_calib_data[3].sens_numer = 2*DS4_ACC_RES_PER_G;
sc->ds4_calib_data[3].sens_denom = range_2g;
range_2g = acc_y_plus - acc_y_minus;
sc->ds4_calib_data[4].abs_code = ABS_Y;
sc->ds4_calib_data[4].bias = acc_y_plus - range_2g / 2;
sc->ds4_calib_data[4].sens_numer = 2*DS4_ACC_RES_PER_G;
sc->ds4_calib_data[4].sens_denom = range_2g;
range_2g = acc_z_plus - acc_z_minus;
sc->ds4_calib_data[5].abs_code = ABS_Z;
sc->ds4_calib_data[5].bias = acc_z_plus - range_2g / 2;
sc->ds4_calib_data[5].sens_numer = 2*DS4_ACC_RES_PER_G;
sc->ds4_calib_data[5].sens_denom = range_2g;
Bluetooth 0x05[]
struct ReportFeatureInCalibrateBT {
union {
BTCRC<41> CRC;
struct {
uint8_t ReportID; // 0x05 // does this exist on USB? confirm
int16_t GyroPitchBias;
int16_t GyroYawBias;
int16_t GyroRollBias;
int16_t GyroPitchPlus;
int16_t GyroPitchMinus;
int16_t GyroYawPlus;
int16_t GyroYawMinus;
int16_t GyroRollPlus;
int16_t GyroRollMinus;
int16_t GyroSpeedPlus;
int16_t GyroSpeedMinus;
int16_t AccelXPlus;
int16_t AccelXMinus;
int16_t AccelYPlus;
int16_t AccelYMinus;
int16_t AccelZPlus;
int16_t AccelZMinus;
int16_t Unknown;
} Data;
}
};
MAC[]
Need to confirm these on BT
Get All MAC USB[]
struct ReportFeatureInMacAll {
uint8_t ReportID; // 0x09
uint8_t[6] ClientMac; // Right to Left
uint8_t Hard08;
uint8_t Hard25;
uint8_t Hard00;
uint8_t[6] HostMac; // Right to Left
uint8_t[3] Pad; // Size according to Linux driver
};
Date and Version[]
Date/Version 0x20 USB/BT[]
struct ReportFeatureInVersion {
union {
BTCRC<64> CRC;
struct {
uint8_t ReportID; // 0x20
char[11] BuildDate; // string
char[8] BuildTime; // string
uint16_t FwType;
uint16_t SwSeries;
uint32_t HardwareInfo; // 0x00FF0000 - Variation
// 0x0000FF00 - Generation
// 0x0000003F - Trial?
// ^ Values tied to enumerations
uint32_t FirmwareVersion; // 0xAABBCCCC AA.BB.CCCC
char[12] DeviceInfo;
////
uint16_t UpdateVersion;
char UpdateImageInfo;
char UpdateUnk;
////
uint32_t FwVersion1; // AKA SblFwVersion
// 0xAABBCCCC AA.BB.CCCC
// Ignored for FwType 0
// HardwareVersion used for FwType 1
// Unknown behavior if HardwareVersion < 0.1.38 for FwType 2 & 3
// If HardwareVersion >= 0.1.38 for FwType 2 & 3
uint32_t FwVersion2; // AKA VenomFwVersion
uint32_t FwVersion3; // AKA SpiderDspFwVersion AKA BettyFwVer
// May be Memory Control Unit for Non Volatile Storage
}
}
};