├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── ForzaDualSense
├── CsvData.cs
├── DataPacket.cs
├── FMData.cs
├── ForzaDualSense.csproj
├── PacketParse.cs
├── Program.cs
├── Settings.cs
├── appsettings.ini
└── obj
│ ├── ForzaDualSense.csproj.nuget.dgspec.json
│ ├── ForzaDualSense.csproj.nuget.g.props
│ ├── ForzaDualSense.csproj.nuget.g.targets
│ ├── project.assets.json
│ └── project.nuget.cache
└── Readme.md
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Problem Description
11 |
12 | ## Debug Ouput
13 | *Run the Program with the `--verbose option` i.e. `ForzaDualSense.exe --verbose`*
14 |
15 | ## System Details
16 | 1. OS Version and Build
17 | 2. Forza Edition
18 | 3. DSX Version
19 | 4. Where is Forza running from(Steam, Xbox, etc)
20 |
21 | ## Steps to reproduce(If applicable)
22 |
23 | ## Try disabling the firewall (Only if it is not working at all)
24 | It is possible, try disabling the firewall to see if that allows the program to work
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ForzaDualSense/bin/*
2 | ForzaDualSense/obj/*
3 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 |
5 |
6 | {
7 | // Use IntelliSense to find out which attributes exist for C# debugging
8 | // Use hover for the description of the existing attributes
9 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
10 | "name": ".NET Core Launch (console)",
11 | "type": "coreclr",
12 | "request": "launch",
13 | "preLaunchTask": "build",
14 | // If you have changed target frameworks, make sure to update the program path.
15 | "program": "${workspaceFolder}/ForzaDualSense/bin/Debug/net6.0/win-x64/ForzaDualSense.exe",
16 | "args": [],
17 | "cwd": "${workspaceFolder}/ForzaDualSense",
18 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
19 | "console": "internalConsole",
20 | "stopAtEntry": false
21 | },
22 | {
23 | "name": ".NET Core Attach",
24 | "type": "coreclr",
25 | "request": "attach"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/ForzaDualSense/ForzaDualSense.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "--configuration",
23 | "release",
24 | "${workspaceFolder}/ForzaDualSense/ForzaDualSense.csproj",
25 | "/property:GenerateFullPaths=true",
26 | "/consoleloggerparameters:NoSummary"
27 | ],
28 | "problemMatcher": "$msCompile"
29 | },
30 | {
31 | "label": "watch",
32 | "command": "dotnet",
33 | "type": "process",
34 | "args": [
35 | "watch",
36 | "run",
37 | "${workspaceFolder}/ForzaDualSense/ForzaDualSense.csproj",
38 | "/property:GenerateFullPaths=true",
39 | "/consoleloggerparameters:NoSummary"
40 | ],
41 | "problemMatcher": "$msCompile"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/ForzaDualSense/CsvData.cs:
--------------------------------------------------------------------------------
1 | namespace ForzaDualSense
2 | {
3 | public class CsvData
4 | {
5 | public uint time { get; set; }
6 | public float AccelerationX { get; set; }
7 | public float AccelerationY { get; set; }
8 | public float AccelerationZ { get; set; }
9 | public float Brake { get; set; }
10 | public float TireCombinedSlipFrontLeft { get; set; }
11 | public float TireCombinedSlipFrontRight { get; set; }
12 | public float TireCombinedSlipRearLeft { get; set; }
13 | public float TireCombinedSlipRearRight { get; set; }
14 | public float combinedTireSlip { get; set; }
15 | public float AverageAcceleration { get; set; }
16 | public float CurrentEngineRpm { get; set; }
17 | public int ThrottleResistance { get; set; }
18 | public int ThrottleResistance_filtered { get; set; }
19 | public int BrakeResistance { get; set; }
20 | public int BrakeResistance_filtered { get; set; }
21 | public int BrakeVibrationFrequency { get; set; }
22 | public int BrakeVibrationFrequency_freq { get; set; }
23 | }
24 | }
--------------------------------------------------------------------------------
/ForzaDualSense/DataPacket.cs:
--------------------------------------------------------------------------------
1 | namespace ForzaDualSense
2 | {
3 | public class DataPacket
4 | {
5 | // Sled
6 | public bool IsRaceOn { get; set; }
7 | public uint TimestampMS { get; set; } // Can overflow to 0 eventually
8 | public float EngineMaxRpm { get; set; }
9 | public float EngineIdleRpm { get; set; }
10 | public float CurrentEngineRpm { get; set; }
11 | public float AccelerationX { get; set; } // In the car's local space; X = right, Y = up, Z = forward
12 | public float AccelerationY { get; set; }
13 | public float AccelerationZ { get; set; }
14 | public float VelocityX { get; set; } // In the car's local space; X = right, Y = up, Z = forward
15 | public float VelocityY { get; set; }
16 | public float VelocityZ { get; set; }
17 | public float AngularVelocityX { get; set; } // In the car's local space; X = pitch, Y = yaw, Z = roll
18 | public float AngularVelocityY { get; set; }
19 | public float AngularVelocityZ { get; set; }
20 | public float Yaw { get; set; }
21 | public float Pitch { get; set; }
22 | public float Roll { get; set; }
23 | public float NormalizedSuspensionTravelFrontLeft { get; set; } // Suspension travel normalized: 0.0f = max stretch; 1.0 = max compression
24 | public float NormalizedSuspensionTravelFrontRight { get; set; }
25 | public float NormalizedSuspensionTravelRearLeft { get; set; }
26 | public float NormalizedSuspensionTravelRearRight { get; set; }
27 | public float TireSlipRatioFrontLeft { get; set; } // Tire normalized slip ratio, = 0 means 100% grip and |ratio| > 1.0 means loss of grip.
28 | public float TireSlipRatioFrontRight { get; set; }
29 | public float TireSlipRatioRearLeft { get; set; }
30 | public float TireSlipRatioRearRight { get; set; }
31 | public float WheelRotationSpeedFrontLeft { get; set; } // Wheel rotation speed radians/sec.
32 | public float WheelRotationSpeedFrontRight { get; set; }
33 | public float WheelRotationSpeedRearLeft { get; set; }
34 | public float WheelRotationSpeedRearRight { get; set; }
35 | public float WheelOnRumbleStripFrontLeft { get; set; } // = 1 when wheel is on rumble strip, = 0 when off.
36 | public float WheelOnRumbleStripFrontRight { get; set; }
37 | public float WheelOnRumbleStripRearLeft { get; set; }
38 | public float WheelOnRumbleStripRearRight { get; set; }
39 | public float WheelInPuddleDepthFrontLeft { get; set; } // = from 0 to 1, where 1 is the deepest puddle
40 | public float WheelInPuddleDepthFrontRight { get; set; }
41 | public float WheelInPuddleDepthRearLeft { get; set; }
42 | public float WheelInPuddleDepthRearRight { get; set; }
43 | public float SurfaceRumbleFrontLeft { get; set; } // Non-dimensional surface rumble values passed to controller force feedback
44 | public float SurfaceRumbleFrontRight { get; set; }
45 | public float SurfaceRumbleRearLeft { get; set; }
46 | public float SurfaceRumbleRearRight { get; set; }
47 | public float TireSlipAngleFrontLeft { get; set; } // Tire normalized slip angle, = 0 means 100% grip and |angle| > 1.0 means loss of grip.
48 | public float TireSlipAngleFrontRight { get; set; }
49 | public float TireSlipAngleRearLeft { get; set; }
50 | public float TireSlipAngleRearRight { get; set; }
51 | public float TireCombinedSlipFrontLeft { get; set; } // Tire normalized combined slip, = 0 means 100% grip and |slip| > 1.0 means loss of grip.
52 | public float TireCombinedSlipFrontRight { get; set; }
53 | public float TireCombinedSlipRearLeft { get; set; }
54 | public float TireCombinedSlipRearRight { get; set; }
55 | public float SuspensionTravelMetersFrontLeft { get; set; } // Actual suspension travel in meters
56 | public float SuspensionTravelMetersFrontRight { get; set; }
57 | public float SuspensionTravelMetersRearLeft { get; set; }
58 | public float SuspensionTravelMetersRearRight { get; set; }
59 | public uint CarOrdinal { get; set; } // Unique ID of the car make/model
60 | public uint CarClass { get; set; } // Between 0 (D -- worst cars) and 7 (X class -- best cars) inclusive
61 | public uint CarPerformanceIndex { get; set; } // Between 100 (slowest car) and 999 (fastest car) inclusive
62 | public uint DrivetrainType { get; set; } // Corresponds to EDrivetrainType; 0 = FWD, 1 = RWD, 2 = AWD
63 | public uint NumCylinders { get; set; } // Number of cylinders in the engine
64 |
65 | // Dash
66 | public float PositionX { get; set; }
67 | public float PositionY { get; set; }
68 | public float PositionZ { get; set; }
69 | public float Speed { get; set; }
70 | public float Power { get; set; }
71 | public float Torque { get; set; }
72 | public float TireTempFl { get; set; }
73 | public float TireTempFr { get; set; }
74 | public float TireTempRl { get; set; }
75 | public float TireTempRr { get; set; }
76 | public float Boost { get; set; }
77 | public float Fuel { get; set; }
78 | public float Distance { get; set; }
79 | public float BestLapTime { get; set; }
80 | public float LastLapTime { get; set; }
81 | public float CurrentLapTime { get; set; }
82 | public float CurrentRaceTime { get; set; }
83 | public uint Lap { get; set; }
84 | public uint RacePosition { get; set; }
85 | public uint Accelerator { get; set; }
86 | public uint Brake { get; set; }
87 | public uint Clutch { get; set; }
88 | public uint Handbrake { get; set; }
89 | public uint Gear { get; set; }
90 | public int Steer { get; set; }
91 | public uint NormalDrivingLine { get; set; }
92 | public uint NormalAiBrakeDifference { get; set; }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ForzaDualSense/FMData.cs:
--------------------------------------------------------------------------------
1 | using static ForzaDualSense.PacketParse;
2 |
3 | namespace ForzaDualSense
4 | {
5 | public static class FMData
6 | {
7 | public static int BufferOffset = 0;
8 |
9 | // sled
10 | public static bool IsRaceOn(this byte[] bytes) { return GetSingle(bytes, 0) > 0; }
11 | public static uint TimestampMs(this byte[] bytes) { return GetUInt32(bytes, 4); }
12 | public static float EngineMaxRpm(this byte[] bytes) { return GetSingle(bytes, 8); }
13 | public static float EngineIdleRpm(this byte[] bytes) { return GetSingle(bytes, 12); }
14 | public static float CurrentEngineRpm(this byte[] bytes) { return GetSingle(bytes, 16); }
15 | public static float AccelerationX(this byte[] bytes) { return GetSingle(bytes, 20); }
16 | public static float AccelerationY(this byte[] bytes) { return GetSingle(bytes, 24); }
17 | public static float AccelerationZ(this byte[] bytes) { return GetSingle(bytes, 28); }
18 | public static float VelocityX(this byte[] bytes) { return GetSingle(bytes, 32); }
19 | public static float VelocityY(this byte[] bytes) { return GetSingle(bytes, 36); }
20 | public static float VelocityZ(this byte[] bytes) { return GetSingle(bytes, 40); }
21 | public static float AngularVelocityX(this byte[] bytes) { return GetSingle(bytes, 44); }
22 | public static float AngularVelocityY(this byte[] bytes) { return GetSingle(bytes, 48); }
23 | public static float AngularVelocityZ(this byte[] bytes) { return GetSingle(bytes, 52); }
24 | public static float Yaw(this byte[] bytes) { return GetSingle(bytes, 56); }
25 | public static float Pitch(this byte[] bytes) { return GetSingle(bytes, 60); }
26 | public static float Roll(this byte[] bytes) { return GetSingle(bytes, 64); }
27 | public static float NormSuspensionTravelFl(this byte[] bytes) { return GetSingle(bytes, 68); }
28 | public static float NormSuspensionTravelFr(this byte[] bytes) { return GetSingle(bytes, 72); }
29 | public static float NormSuspensionTravelRl(this byte[] bytes) { return GetSingle(bytes, 76); }
30 | public static float NormSuspensionTravelRr(this byte[] bytes) { return GetSingle(bytes, 80); }
31 | public static float TireSlipRatioFl(this byte[] bytes) { return GetSingle(bytes, 84); }
32 | public static float TireSlipRatioFr(this byte[] bytes) { return GetSingle(bytes, 88); }
33 | public static float TireSlipRatioRl(this byte[] bytes) { return GetSingle(bytes, 92); }
34 | public static float TireSlipRatioRr(this byte[] bytes) { return GetSingle(bytes, 96); }
35 | public static float WheelRotationSpeedFl(this byte[] bytes) { return GetSingle(bytes, 100); }
36 | public static float WheelRotationSpeedFr(this byte[] bytes) { return GetSingle(bytes, 104); }
37 | public static float WheelRotationSpeedRl(this byte[] bytes) { return GetSingle(bytes, 108); }
38 | public static float WheelRotationSpeedRr(this byte[] bytes) { return GetSingle(bytes, 112); }
39 | public static float WheelOnRumbleStripFl(this byte[] bytes) { return GetSingle(bytes, 116); }
40 | public static float WheelOnRumbleStripFr(this byte[] bytes) { return GetSingle(bytes, 120); }
41 | public static float WheelOnRumbleStripRl(this byte[] bytes) { return GetSingle(bytes, 124); }
42 | public static float WheelOnRumbleStripRr(this byte[] bytes) { return GetSingle(bytes, 128); }
43 | public static float WheelInPuddleFl(this byte[] bytes) { return GetSingle(bytes, 132); }
44 | public static float WheelInPuddleFr(this byte[] bytes) { return GetSingle(bytes, 136); }
45 | public static float WheelInPuddleRl(this byte[] bytes) { return GetSingle(bytes, 140); }
46 | public static float WheelInPuddleRr(this byte[] bytes) { return GetSingle(bytes, 144); }
47 | public static float SurfaceRumbleFl(this byte[] bytes) { return GetSingle(bytes, 148); }
48 | public static float SurfaceRumbleFr(this byte[] bytes) { return GetSingle(bytes, 152); }
49 | public static float SurfaceRumbleRl(this byte[] bytes) { return GetSingle(bytes, 156); }
50 | public static float SurfaceRumbleRr(this byte[] bytes) { return GetSingle(bytes, 160); }
51 | public static float TireSlipAngleFl(this byte[] bytes) { return GetSingle(bytes, 164); }
52 | public static float TireSlipAngleFr(this byte[] bytes) { return GetSingle(bytes, 168); }
53 | public static float TireSlipAngleRl(this byte[] bytes) { return GetSingle(bytes, 172); }
54 | public static float TireSlipAngleRr(this byte[] bytes) { return GetSingle(bytes, 176); }
55 | public static float TireCombinedSlipFl(this byte[] bytes) { return GetSingle(bytes, 180); }
56 | public static float TireCombinedSlipFr(this byte[] bytes) { return GetSingle(bytes, 184); }
57 | public static float TireCombinedSlipRl(this byte[] bytes) { return GetSingle(bytes, 188); }
58 | public static float TireCombinedSlipRr(this byte[] bytes) { return GetSingle(bytes, 192); }
59 | public static float SuspensionTravelMetersFl(this byte[] bytes) { return GetSingle(bytes, 196); }
60 | public static float SuspensionTravelMetersFr(this byte[] bytes) { return GetSingle(bytes, 200); }
61 | public static float SuspensionTravelMetersRl(this byte[] bytes) { return GetSingle(bytes, 204); }
62 | public static float SuspensionTravelMetersRr(this byte[] bytes) { return GetSingle(bytes, 208); }
63 | public static uint CarOrdinal(this byte[] bytes) { return GetUInt8(bytes, 212); }
64 | public static uint CarClass(this byte[] bytes) { return GetUInt8(bytes, 216); }
65 | public static uint CarPerformanceIndex(this byte[] bytes) { return GetUInt8(bytes, 220); }
66 | public static uint DriveTrain(this byte[] bytes) { return GetUInt8(bytes, 224); }
67 | public static uint NumCylinders(this byte[] bytes) { return GetUInt8(bytes, 228); }
68 |
69 | // dash
70 | public static float PositionX(this byte[] bytes) { return GetSingle(bytes, 232 + BufferOffset); }
71 | public static float PositionY(this byte[] bytes) { return GetSingle(bytes, 236 + BufferOffset); }
72 | public static float PositionZ(this byte[] bytes) { return GetSingle(bytes, 240 + BufferOffset); }
73 | public static float Speed(this byte[] bytes) { return GetSingle(bytes, 244 + BufferOffset); }
74 | public static float Power(this byte[] bytes) { return GetSingle(bytes, 248 + BufferOffset); }
75 | public static float Torque(this byte[] bytes) { return GetSingle(bytes, 252 + BufferOffset); }
76 | public static float TireTempFl(this byte[] bytes) { return GetSingle(bytes, 256 + BufferOffset); }
77 | public static float TireTempFr(this byte[] bytes) { return GetSingle(bytes, 260 + BufferOffset); }
78 | public static float TireTempRl(this byte[] bytes) { return GetSingle(bytes, 264 + BufferOffset); }
79 | public static float TireTempRr(this byte[] bytes) { return GetSingle(bytes, 268 + BufferOffset); }
80 | public static float Boost(this byte[] bytes) { return GetSingle(bytes, 272 + BufferOffset); }
81 | public static float Fuel(this byte[] bytes) { return GetSingle(bytes, 276 + BufferOffset); }
82 | public static float Distance(this byte[] bytes) { return GetSingle(bytes, 280 + BufferOffset); }
83 | public static float BestLapTime(this byte[] bytes) { return GetSingle(bytes, 284 + BufferOffset); }
84 | public static float LastLapTime(this byte[] bytes) { return GetSingle(bytes, 288 + BufferOffset); }
85 | public static float CurrentLapTime(this byte[] bytes) { return GetSingle(bytes, 292 + BufferOffset); }
86 | public static float CurrentRaceTime(this byte[] bytes) { return GetSingle(bytes, 296 + BufferOffset); }
87 | public static uint Lap(this byte[] bytes) { return GetUInt16(bytes, 300 + BufferOffset); }
88 | public static uint RacePosition(this byte[] bytes) { return GetUInt8(bytes, 302 + BufferOffset); }
89 | public static uint Accelerator(this byte[] bytes) { return GetUInt8(bytes, 303 + BufferOffset); }
90 | public static uint Brake(this byte[] bytes) { return GetUInt8(bytes, 304 + BufferOffset); }
91 | public static uint Clutch(this byte[] bytes) { return GetUInt8(bytes, 305 + BufferOffset); }
92 | public static uint Handbrake(this byte[] bytes) { return GetUInt8(bytes, 306 + BufferOffset); }
93 | public static uint Gear(this byte[] bytes) { return GetUInt8(bytes, 307 + BufferOffset); }
94 | public static int Steer(this byte[] bytes) { return GetInt8(bytes, 308 + BufferOffset); }
95 | public static uint NormalDrivingLine(this byte[] bytes) { return GetUInt8(bytes, 309 + BufferOffset); }
96 | public static uint NormalAiBrakeDifference(this byte[] bytes) { return GetUInt8(bytes, 310 + BufferOffset); }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ForzaDualSense/ForzaDualSense.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | true
7 |
8 | true
9 | win-x64
10 | true
11 |
12 |
13 |
14 | Always
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ForzaDualSense/PacketParse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ForzaDualSense
4 | {
5 | public static class PacketParse
6 | {
7 | private const int SLED_PACKET_LENGTH = 232; // FM7
8 | private const int DASH_PACKET_LENGTH = 311; // FM7
9 | private const int FH7_PACKET_LENGTH = 324; // FH4
10 |
11 | public static bool IsSledFormat(byte[] packet)
12 | {
13 | return packet.Length == SLED_PACKET_LENGTH;
14 | }
15 |
16 | public static bool IsDashFormat(byte[] packet)
17 | {
18 | return packet.Length == DASH_PACKET_LENGTH;
19 | }
20 |
21 | public static bool IsFH7Format(byte[] packet)
22 | {
23 | return packet.Length == FH7_PACKET_LENGTH;
24 | }
25 |
26 | internal static float GetSingle(byte[] bytes, int index)
27 | {
28 | ByteCheck(bytes, index, 4);
29 | return BitConverter.ToSingle(bytes, index);
30 | }
31 |
32 | internal static uint GetUInt16(byte[] bytes, int index)
33 | {
34 | ByteCheck(bytes, index, 2);
35 | return BitConverter.ToUInt16(bytes, index);
36 | }
37 |
38 | internal static uint GetUInt32(byte[] bytes, int index)
39 | {
40 | ByteCheck(bytes, index, 4);
41 | return BitConverter.ToUInt32(bytes, index);
42 | }
43 |
44 | internal static uint GetUInt8(byte[] bytes, int index)
45 | {
46 | ByteCheck(bytes, index, 1);
47 | return bytes[index];
48 | }
49 |
50 | internal static int GetInt8(byte[] bytes, int index)
51 | {
52 | ByteCheck(bytes, index, 1);
53 | return Convert.ToInt16((sbyte)bytes[index]);
54 | }
55 |
56 | private static void ByteCheck(byte[] bytes, int index, int byteCount)
57 | {
58 | if (index + byteCount <= bytes.Length)
59 | {
60 | return;
61 | }
62 |
63 | throw new ArgumentException("Not enough bytes in this array");
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ForzaDualSense/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Newtonsoft.Json;
7 | using System.Diagnostics;
8 | using Microsoft.Extensions.Configuration;
9 | using System.IO;
10 | using CsvHelper;
11 | using System.Globalization;
12 |
13 | namespace ForzaDualSense
14 | {
15 | class Program
16 | {
17 | public const String VERSION = "0.2.2";
18 | static Settings settings = new Settings();
19 | static bool verbose = false;
20 | static bool logToCsv = false;
21 | static String csvFileName = "";
22 | public const int CSV_BUFFER_LENGTH = 120;
23 | static int lastThrottleResistance = 1;
24 | static int lastBrakeResistance = 200;
25 | static int lastBrakeFreq = 0;
26 | //This sends the data to DualSenseX based on the input parsed data from Forza.
27 | //See DataPacket.cs for more details about what forza parameters can be accessed.
28 | //See the Enums at the bottom of this file for details about commands that can be sent to DualSenseX
29 | //Also see the Test Function below to see examples about those commands
30 | static void SendData(DataPacket data, CsvWriter csv)
31 | {
32 | Packet p = new Packet();
33 | CsvData csvRecord = new CsvData();
34 | //Set the controller to do this for
35 | int controllerIndex = 0;
36 | int resistance = 0;
37 | int filteredResistance = 0;
38 | //Initialize our array of instructions
39 | p.instructions = new Instruction[4];
40 | if (logToCsv)
41 | {
42 | csvRecord.time = data.TimestampMS;
43 | csvRecord.AccelerationX = data.AccelerationX;
44 | csvRecord.AccelerationY = data.AccelerationY;
45 | csvRecord.AccelerationZ = data.AccelerationZ;
46 | csvRecord.Brake = data.Brake;
47 | csvRecord.TireCombinedSlipFrontLeft = data.TireCombinedSlipFrontLeft;
48 | csvRecord.TireCombinedSlipFrontRight = data.TireCombinedSlipFrontRight;
49 | csvRecord.TireCombinedSlipRearLeft = data.TireCombinedSlipRearLeft;
50 | csvRecord.TireCombinedSlipRearRight = data.TireCombinedSlipRearRight;
51 | csvRecord.CurrentEngineRpm = data.CurrentEngineRpm;
52 | }
53 | //Set the updates for the right Trigger(Throttle)
54 | p.instructions[2].type = InstructionType.TriggerUpdate;
55 | //It should probably always be uniformly stiff
56 | float avgAccel = (float)Math.Sqrt((settings.TURN_ACCEL_MOD * (data.AccelerationX * data.AccelerationX)) + (settings.FORWARD_ACCEL_MOD * (data.AccelerationZ * data.AccelerationZ)));
57 | resistance = (int)Math.Floor(Map(avgAccel, 0, settings.ACCELRATION_LIMIT, settings.MIN_THROTTLE_RESISTANCE, settings.MAX_THROTTLE_RESISTANCE));
58 | filteredResistance = EWMA(resistance, lastThrottleResistance, settings.EWMA_ALPHA_THROTTLE);
59 | if (logToCsv)
60 | {
61 | csvRecord.AverageAcceleration = avgAccel;
62 | csvRecord.ThrottleResistance = resistance;
63 | csvRecord.ThrottleResistance_filtered = filteredResistance;
64 | }
65 | lastThrottleResistance = filteredResistance;
66 | p.instructions[2].parameters = new object[] { controllerIndex, Trigger.Right, TriggerMode.Resistance, 0, filteredResistance };
67 |
68 | if (verbose)
69 | {
70 | Console.WriteLine($"Average Acceleration: {avgAccel}; Throttle Resistance: {filteredResistance}");
71 | }
72 | //Update the left(Brake) trigger
73 | p.instructions[0].type = InstructionType.TriggerUpdate;
74 | float combinedTireSlip = (Math.Abs(data.TireCombinedSlipFrontLeft) + Math.Abs(data.TireCombinedSlipFrontRight) + Math.Abs(data.TireCombinedSlipRearLeft) + Math.Abs(data.TireCombinedSlipRearRight)) / 4;
75 |
76 |
77 |
78 | int freq = 0;
79 | int filteredFreq = 0;
80 | //All grip lost, trigger should be loose
81 | // if (combinedTireSlip > 1)
82 | // {
83 | // //Set left trigger to normal mode(i.e no resistance)
84 | // p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Normal, 0, 0 };
85 | // if (verbose)
86 | // {
87 | // Console.WriteLine($"Setting Brake to no resistance");
88 | // }
89 | // }
90 | // //Some grip lost, begin to vibrate according to the amount of grip lost
91 | // else
92 | if (combinedTireSlip > settings.GRIP_LOSS_VAL && data.Brake > settings.BRAKE_VIBRATION__MODE_START)
93 | {
94 | freq = settings.MAX_BRAKE_VIBRATION - (int)Math.Floor(Map(combinedTireSlip, settings.GRIP_LOSS_VAL, 1, 0, settings.MAX_BRAKE_VIBRATION));
95 | resistance = settings.MIN_BRAKE_STIFFNESS - (int)Math.Floor(Map(data.Brake, 0, 255, settings.MAX_BRAKE_STIFFNESS, settings.MIN_BRAKE_STIFFNESS));
96 | filteredResistance = EWMA(resistance, lastBrakeResistance, settings.EWMA_ALPHA_BRAKE);
97 | filteredFreq = EWMA(freq, lastBrakeFreq, settings.EWMA_ALPHA_BRAKE_FREQ);
98 | lastBrakeFreq = filteredFreq;
99 | lastBrakeResistance = filteredResistance;
100 | if (filteredFreq <= settings.MIN_BRAKE_VIBRATION)
101 | {
102 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Resistance, 0, 0 };
103 |
104 | }
105 | else
106 | {
107 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.CustomTriggerValue, CustomTriggerValueMode.VibrateResistance, filteredFreq, filteredResistance, settings.BRAKE_VIBRATION_START, 0, 0, 0, 0 };
108 |
109 | }
110 | //Set left trigger to the custom mode VibrateResitance with values of Frequency = freq, Stiffness = 104, startPostion = 76.
111 | if (verbose)
112 | {
113 | Console.WriteLine($"Setting Brake to vibration mode with freq: {filteredFreq}, Resistance: {filteredResistance}");
114 | }
115 |
116 | }
117 | else
118 | {
119 | //By default, Increasingly resistant to force
120 | resistance = (int)Math.Floor(Map(data.Brake, 0, 255, settings.MIN_BRAKE_RESISTANCE, settings.MAX_BRAKE_RESISTANCE));
121 | filteredResistance = EWMA(resistance, lastBrakeResistance, settings.EWMA_ALPHA_BRAKE);
122 | lastBrakeResistance = filteredResistance;
123 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Resistance, 0, filteredResistance };
124 |
125 | //Get average tire slippage. This value runs from 0.0 upwards with a value of 1.0 or greater meaning total loss of grip.
126 | }
127 | if (verbose)
128 | {
129 | Console.WriteLine($"Brake: {data.Brake}; Brake Resistance: {filteredResistance}; Tire Slip: {combinedTireSlip}");
130 | }
131 | if (logToCsv)
132 | {
133 | csvRecord.BrakeResistance = resistance;
134 | csvRecord.combinedTireSlip = combinedTireSlip;
135 | csvRecord.BrakeVibrationFrequency = freq;
136 | csvRecord.BrakeResistance_filtered = filteredResistance;
137 | csvRecord.BrakeVibrationFrequency_freq = filteredFreq;
138 |
139 | }
140 | //Update the light bar
141 | p.instructions[1].type = InstructionType.RGBUpdate;
142 | //Currently registers intensity on the green channel based on engnine RPM as a percantage of the maxium.
143 | p.instructions[1].parameters = new object[] { controllerIndex, 0, (int)Math.Floor((data.CurrentEngineRpm / data.EngineMaxRpm) * 255), 0 };
144 | if (verbose)
145 | {
146 | Console.WriteLine($"Engine RPM: {data.CurrentEngineRpm}");
147 |
148 | }
149 | if (logToCsv)
150 | {
151 | csv.WriteRecord(csvRecord);
152 | csv.NextRecord();
153 | }
154 | //Send the commands to DualSenseX
155 | Send(p);
156 |
157 |
158 | }
159 | //This is the same test method from the UDPExample in DualSenseX. It just provides a basic overview of the different commands that can be used with DualSenseX.
160 | static void test(string[] args)
161 | {
162 |
163 |
164 | while (true)
165 | {
166 | Packet p = new Packet();
167 |
168 | int controllerIndex = 0;
169 |
170 | p.instructions = new Instruction[4];
171 |
172 | // ----------------------------------------------------------------------------------------------------------------------------
173 |
174 | //Normal:
175 | p.instructions[0].type = InstructionType.TriggerUpdate;
176 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Normal };
177 |
178 | //GameCube:
179 | p.instructions[0].type = InstructionType.TriggerUpdate;
180 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.GameCube };
181 |
182 | //VerySoft:
183 | p.instructions[0].type = InstructionType.TriggerUpdate;
184 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.VerySoft };
185 |
186 | //Soft:
187 | p.instructions[0].type = InstructionType.TriggerUpdate;
188 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Soft };
189 |
190 | //Hard:
191 | p.instructions[0].type = InstructionType.TriggerUpdate;
192 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Hard };
193 |
194 | //VeryHard:
195 | p.instructions[0].type = InstructionType.TriggerUpdate;
196 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.VeryHard };
197 |
198 | //Hardest:
199 | p.instructions[0].type = InstructionType.TriggerUpdate;
200 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Hardest };
201 |
202 | //Rigid:
203 | p.instructions[0].type = InstructionType.TriggerUpdate;
204 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Rigid };
205 |
206 | //VibrateTrigger needs 1 param of value from 0-255:
207 | p.instructions[0].type = InstructionType.TriggerUpdate;
208 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.VibrateTrigger, 10 };
209 |
210 | //Choppy:
211 | p.instructions[0].type = InstructionType.TriggerUpdate;
212 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Choppy };
213 |
214 | //Medium:
215 | p.instructions[0].type = InstructionType.TriggerUpdate;
216 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Medium };
217 |
218 | //VibrateTriggerPulse:
219 | p.instructions[0].type = InstructionType.TriggerUpdate;
220 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.VibrateTriggerPulse };
221 |
222 | //CustomTriggerValue with CustomTriggerValueMode:
223 | p.instructions[0].type = InstructionType.TriggerUpdate;
224 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.CustomTriggerValue, CustomTriggerValueMode.PulseAB, 0, 101, 255, 255, 0, 0, 0 };
225 |
226 | //Resistance needs 2 params Start: 0-9 Force:0-8:
227 | p.instructions[0].type = InstructionType.TriggerUpdate;
228 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Resistance, 0, 8 };
229 |
230 | //Bow needs 4 params Start: 0-8 End:0-8 Force:0-8 SnapForce:0-8:
231 | p.instructions[0].type = InstructionType.TriggerUpdate;
232 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Bow, 0, 8, 2, 5 };
233 |
234 | //Galloping needs 5 params Start: 0-8 End:0-9 FirstFoot:0-6 SecondFoot:0-7 Frequency:0-255:
235 | p.instructions[0].type = InstructionType.TriggerUpdate;
236 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Galloping, 0, 9, 2, 4, 10 };
237 |
238 | //SemiAutomaticGun needs 3 params Start: 2-7 End:0-8 Force:0-8:
239 | p.instructions[0].type = InstructionType.TriggerUpdate;
240 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.SemiAutomaticGun, 2, 7, 8 };
241 |
242 | //AutomaticGun needs 3 params Start: 0-8 End:0-9 StrengthA:0-7 StrengthB:0-7 Frequency:0-255 Period 0-2:
243 | p.instructions[0].type = InstructionType.TriggerUpdate;
244 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.AutomaticGun, 0, 8, 10 };
245 |
246 | //AutomaticGun needs 6 params Start: 0-9 Strength:0-8 Frequency:0-255:
247 | p.instructions[0].type = InstructionType.TriggerUpdate;
248 | p.instructions[0].parameters = new object[] { controllerIndex, Trigger.Left, TriggerMode.Machine, 0, 9, 7, 7, 10, 0 };
249 |
250 | // ----------------------------------------------------------------------------------------------------------------------------
251 |
252 | p.instructions[1].type = InstructionType.RGBUpdate;
253 | p.instructions[1].parameters = new object[] { controllerIndex, 0, 255, 0 };
254 |
255 | // ----------------------------------------------------------------------------------------------------------------------------
256 |
257 | // PLAYER LED 1-5 true/false state
258 | p.instructions[2].type = InstructionType.PlayerLED;
259 | p.instructions[2].parameters = new object[] { controllerIndex, true, false, true, false, true };
260 |
261 | // ----------------------------------------------------------------------------------------------------------------------------
262 |
263 | // TriggerThreshold needs 2 params LeftTrigger:0-255 RightTrigger:0-255
264 | p.instructions[3].type = InstructionType.TriggerThreshold;
265 | p.instructions[3].parameters = new object[] { controllerIndex, Trigger.Right, 0 };
266 |
267 | // ----------------------------------------------------------------------------------------------------------------------------
268 |
269 | Send(p);
270 |
271 | Console.WriteLine("Press any key to send again");
272 | Console.ReadKey();
273 | }
274 | }
275 | //Maps floats from one range to another.
276 | public static float Map(float x, float in_min, float in_max, float out_min, float out_max)
277 | {
278 | if (x > in_max)
279 | {
280 | x = in_max;
281 | }
282 | else if (x < in_min)
283 | {
284 | x = in_min;
285 | }
286 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
287 | }
288 |
289 | private static DataPacket data = new DataPacket();
290 | static UdpClient senderClient;
291 | static IPEndPoint endPoint;
292 | //Connect to DualSenseX
293 | static void Connect()
294 | {
295 | senderClient = new UdpClient();
296 | var portNumber = File.ReadAllText(@"C:\Temp\DualSenseX\DualSenseX_PortNumber.txt");
297 | Console.WriteLine("DSX is using port " + portNumber + ". Attempting to connect..");
298 | int portNum = settings.DSX_PORT;
299 | if (portNumber != null)
300 | {
301 | try
302 | {
303 | portNum = Convert.ToInt32(portNumber);
304 | }
305 | catch (FormatException e)
306 | {
307 | Console.WriteLine($"DSX provided a non numerical Port! Using configured default({settings.DSX_PORT}).");
308 | portNum = settings.DSX_PORT;
309 | }
310 | }
311 | else
312 | {
313 | Console.WriteLine($"DSX did not provided a port value. Using configured default({settings.DSX_PORT})");
314 | }
315 |
316 | endPoint = new IPEndPoint(Triggers.localhost, Convert.ToInt32(portNumber));
317 | try
318 | {
319 | senderClient.Connect(endPoint);
320 | }
321 | catch (Exception e)
322 | {
323 | Console.Write("Error Connecting: ");
324 |
325 | if (e is SocketException)
326 | {
327 | Console.WriteLine("Couldn't Access Port. " + e.Message);
328 | }
329 | else if (e is ObjectDisposedException)
330 | {
331 | Console.WriteLine("Connection Object Closed. Restart the Application.");
332 | }
333 | else
334 | {
335 | Console.WriteLine("Unknown Error: " + e.Message);
336 | }
337 | throw e;
338 | }
339 | }
340 | //Send Data to DualSenseX
341 | static void Send(Packet data)
342 | {
343 | if (verbose)
344 | {
345 | Console.WriteLine($"Converting Message to JSON");
346 | }
347 | byte[] RequestData = Encoding.ASCII.GetBytes(Triggers.PacketToJson(data));
348 | if (verbose)
349 | {
350 | Console.WriteLine($"{Encoding.ASCII.GetString(RequestData)}");
351 | }
352 | try
353 | {
354 | if (verbose)
355 | {
356 | Console.WriteLine($"Sending Message to DSX...");
357 | }
358 | senderClient.Send(RequestData, RequestData.Length);
359 | if (verbose)
360 | {
361 | Console.WriteLine($"Message sent to DSX");
362 | }
363 | }
364 | catch (Exception e)
365 | {
366 | Console.Write("Error Sending Message: ");
367 |
368 | if (e is SocketException)
369 | {
370 | Console.WriteLine("Couldn't Access Port. " + e.Message);
371 | throw e;
372 | }
373 | else if (e is ObjectDisposedException)
374 | {
375 | Console.WriteLine("Connection closed. Restarting...");
376 | Connect();
377 | }
378 | else
379 | {
380 | Console.WriteLine("Unknown Error: " + e.Message);
381 |
382 | }
383 |
384 | }
385 | }
386 |
387 | //Main running thread of program.
388 | static async Task Main(string[] args)
389 | {
390 | IPEndPoint ipEndPoint = null;
391 | UdpClient client = null;
392 | StreamWriter writer = null;
393 | CsvWriter csv = null;
394 | try
395 | {
396 | for (int i = 0; i < args.Length; i++)
397 |
398 | {
399 | string arg = args[i];
400 |
401 | switch (arg)
402 | {
403 | case "-v":
404 | {
405 | Console.WriteLine($"ForzaDualSense Version {VERSION}");
406 | return;
407 | }
408 | case "--verbose":
409 | {
410 | Console.WriteLine("Verbose Mode Enabled!");
411 | verbose = true;
412 | break;
413 | }
414 | case "--csv":
415 | {
416 | logToCsv = true;
417 | i++;
418 | if (i >= args.Length)
419 | {
420 | Console.WriteLine("No Path Entered for Csv file output!! Exiting");
421 | return;
422 | }
423 | csvFileName = args[i];
424 | break;
425 | }
426 | default:
427 | {
428 |
429 | break;
430 | }
431 | }
432 | }
433 | // Build a config object, using env vars and JSON providers.
434 | IConfiguration config = new ConfigurationBuilder()
435 | .AddIniFile("appsettings.ini")
436 | .Build();
437 | try
438 | {
439 | // Get values from the config given their key and their target type.
440 | settings = config.Get();
441 | }
442 | catch (Exception e)
443 | {
444 | Console.WriteLine("Invalid Configuration File!");
445 | Console.WriteLine(e.Message);
446 | return;
447 | }
448 | if (!settings.DISABLE_APP_CHECK)
449 | {
450 | int forzaProcesses = Process.GetProcessesByName("ForzaHorizon 5").Length;
451 | forzaProcesses += Process.GetProcessesByName("ForzaHorizon4").Length;
452 | forzaProcesses += Process.GetProcessesByName("ForzaMotorsport7").Length;
453 | Process[] DSX = Process.GetProcessesByName("DualSenseX");
454 | Process[] cur = Process.GetProcesses();
455 | while (forzaProcesses == 0 || DSX.Length == 0)
456 | {
457 | if (forzaProcesses == 0)
458 | {
459 | Console.WriteLine("No Running Instances of Forza found. Waiting... ");
460 |
461 | }
462 | else if (DSX.Length == 0)
463 | {
464 | Console.WriteLine("No Running Instances of DualSenseX found. Waiting... ");
465 | }
466 | System.Threading.Thread.Sleep(1000);
467 | forzaProcesses += Process.GetProcessesByName("ForzaHorizon5").Length;
468 | forzaProcesses += Process.GetProcessesByName("ForzaHorizon4").Length; //Guess at name
469 | forzaProcesses += Process.GetProcessesByName("ForzaMotorsport7").Length; //Guess at name
470 | DSX = Process.GetProcessesByName("DualSenseX");
471 | }
472 | Console.WriteLine("Forza and DSX are running. Let's Go!");
473 | }
474 | //Connect to DualSenseX
475 | Connect();
476 |
477 | //Connect to Forza
478 | ipEndPoint = new IPEndPoint(IPAddress.Loopback, settings.FORZA_PORT);
479 | client = new UdpClient(settings.FORZA_PORT);
480 |
481 | Console.WriteLine($"The Program is running. Please set the Forza data out to 127.0.0.1, port {settings.FORZA_PORT} and verify the DualSenseX UDP Port is set to {settings.DSX_PORT}");
482 | UdpReceiveResult receive;
483 | if (logToCsv)
484 | {
485 | try
486 | {
487 | writer = new StreamWriter(csvFileName);
488 | csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
489 | csv.WriteHeader();
490 | csv.NextRecord();
491 | }
492 | catch (Exception e)
493 | {
494 | Console.WriteLine("Failed to open csv File output. Ensure it is a valid path!");
495 | throw e;
496 | }
497 | }
498 |
499 | int count = 0;
500 |
501 | //Main loop, go until killed
502 | while (true)
503 | {
504 | //If Forza sends an update
505 | receive = await client.ReceiveAsync();
506 | if (verbose)
507 | {
508 | Console.WriteLine("recieved Message from Forza!");
509 | }
510 | //parse data
511 | var resultBuffer = receive.Buffer;
512 | if (!AdjustToBufferType(resultBuffer.Length))
513 | {
514 | // return;
515 | }
516 | data = ParseData(resultBuffer);
517 | if (verbose)
518 | {
519 | Console.WriteLine("Data Parsed");
520 | }
521 |
522 | //Process and send data to DualSenseX
523 | SendData(data, csv);
524 | if (logToCsv && count++ > CSV_BUFFER_LENGTH)
525 | {
526 | writer.Flush();
527 | count = 0;
528 | }
529 | }
530 |
531 |
532 | }
533 | catch (Exception e)
534 | {
535 | Console.WriteLine("Application encountered an exception: " + e.Message);
536 | }
537 | finally
538 | {
539 | if (verbose)
540 | {
541 | Console.WriteLine($"Cleaning Up");
542 | }
543 | if (client != null)
544 | {
545 | client.Close();
546 | client.Dispose();
547 | }
548 | if (senderClient != null)
549 | {
550 | senderClient.Close();
551 | senderClient.Dispose();
552 | }
553 | if (csv != null)
554 | {
555 | csv.Dispose();
556 | }
557 | if (writer != null)
558 | {
559 | writer.Flush();
560 | writer.Close();
561 |
562 | }
563 |
564 | if (verbose)
565 | {
566 | Console.WriteLine($"Cleanup Finished. Exiting...");
567 | }
568 |
569 | }
570 | return;
571 |
572 | }
573 |
574 | static float EWMA(float input, float last, float alpha)
575 | {
576 | return (alpha * input) + (1 - alpha) * last;
577 | }
578 | static int EWMA(int input, int last, float alpha)
579 | {
580 | return (int)Math.Floor(EWMA((float)input, (float)last, alpha));
581 | }
582 |
583 | //Parses data from Forza into a DataPacket
584 | static DataPacket ParseData(byte[] packet)
585 | {
586 | DataPacket data = new DataPacket();
587 |
588 | // sled
589 | data.IsRaceOn = packet.IsRaceOn();
590 | data.TimestampMS = packet.TimestampMs();
591 | data.EngineMaxRpm = packet.EngineMaxRpm();
592 | data.EngineIdleRpm = packet.EngineIdleRpm();
593 | data.CurrentEngineRpm = packet.CurrentEngineRpm();
594 | data.AccelerationX = packet.AccelerationX();
595 | data.AccelerationY = packet.AccelerationY();
596 | data.AccelerationZ = packet.AccelerationZ();
597 | data.VelocityX = packet.VelocityX();
598 | data.VelocityY = packet.VelocityY();
599 | data.VelocityZ = packet.VelocityZ();
600 | data.AngularVelocityX = packet.AngularVelocityX();
601 | data.AngularVelocityY = packet.AngularVelocityY();
602 | data.AngularVelocityZ = packet.AngularVelocityZ();
603 | data.Yaw = packet.Yaw();
604 | data.Pitch = packet.Pitch();
605 | data.Roll = packet.Roll();
606 | data.NormalizedSuspensionTravelFrontLeft = packet.NormSuspensionTravelFl();
607 | data.NormalizedSuspensionTravelFrontRight = packet.NormSuspensionTravelFr();
608 | data.NormalizedSuspensionTravelRearLeft = packet.NormSuspensionTravelRl();
609 | data.NormalizedSuspensionTravelRearRight = packet.NormSuspensionTravelRr();
610 | data.TireSlipRatioFrontLeft = packet.TireSlipRatioFl();
611 | data.TireSlipRatioFrontRight = packet.TireSlipRatioFr();
612 | data.TireSlipRatioRearLeft = packet.TireSlipRatioRl();
613 | data.TireSlipRatioRearRight = packet.TireSlipRatioRr();
614 | data.WheelRotationSpeedFrontLeft = packet.WheelRotationSpeedFl();
615 | data.WheelRotationSpeedFrontRight = packet.WheelRotationSpeedFr();
616 | data.WheelRotationSpeedRearLeft = packet.WheelRotationSpeedRl();
617 | data.WheelRotationSpeedRearRight = packet.WheelRotationSpeedRr();
618 | data.WheelOnRumbleStripFrontLeft = packet.WheelOnRumbleStripFl();
619 | data.WheelOnRumbleStripFrontRight = packet.WheelOnRumbleStripFr();
620 | data.WheelOnRumbleStripRearLeft = packet.WheelOnRumbleStripRl();
621 | data.WheelOnRumbleStripRearRight = packet.WheelOnRumbleStripRr();
622 | data.WheelInPuddleDepthFrontLeft = packet.WheelInPuddleFl();
623 | data.WheelInPuddleDepthFrontRight = packet.WheelInPuddleFr();
624 | data.WheelInPuddleDepthRearLeft = packet.WheelInPuddleRl();
625 | data.WheelInPuddleDepthRearRight = packet.WheelInPuddleRr();
626 | data.SurfaceRumbleFrontLeft = packet.SurfaceRumbleFl();
627 | data.SurfaceRumbleFrontRight = packet.SurfaceRumbleFr();
628 | data.SurfaceRumbleRearLeft = packet.SurfaceRumbleRl();
629 | data.SurfaceRumbleRearRight = packet.SurfaceRumbleRr();
630 | data.TireSlipAngleFrontLeft = packet.TireSlipAngleFl();
631 | data.TireSlipAngleFrontRight = packet.TireSlipAngleFr();
632 | data.TireSlipAngleRearLeft = packet.TireSlipAngleRl();
633 | data.TireSlipAngleRearRight = packet.TireSlipAngleRr();
634 | data.TireCombinedSlipFrontLeft = packet.TireCombinedSlipFl();
635 | data.TireCombinedSlipFrontRight = packet.TireCombinedSlipFr();
636 | data.TireCombinedSlipRearLeft = packet.TireCombinedSlipRl();
637 | data.TireCombinedSlipRearRight = packet.TireCombinedSlipRr();
638 | data.SuspensionTravelMetersFrontLeft = packet.SuspensionTravelMetersFl();
639 | data.SuspensionTravelMetersFrontRight = packet.SuspensionTravelMetersFr();
640 | data.SuspensionTravelMetersRearLeft = packet.SuspensionTravelMetersRl();
641 | data.SuspensionTravelMetersRearRight = packet.SuspensionTravelMetersRr();
642 | data.CarOrdinal = packet.CarOrdinal();
643 | data.CarClass = packet.CarClass();
644 | data.CarPerformanceIndex = packet.CarPerformanceIndex();
645 | data.DrivetrainType = packet.DriveTrain();
646 | data.NumCylinders = packet.NumCylinders();
647 |
648 | // dash
649 | data.PositionX = packet.PositionX();
650 | data.PositionY = packet.PositionY();
651 | data.PositionZ = packet.PositionZ();
652 | data.Speed = packet.Speed();
653 | data.Power = packet.Power();
654 | data.Torque = packet.Torque();
655 | data.TireTempFl = packet.TireTempFl();
656 | data.TireTempFr = packet.TireTempFr();
657 | data.TireTempRl = packet.TireTempRl();
658 | data.TireTempRr = packet.TireTempRr();
659 | data.Boost = packet.Boost();
660 | data.Fuel = packet.Fuel();
661 | data.Distance = packet.Distance();
662 | data.BestLapTime = packet.BestLapTime();
663 | data.LastLapTime = packet.LastLapTime();
664 | data.CurrentLapTime = packet.CurrentLapTime();
665 | data.CurrentRaceTime = packet.CurrentRaceTime();
666 | data.Lap = packet.Lap();
667 | data.RacePosition = packet.RacePosition();
668 | data.Accelerator = packet.Accelerator();
669 | data.Brake = packet.Brake();
670 | data.Clutch = packet.Clutch();
671 | data.Handbrake = packet.Handbrake();
672 | data.Gear = packet.Gear();
673 | data.Steer = packet.Steer();
674 | data.NormalDrivingLine = packet.NormalDrivingLine();
675 | data.NormalAiBrakeDifference = packet.NormalAiBrakeDifference();
676 |
677 | return data;
678 | }
679 |
680 | //Support different standards
681 | static bool AdjustToBufferType(int bufferLength)
682 | {
683 | switch (bufferLength)
684 | {
685 | case 232: // FM7 sled
686 | return false;
687 | case 311: // FM7 dash
688 | FMData.BufferOffset = 0;
689 | return true;
690 | case 324: // FH4
691 | FMData.BufferOffset = 12;
692 | return true;
693 | default:
694 | return false;
695 | }
696 | }
697 |
698 |
699 | }
700 |
701 | //Needed to communicate with DualSenseX
702 | public static class Triggers
703 | {
704 | public static IPAddress localhost = new IPAddress(new byte[] { 127, 0, 0, 1 });
705 |
706 | public static string PacketToJson(Packet packet)
707 | {
708 | return Newtonsoft.Json.JsonConvert.SerializeObject(packet);
709 | }
710 |
711 | public static Packet JsonToPacket(string json)
712 | {
713 | return JsonConvert.DeserializeObject(json);
714 | }
715 | }
716 |
717 | //The different trigger Modes. These correlate the values in the DualSenseX UI
718 | public enum TriggerMode
719 | {
720 | Normal = 0,
721 | GameCube = 1,
722 | VerySoft = 2,
723 | Soft = 3,
724 | Hard = 4,
725 | VeryHard = 5,
726 | Hardest = 6,
727 | Rigid = 7,
728 | VibrateTrigger = 8,
729 | Choppy = 9,
730 | Medium = 10,
731 | VibrateTriggerPulse = 11,
732 | CustomTriggerValue = 12,
733 | Resistance = 13,
734 | Bow = 14,
735 | Galloping = 15,
736 | SemiAutomaticGun = 16,
737 | AutomaticGun = 17,
738 | Machine = 18
739 | }
740 |
741 | //Custom Trigger Values. These correspond to the values in the DualSenseX UI
742 | public enum CustomTriggerValueMode
743 | {
744 | OFF = 0,
745 | Rigid = 1,
746 | RigidA = 2,
747 | RigidB = 3,
748 | RigidAB = 4,
749 | Pulse = 5,
750 | PulseA = 6,
751 | PulseB = 7,
752 | PulseAB = 8,
753 | VibrateResistance = 9,
754 | VibrateResistanceA = 10,
755 | VibrateResistanceB = 11,
756 | VibrateResistanceAB = 12,
757 | VibratePulse = 13,
758 | VibratePulseA = 14,
759 | VibratePulsB = 15,
760 | VibratePulseAB = 16
761 | }
762 |
763 | public enum Trigger
764 | {
765 | Invalid,
766 | Left,
767 | Right
768 | }
769 |
770 | public enum InstructionType
771 | {
772 | Invalid,
773 | TriggerUpdate,
774 | RGBUpdate,
775 | PlayerLED,
776 | TriggerThreshold
777 | }
778 |
779 | public struct Instruction
780 | {
781 | public InstructionType type;
782 | public object[] parameters;
783 | }
784 |
785 | public class Packet
786 | {
787 | public Instruction[] instructions;
788 | }
789 | }
790 |
--------------------------------------------------------------------------------
/ForzaDualSense/Settings.cs:
--------------------------------------------------------------------------------
1 | namespace ForzaDualSense
2 | {
3 | public class Settings
4 | {
5 | public float GRIP_LOSS_VAL { get; set; } = 0.5f; //The point at which the brake will begin to become choppy
6 | public int MAX_BRAKE_VIBRATION { get; set; } = 35; //The maximum brake frequency in Hz (avoid over 40). COrrelates to better grip
7 | public int MIN_BRAKE_VIBRATION { get; set; } = 3; //The Minimum brake frequency in Hz (avoid over 40). Helps avoid clicking in controller
8 | public float TURN_ACCEL_MOD { get; set; } = 0.5f; //How to scale turning acceleration in determining throttle stiffness.
9 | public float EWMA_ALPHA_THROTTLE { get; set; } = 0.01f; //Smoothing for Throttle Resistance output. Lower = smoother. Must be greater than 0
10 | public float EWMA_ALPHA_BRAKE { get; set; } = 1.0f; //Smoothing for Brake Resistance output. Lower = smoother. Must be greater than 0
11 | public float EWMA_ALPHA_BRAKE_FREQ { get; set; } = 1.0f; //Smoothing for Brake Resistance output. Lower = smoother. Must be greater than 0
12 | public float FORWARD_ACCEL_MOD { get; set; } = 1.0f;//How to scale Forward acceleration in determining throttle stiffness.
13 | public int MIN_BRAKE_STIFFNESS { get; set; } = 200; //On a scale of 1-200 with 1 being most stiff
14 | public int MAX_BRAKE_STIFFNESS { get; set; } = 1; //On a scale of 1-200 with 1 being most stiff
15 | public int BRAKE_VIBRATION_START { get; set; } = 20; //The position (0-255) at which the brake should feel engaged with low grip surfaces
16 | public int BRAKE_VIBRATION__MODE_START { get; set; } = 10; //The depression of the brake lever at which the program should switch to vibration mode rather than smooth resistance. This helps to avoid clicking as vibration mode clicks when no force is applied.
17 | public int MAX_THROTTLE_RESISTANCE { get; set; } = 6; //The Maximum resistance on the throttle (0-7)
18 | public int MAX_BRAKE_RESISTANCE { get; set; } = 6;//The Maximum resistance on the Brake (0-7)
19 | public int MIN_THROTTLE_RESISTANCE { get; set; } = 1;//The Minimum resistance on the throttle (0-7)
20 | public int MIN_BRAKE_RESISTANCE { get; set; } = 1;//The Minimum resistance on the Brake (0-7)
21 | public int ACCELRATION_LIMIT { get; set; } = 10; //The upper end acceleration when calculating the throttle resistance. Any acceleration above this will be counted as this value when determining the throttle resistance.
22 | public bool DISABLE_APP_CHECK { get; set; } = false; //Should we disable the check for running applications?
23 | public int DSX_PORT { get; set; } = 6969; //Port for DSX Port Listener
24 | public int FORZA_PORT { get; set; } = 5300; //Port for Forza UDP server
25 | }
26 | }
--------------------------------------------------------------------------------
/ForzaDualSense/appsettings.ini:
--------------------------------------------------------------------------------
1 | ;The point at which the brake will begin to become choppy
2 | GRIP_LOSS_VAL=0.5
3 | ;The maximum brake frequency in Hz (avoid over 40). COrrelates to better grip
4 | MAX_BRAKE_VIBRATION=35
5 | ;How to scale turning acceleration in determining throttle stiffness.
6 | TURN_ACCEL_MOD=0.5
7 | ;How to scale Forward acceleration in determining throttle stiffness.
8 | FORWARD_ACCEL_MOD=1.0
9 | ;On a scale of 1-200 with 1 being most stiff
10 | MIN_BRAKE_STIFFNESS=200
11 | ;On a scale of 1-200 with 1 being most stiff
12 | MAX_BRAKE_STIFFNESS=1
13 | ;The position (0-255) at which the brake should feel engaged with low grip surfaces
14 | BRAKE_VIBRATION_START=20
15 | ;The Maximum resistance on the throttle (0-7)
16 | MAX_THROTTLE_RESISTANCE=6
17 | ;The Maximum resistance on the Brake (0-7)
18 | MAX_BRAKE_RESISTANCE=6
19 | ;The Minimum resistance on the throttle (0-7)
20 | MIN_THROTTLE_RESISTANCE=1
21 | ;The Minimum resistance on the Brake (0-7)
22 | MIN_BRAKE_RESISTANCE=1
23 | ;The upper end acceleration when calculating the throttle resistance. Any acceleration above this will be counted as this value when determining the throttle resistance.
24 | ACCELRATION_LIMIT=10
25 | DISABLE_APP_CHECK=true
26 | DSX_PORT=6969
27 | FORZA_PORT=5300
28 | ;Smoothing for Throttle Resistance output. Lower = smoother. Must be greater than 0
29 | EWMA_ALPHA_THROTTLE = 0.01
30 | ;Smoothing for Brake Resistance output. Lower = smoother. Must be greater than 0
31 | EWMA_ALPHA_BRAKE = 1.0
32 | ;Smoothing for Brake Resistance output. Lower = smoother. Must be greater than 0
33 | EWMA_ALPHA_BRAKE_FREQ = 1.0
34 | ; //The depression of the brake lever at which the program should switch to vibration mode rather than smooth resistance. This helps to avoid clicking as vibration mode clicks when no force is applied.
35 | BRAKE_VIBRATION__MODE_START = 10
36 | ; //The Minimum brake frequency in Hz (avoid over 40). Helps avoid clicking in controller
37 | MIN_BRAKE_VIBRATION = 3
--------------------------------------------------------------------------------
/ForzaDualSense/obj/ForzaDualSense.csproj.nuget.dgspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "format": 1,
3 | "restore": {
4 | "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj": {}
5 | },
6 | "projects": {
7 | "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj": {
8 | "version": "1.0.0",
9 | "restore": {
10 | "projectUniqueName": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj",
11 | "projectName": "ForzaDualSense",
12 | "projectPath": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj",
13 | "packagesPath": "C:\\Users\\patma\\.nuget\\packages\\",
14 | "outputPath": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\obj\\",
15 | "projectStyle": "PackageReference",
16 | "configFilePaths": [
17 | "C:\\Users\\patma\\AppData\\Roaming\\NuGet\\NuGet.Config"
18 | ],
19 | "originalTargetFrameworks": [
20 | "net6.0"
21 | ],
22 | "sources": {
23 | "https://api.nuget.org/v3/index.json": {}
24 | },
25 | "frameworks": {
26 | "net6.0": {
27 | "targetAlias": "net6.0",
28 | "projectReferences": {}
29 | }
30 | },
31 | "warningProperties": {
32 | "warnAsError": [
33 | "NU1605"
34 | ]
35 | }
36 | },
37 | "frameworks": {
38 | "net6.0": {
39 | "targetAlias": "net6.0",
40 | "dependencies": {
41 | "CsvHelper": {
42 | "target": "Package",
43 | "version": "[27.2.1, )"
44 | },
45 | "Microsoft.Extensions.Configuration.Binder": {
46 | "target": "Package",
47 | "version": "[6.0.0, )"
48 | },
49 | "Microsoft.Extensions.Configuration.EnvironmentVariables": {
50 | "target": "Package",
51 | "version": "[6.0.0, )"
52 | },
53 | "Microsoft.Extensions.Configuration.Ini": {
54 | "target": "Package",
55 | "version": "[6.0.0, )"
56 | },
57 | "Newtonsoft.Json": {
58 | "target": "Package",
59 | "version": "[13.0.1, )"
60 | }
61 | },
62 | "imports": [
63 | "net461",
64 | "net462",
65 | "net47",
66 | "net471",
67 | "net472",
68 | "net48"
69 | ],
70 | "assetTargetFallback": true,
71 | "warn": true,
72 | "downloadDependencies": [
73 | {
74 | "name": "Microsoft.AspNetCore.App.Runtime.win-x64",
75 | "version": "[6.0.1, 6.0.1]"
76 | },
77 | {
78 | "name": "Microsoft.NETCore.App.Crossgen2.win-x64",
79 | "version": "[6.0.1, 6.0.1]"
80 | },
81 | {
82 | "name": "Microsoft.NETCore.App.Runtime.win-x64",
83 | "version": "[6.0.1, 6.0.1]"
84 | },
85 | {
86 | "name": "Microsoft.WindowsDesktop.App.Runtime.win-x64",
87 | "version": "[6.0.1, 6.0.1]"
88 | }
89 | ],
90 | "frameworkReferences": {
91 | "Microsoft.NETCore.App": {
92 | "privateAssets": "all"
93 | }
94 | },
95 | "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.101\\RuntimeIdentifierGraph.json"
96 | }
97 | },
98 | "runtimes": {
99 | "win-x64": {
100 | "#import": []
101 | }
102 | }
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/ForzaDualSense/obj/ForzaDualSense.csproj.nuget.g.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | True
5 | NuGet
6 | $(MSBuildThisFileDirectory)project.assets.json
7 | $(UserProfile)\.nuget\packages\
8 | C:\Users\patma\.nuget\packages\
9 | PackageReference
10 | 6.0.0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ForzaDualSense/obj/ForzaDualSense.csproj.nuget.g.targets:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ForzaDualSense/obj/project.assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "targets": {
4 | "net6.0": {
5 | "CsvHelper/27.2.1": {
6 | "type": "package",
7 | "compile": {
8 | "lib/net6.0/CsvHelper.dll": {}
9 | },
10 | "runtime": {
11 | "lib/net6.0/CsvHelper.dll": {}
12 | }
13 | },
14 | "Microsoft.Extensions.Configuration/6.0.0": {
15 | "type": "package",
16 | "dependencies": {
17 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
18 | "Microsoft.Extensions.Primitives": "6.0.0"
19 | },
20 | "compile": {
21 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll": {}
22 | },
23 | "runtime": {
24 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll": {}
25 | }
26 | },
27 | "Microsoft.Extensions.Configuration.Abstractions/6.0.0": {
28 | "type": "package",
29 | "dependencies": {
30 | "Microsoft.Extensions.Primitives": "6.0.0"
31 | },
32 | "compile": {
33 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {}
34 | },
35 | "runtime": {
36 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {}
37 | }
38 | },
39 | "Microsoft.Extensions.Configuration.Binder/6.0.0": {
40 | "type": "package",
41 | "dependencies": {
42 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
43 | },
44 | "compile": {
45 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll": {}
46 | },
47 | "runtime": {
48 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll": {}
49 | }
50 | },
51 | "Microsoft.Extensions.Configuration.EnvironmentVariables/6.0.0": {
52 | "type": "package",
53 | "dependencies": {
54 | "Microsoft.Extensions.Configuration": "6.0.0",
55 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
56 | },
57 | "compile": {
58 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll": {}
59 | },
60 | "runtime": {
61 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll": {}
62 | }
63 | },
64 | "Microsoft.Extensions.Configuration.FileExtensions/6.0.0": {
65 | "type": "package",
66 | "dependencies": {
67 | "Microsoft.Extensions.Configuration": "6.0.0",
68 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
69 | "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0",
70 | "Microsoft.Extensions.FileProviders.Physical": "6.0.0",
71 | "Microsoft.Extensions.Primitives": "6.0.0"
72 | },
73 | "compile": {
74 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {}
75 | },
76 | "runtime": {
77 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {}
78 | }
79 | },
80 | "Microsoft.Extensions.Configuration.Ini/6.0.0": {
81 | "type": "package",
82 | "dependencies": {
83 | "Microsoft.Extensions.Configuration": "6.0.0",
84 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
85 | "Microsoft.Extensions.Configuration.FileExtensions": "6.0.0",
86 | "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0"
87 | },
88 | "compile": {
89 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Ini.dll": {}
90 | },
91 | "runtime": {
92 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Ini.dll": {}
93 | }
94 | },
95 | "Microsoft.Extensions.FileProviders.Abstractions/6.0.0": {
96 | "type": "package",
97 | "dependencies": {
98 | "Microsoft.Extensions.Primitives": "6.0.0"
99 | },
100 | "compile": {
101 | "lib/net6.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {}
102 | },
103 | "runtime": {
104 | "lib/net6.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {}
105 | },
106 | "build": {
107 | "buildTransitive/netcoreapp3.1/_._": {}
108 | }
109 | },
110 | "Microsoft.Extensions.FileProviders.Physical/6.0.0": {
111 | "type": "package",
112 | "dependencies": {
113 | "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0",
114 | "Microsoft.Extensions.FileSystemGlobbing": "6.0.0",
115 | "Microsoft.Extensions.Primitives": "6.0.0"
116 | },
117 | "compile": {
118 | "lib/net6.0/Microsoft.Extensions.FileProviders.Physical.dll": {}
119 | },
120 | "runtime": {
121 | "lib/net6.0/Microsoft.Extensions.FileProviders.Physical.dll": {}
122 | },
123 | "build": {
124 | "buildTransitive/netcoreapp3.1/_._": {}
125 | }
126 | },
127 | "Microsoft.Extensions.FileSystemGlobbing/6.0.0": {
128 | "type": "package",
129 | "compile": {
130 | "lib/net6.0/Microsoft.Extensions.FileSystemGlobbing.dll": {}
131 | },
132 | "runtime": {
133 | "lib/net6.0/Microsoft.Extensions.FileSystemGlobbing.dll": {}
134 | },
135 | "build": {
136 | "buildTransitive/netcoreapp3.1/_._": {}
137 | }
138 | },
139 | "Microsoft.Extensions.Primitives/6.0.0": {
140 | "type": "package",
141 | "dependencies": {
142 | "System.Runtime.CompilerServices.Unsafe": "6.0.0"
143 | },
144 | "compile": {
145 | "lib/net6.0/Microsoft.Extensions.Primitives.dll": {}
146 | },
147 | "runtime": {
148 | "lib/net6.0/Microsoft.Extensions.Primitives.dll": {}
149 | },
150 | "build": {
151 | "buildTransitive/netcoreapp3.1/_._": {}
152 | }
153 | },
154 | "Newtonsoft.Json/13.0.1": {
155 | "type": "package",
156 | "compile": {
157 | "lib/netstandard2.0/Newtonsoft.Json.dll": {}
158 | },
159 | "runtime": {
160 | "lib/netstandard2.0/Newtonsoft.Json.dll": {}
161 | }
162 | },
163 | "System.Runtime.CompilerServices.Unsafe/6.0.0": {
164 | "type": "package",
165 | "compile": {
166 | "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {}
167 | },
168 | "runtime": {
169 | "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {}
170 | },
171 | "build": {
172 | "buildTransitive/netcoreapp3.1/_._": {}
173 | }
174 | }
175 | },
176 | "net6.0/win-x64": {
177 | "CsvHelper/27.2.1": {
178 | "type": "package",
179 | "compile": {
180 | "lib/net6.0/CsvHelper.dll": {}
181 | },
182 | "runtime": {
183 | "lib/net6.0/CsvHelper.dll": {}
184 | }
185 | },
186 | "Microsoft.Extensions.Configuration/6.0.0": {
187 | "type": "package",
188 | "dependencies": {
189 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
190 | "Microsoft.Extensions.Primitives": "6.0.0"
191 | },
192 | "compile": {
193 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll": {}
194 | },
195 | "runtime": {
196 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll": {}
197 | }
198 | },
199 | "Microsoft.Extensions.Configuration.Abstractions/6.0.0": {
200 | "type": "package",
201 | "dependencies": {
202 | "Microsoft.Extensions.Primitives": "6.0.0"
203 | },
204 | "compile": {
205 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {}
206 | },
207 | "runtime": {
208 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {}
209 | }
210 | },
211 | "Microsoft.Extensions.Configuration.Binder/6.0.0": {
212 | "type": "package",
213 | "dependencies": {
214 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
215 | },
216 | "compile": {
217 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll": {}
218 | },
219 | "runtime": {
220 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll": {}
221 | }
222 | },
223 | "Microsoft.Extensions.Configuration.EnvironmentVariables/6.0.0": {
224 | "type": "package",
225 | "dependencies": {
226 | "Microsoft.Extensions.Configuration": "6.0.0",
227 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
228 | },
229 | "compile": {
230 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll": {}
231 | },
232 | "runtime": {
233 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll": {}
234 | }
235 | },
236 | "Microsoft.Extensions.Configuration.FileExtensions/6.0.0": {
237 | "type": "package",
238 | "dependencies": {
239 | "Microsoft.Extensions.Configuration": "6.0.0",
240 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
241 | "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0",
242 | "Microsoft.Extensions.FileProviders.Physical": "6.0.0",
243 | "Microsoft.Extensions.Primitives": "6.0.0"
244 | },
245 | "compile": {
246 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {}
247 | },
248 | "runtime": {
249 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.FileExtensions.dll": {}
250 | }
251 | },
252 | "Microsoft.Extensions.Configuration.Ini/6.0.0": {
253 | "type": "package",
254 | "dependencies": {
255 | "Microsoft.Extensions.Configuration": "6.0.0",
256 | "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
257 | "Microsoft.Extensions.Configuration.FileExtensions": "6.0.0",
258 | "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0"
259 | },
260 | "compile": {
261 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Ini.dll": {}
262 | },
263 | "runtime": {
264 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Ini.dll": {}
265 | }
266 | },
267 | "Microsoft.Extensions.FileProviders.Abstractions/6.0.0": {
268 | "type": "package",
269 | "dependencies": {
270 | "Microsoft.Extensions.Primitives": "6.0.0"
271 | },
272 | "compile": {
273 | "lib/net6.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {}
274 | },
275 | "runtime": {
276 | "lib/net6.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {}
277 | },
278 | "build": {
279 | "buildTransitive/netcoreapp3.1/_._": {}
280 | }
281 | },
282 | "Microsoft.Extensions.FileProviders.Physical/6.0.0": {
283 | "type": "package",
284 | "dependencies": {
285 | "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0",
286 | "Microsoft.Extensions.FileSystemGlobbing": "6.0.0",
287 | "Microsoft.Extensions.Primitives": "6.0.0"
288 | },
289 | "compile": {
290 | "lib/net6.0/Microsoft.Extensions.FileProviders.Physical.dll": {}
291 | },
292 | "runtime": {
293 | "lib/net6.0/Microsoft.Extensions.FileProviders.Physical.dll": {}
294 | },
295 | "build": {
296 | "buildTransitive/netcoreapp3.1/_._": {}
297 | }
298 | },
299 | "Microsoft.Extensions.FileSystemGlobbing/6.0.0": {
300 | "type": "package",
301 | "compile": {
302 | "lib/net6.0/Microsoft.Extensions.FileSystemGlobbing.dll": {}
303 | },
304 | "runtime": {
305 | "lib/net6.0/Microsoft.Extensions.FileSystemGlobbing.dll": {}
306 | },
307 | "build": {
308 | "buildTransitive/netcoreapp3.1/_._": {}
309 | }
310 | },
311 | "Microsoft.Extensions.Primitives/6.0.0": {
312 | "type": "package",
313 | "dependencies": {
314 | "System.Runtime.CompilerServices.Unsafe": "6.0.0"
315 | },
316 | "compile": {
317 | "lib/net6.0/Microsoft.Extensions.Primitives.dll": {}
318 | },
319 | "runtime": {
320 | "lib/net6.0/Microsoft.Extensions.Primitives.dll": {}
321 | },
322 | "build": {
323 | "buildTransitive/netcoreapp3.1/_._": {}
324 | }
325 | },
326 | "Newtonsoft.Json/13.0.1": {
327 | "type": "package",
328 | "compile": {
329 | "lib/netstandard2.0/Newtonsoft.Json.dll": {}
330 | },
331 | "runtime": {
332 | "lib/netstandard2.0/Newtonsoft.Json.dll": {}
333 | }
334 | },
335 | "System.Runtime.CompilerServices.Unsafe/6.0.0": {
336 | "type": "package",
337 | "compile": {
338 | "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {}
339 | },
340 | "runtime": {
341 | "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {}
342 | },
343 | "build": {
344 | "buildTransitive/netcoreapp3.1/_._": {}
345 | }
346 | }
347 | }
348 | },
349 | "libraries": {
350 | "CsvHelper/27.2.1": {
351 | "sha512": "uS5ix5hL9gL5taiAG//CScyJa8Fn1ZOh3FDhDvf4laboESFs84mCNropfp7PIt8xCkyQofljFpqu1B5UnSXjyA==",
352 | "type": "package",
353 | "path": "csvhelper/27.2.1",
354 | "files": [
355 | ".nupkg.metadata",
356 | ".signature.p7s",
357 | "Icon.png",
358 | "csvhelper.27.2.1.nupkg.sha512",
359 | "csvhelper.nuspec",
360 | "lib/net45/CsvHelper.dll",
361 | "lib/net45/CsvHelper.xml",
362 | "lib/net47/CsvHelper.dll",
363 | "lib/net47/CsvHelper.xml",
364 | "lib/net5.0/CsvHelper.dll",
365 | "lib/net5.0/CsvHelper.xml",
366 | "lib/net6.0/CsvHelper.dll",
367 | "lib/net6.0/CsvHelper.xml",
368 | "lib/netstandard2.0/CsvHelper.dll",
369 | "lib/netstandard2.0/CsvHelper.xml",
370 | "lib/netstandard2.1/CsvHelper.dll",
371 | "lib/netstandard2.1/CsvHelper.xml"
372 | ]
373 | },
374 | "Microsoft.Extensions.Configuration/6.0.0": {
375 | "sha512": "tq2wXyh3fL17EMF2bXgRhU7JrbO3on93MRKYxzz4JzzvuGSA1l0W3GI9/tl8EO89TH+KWEymP7bcFway6z9fXg==",
376 | "type": "package",
377 | "path": "microsoft.extensions.configuration/6.0.0",
378 | "files": [
379 | ".nupkg.metadata",
380 | ".signature.p7s",
381 | "Icon.png",
382 | "LICENSE.TXT",
383 | "THIRD-PARTY-NOTICES.TXT",
384 | "lib/net461/Microsoft.Extensions.Configuration.dll",
385 | "lib/net461/Microsoft.Extensions.Configuration.xml",
386 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll",
387 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.xml",
388 | "microsoft.extensions.configuration.6.0.0.nupkg.sha512",
389 | "microsoft.extensions.configuration.nuspec",
390 | "useSharedDesignerContext.txt"
391 | ]
392 | },
393 | "Microsoft.Extensions.Configuration.Abstractions/6.0.0": {
394 | "sha512": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==",
395 | "type": "package",
396 | "path": "microsoft.extensions.configuration.abstractions/6.0.0",
397 | "files": [
398 | ".nupkg.metadata",
399 | ".signature.p7s",
400 | "Icon.png",
401 | "LICENSE.TXT",
402 | "THIRD-PARTY-NOTICES.TXT",
403 | "lib/net461/Microsoft.Extensions.Configuration.Abstractions.dll",
404 | "lib/net461/Microsoft.Extensions.Configuration.Abstractions.xml",
405 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll",
406 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml",
407 | "microsoft.extensions.configuration.abstractions.6.0.0.nupkg.sha512",
408 | "microsoft.extensions.configuration.abstractions.nuspec",
409 | "useSharedDesignerContext.txt"
410 | ]
411 | },
412 | "Microsoft.Extensions.Configuration.Binder/6.0.0": {
413 | "sha512": "b3ErKzND8LIC7o08QAVlKfaEIYEvLJbtmVbFZVBRXeu9YkKfSSzLZfR1SUfQPBIy9mKLhEtJgGYImkcMNaKE0A==",
414 | "type": "package",
415 | "path": "microsoft.extensions.configuration.binder/6.0.0",
416 | "files": [
417 | ".nupkg.metadata",
418 | ".signature.p7s",
419 | "Icon.png",
420 | "LICENSE.TXT",
421 | "THIRD-PARTY-NOTICES.TXT",
422 | "lib/net461/Microsoft.Extensions.Configuration.Binder.dll",
423 | "lib/net461/Microsoft.Extensions.Configuration.Binder.xml",
424 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll",
425 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.xml",
426 | "microsoft.extensions.configuration.binder.6.0.0.nupkg.sha512",
427 | "microsoft.extensions.configuration.binder.nuspec",
428 | "useSharedDesignerContext.txt"
429 | ]
430 | },
431 | "Microsoft.Extensions.Configuration.EnvironmentVariables/6.0.0": {
432 | "sha512": "DjYkzqvhiHCq38LW71PcIxXk6nhtV6VySP9yDcSO0goPl7YCU1VG1f2Wbgy58lkA10pWkjHCblZPUyboCB93ZA==",
433 | "type": "package",
434 | "path": "microsoft.extensions.configuration.environmentvariables/6.0.0",
435 | "files": [
436 | ".nupkg.metadata",
437 | ".signature.p7s",
438 | "Icon.png",
439 | "LICENSE.TXT",
440 | "THIRD-PARTY-NOTICES.TXT",
441 | "lib/net461/Microsoft.Extensions.Configuration.EnvironmentVariables.dll",
442 | "lib/net461/Microsoft.Extensions.Configuration.EnvironmentVariables.xml",
443 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll",
444 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.EnvironmentVariables.xml",
445 | "microsoft.extensions.configuration.environmentvariables.6.0.0.nupkg.sha512",
446 | "microsoft.extensions.configuration.environmentvariables.nuspec",
447 | "useSharedDesignerContext.txt"
448 | ]
449 | },
450 | "Microsoft.Extensions.Configuration.FileExtensions/6.0.0": {
451 | "sha512": "V4Dth2cYMZpw3HhGw9XUDIijpI6gN+22LDt0AhufIgOppCUfpWX4483OmN+dFXRJkJLc8Tv0Q8QK+1ingT2+KQ==",
452 | "type": "package",
453 | "path": "microsoft.extensions.configuration.fileextensions/6.0.0",
454 | "files": [
455 | ".nupkg.metadata",
456 | ".signature.p7s",
457 | "Icon.png",
458 | "LICENSE.TXT",
459 | "THIRD-PARTY-NOTICES.TXT",
460 | "lib/net461/Microsoft.Extensions.Configuration.FileExtensions.dll",
461 | "lib/net461/Microsoft.Extensions.Configuration.FileExtensions.xml",
462 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.FileExtensions.dll",
463 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.FileExtensions.xml",
464 | "microsoft.extensions.configuration.fileextensions.6.0.0.nupkg.sha512",
465 | "microsoft.extensions.configuration.fileextensions.nuspec",
466 | "useSharedDesignerContext.txt"
467 | ]
468 | },
469 | "Microsoft.Extensions.Configuration.Ini/6.0.0": {
470 | "sha512": "4p1kPxibT+RNlj91k9SfvtNIUALqru9xmh+XT7Pfw80WAufCmgj3F81hpXZ4YOcFFphBSw1a+n8NZQooWxCHWQ==",
471 | "type": "package",
472 | "path": "microsoft.extensions.configuration.ini/6.0.0",
473 | "files": [
474 | ".nupkg.metadata",
475 | ".signature.p7s",
476 | "Icon.png",
477 | "LICENSE.TXT",
478 | "THIRD-PARTY-NOTICES.TXT",
479 | "lib/net461/Microsoft.Extensions.Configuration.Ini.dll",
480 | "lib/net461/Microsoft.Extensions.Configuration.Ini.xml",
481 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Ini.dll",
482 | "lib/netstandard2.0/Microsoft.Extensions.Configuration.Ini.xml",
483 | "microsoft.extensions.configuration.ini.6.0.0.nupkg.sha512",
484 | "microsoft.extensions.configuration.ini.nuspec",
485 | "useSharedDesignerContext.txt"
486 | ]
487 | },
488 | "Microsoft.Extensions.FileProviders.Abstractions/6.0.0": {
489 | "sha512": "0pd4/fho0gC12rQswaGQxbU34jOS1TPS8lZPpkFCH68ppQjHNHYle9iRuHeev1LhrJ94YPvzcRd8UmIuFk23Qw==",
490 | "type": "package",
491 | "path": "microsoft.extensions.fileproviders.abstractions/6.0.0",
492 | "files": [
493 | ".nupkg.metadata",
494 | ".signature.p7s",
495 | "Icon.png",
496 | "LICENSE.TXT",
497 | "THIRD-PARTY-NOTICES.TXT",
498 | "buildTransitive/netcoreapp2.0/Microsoft.Extensions.FileProviders.Abstractions.targets",
499 | "buildTransitive/netcoreapp3.1/_._",
500 | "lib/net461/Microsoft.Extensions.FileProviders.Abstractions.dll",
501 | "lib/net461/Microsoft.Extensions.FileProviders.Abstractions.xml",
502 | "lib/net6.0/Microsoft.Extensions.FileProviders.Abstractions.dll",
503 | "lib/net6.0/Microsoft.Extensions.FileProviders.Abstractions.xml",
504 | "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll",
505 | "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.xml",
506 | "microsoft.extensions.fileproviders.abstractions.6.0.0.nupkg.sha512",
507 | "microsoft.extensions.fileproviders.abstractions.nuspec",
508 | "useSharedDesignerContext.txt"
509 | ]
510 | },
511 | "Microsoft.Extensions.FileProviders.Physical/6.0.0": {
512 | "sha512": "QvkL7l0nM8udt3gfyu0Vw8bbCXblxaKOl7c2oBfgGy4LCURRaL9XWZX1FWJrQc43oMokVneVxH38iz+bY1sbhg==",
513 | "type": "package",
514 | "path": "microsoft.extensions.fileproviders.physical/6.0.0",
515 | "files": [
516 | ".nupkg.metadata",
517 | ".signature.p7s",
518 | "Icon.png",
519 | "LICENSE.TXT",
520 | "THIRD-PARTY-NOTICES.TXT",
521 | "buildTransitive/netcoreapp2.0/Microsoft.Extensions.FileProviders.Physical.targets",
522 | "buildTransitive/netcoreapp3.1/_._",
523 | "lib/net461/Microsoft.Extensions.FileProviders.Physical.dll",
524 | "lib/net461/Microsoft.Extensions.FileProviders.Physical.xml",
525 | "lib/net6.0/Microsoft.Extensions.FileProviders.Physical.dll",
526 | "lib/net6.0/Microsoft.Extensions.FileProviders.Physical.xml",
527 | "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Physical.dll",
528 | "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Physical.xml",
529 | "microsoft.extensions.fileproviders.physical.6.0.0.nupkg.sha512",
530 | "microsoft.extensions.fileproviders.physical.nuspec",
531 | "useSharedDesignerContext.txt"
532 | ]
533 | },
534 | "Microsoft.Extensions.FileSystemGlobbing/6.0.0": {
535 | "sha512": "ip8jnL1aPiaPeKINCqaTEbvBFDmVx9dXQEBZ2HOBRXPD1eabGNqP/bKlsIcp7U2lGxiXd5xIhoFcmY8nM4Hdiw==",
536 | "type": "package",
537 | "path": "microsoft.extensions.filesystemglobbing/6.0.0",
538 | "files": [
539 | ".nupkg.metadata",
540 | ".signature.p7s",
541 | "Icon.png",
542 | "LICENSE.TXT",
543 | "THIRD-PARTY-NOTICES.TXT",
544 | "buildTransitive/netcoreapp2.0/Microsoft.Extensions.FileSystemGlobbing.targets",
545 | "buildTransitive/netcoreapp3.1/_._",
546 | "lib/net461/Microsoft.Extensions.FileSystemGlobbing.dll",
547 | "lib/net461/Microsoft.Extensions.FileSystemGlobbing.xml",
548 | "lib/net6.0/Microsoft.Extensions.FileSystemGlobbing.dll",
549 | "lib/net6.0/Microsoft.Extensions.FileSystemGlobbing.xml",
550 | "lib/netstandard2.0/Microsoft.Extensions.FileSystemGlobbing.dll",
551 | "lib/netstandard2.0/Microsoft.Extensions.FileSystemGlobbing.xml",
552 | "microsoft.extensions.filesystemglobbing.6.0.0.nupkg.sha512",
553 | "microsoft.extensions.filesystemglobbing.nuspec",
554 | "useSharedDesignerContext.txt"
555 | ]
556 | },
557 | "Microsoft.Extensions.Primitives/6.0.0": {
558 | "sha512": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==",
559 | "type": "package",
560 | "path": "microsoft.extensions.primitives/6.0.0",
561 | "files": [
562 | ".nupkg.metadata",
563 | ".signature.p7s",
564 | "Icon.png",
565 | "LICENSE.TXT",
566 | "THIRD-PARTY-NOTICES.TXT",
567 | "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets",
568 | "buildTransitive/netcoreapp3.1/_._",
569 | "lib/net461/Microsoft.Extensions.Primitives.dll",
570 | "lib/net461/Microsoft.Extensions.Primitives.xml",
571 | "lib/net6.0/Microsoft.Extensions.Primitives.dll",
572 | "lib/net6.0/Microsoft.Extensions.Primitives.xml",
573 | "lib/netcoreapp3.1/Microsoft.Extensions.Primitives.dll",
574 | "lib/netcoreapp3.1/Microsoft.Extensions.Primitives.xml",
575 | "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll",
576 | "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml",
577 | "microsoft.extensions.primitives.6.0.0.nupkg.sha512",
578 | "microsoft.extensions.primitives.nuspec",
579 | "useSharedDesignerContext.txt"
580 | ]
581 | },
582 | "Newtonsoft.Json/13.0.1": {
583 | "sha512": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
584 | "type": "package",
585 | "path": "newtonsoft.json/13.0.1",
586 | "files": [
587 | ".nupkg.metadata",
588 | ".signature.p7s",
589 | "LICENSE.md",
590 | "lib/net20/Newtonsoft.Json.dll",
591 | "lib/net20/Newtonsoft.Json.xml",
592 | "lib/net35/Newtonsoft.Json.dll",
593 | "lib/net35/Newtonsoft.Json.xml",
594 | "lib/net40/Newtonsoft.Json.dll",
595 | "lib/net40/Newtonsoft.Json.xml",
596 | "lib/net45/Newtonsoft.Json.dll",
597 | "lib/net45/Newtonsoft.Json.xml",
598 | "lib/netstandard1.0/Newtonsoft.Json.dll",
599 | "lib/netstandard1.0/Newtonsoft.Json.xml",
600 | "lib/netstandard1.3/Newtonsoft.Json.dll",
601 | "lib/netstandard1.3/Newtonsoft.Json.xml",
602 | "lib/netstandard2.0/Newtonsoft.Json.dll",
603 | "lib/netstandard2.0/Newtonsoft.Json.xml",
604 | "newtonsoft.json.13.0.1.nupkg.sha512",
605 | "newtonsoft.json.nuspec",
606 | "packageIcon.png"
607 | ]
608 | },
609 | "System.Runtime.CompilerServices.Unsafe/6.0.0": {
610 | "sha512": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
611 | "type": "package",
612 | "path": "system.runtime.compilerservices.unsafe/6.0.0",
613 | "files": [
614 | ".nupkg.metadata",
615 | ".signature.p7s",
616 | "Icon.png",
617 | "LICENSE.TXT",
618 | "THIRD-PARTY-NOTICES.TXT",
619 | "buildTransitive/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.targets",
620 | "buildTransitive/netcoreapp3.1/_._",
621 | "lib/net461/System.Runtime.CompilerServices.Unsafe.dll",
622 | "lib/net461/System.Runtime.CompilerServices.Unsafe.xml",
623 | "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll",
624 | "lib/net6.0/System.Runtime.CompilerServices.Unsafe.xml",
625 | "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.dll",
626 | "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.xml",
627 | "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll",
628 | "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml",
629 | "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
630 | "system.runtime.compilerservices.unsafe.nuspec",
631 | "useSharedDesignerContext.txt"
632 | ]
633 | }
634 | },
635 | "projectFileDependencyGroups": {
636 | "net6.0": [
637 | "CsvHelper >= 27.2.1",
638 | "Microsoft.Extensions.Configuration.Binder >= 6.0.0",
639 | "Microsoft.Extensions.Configuration.EnvironmentVariables >= 6.0.0",
640 | "Microsoft.Extensions.Configuration.Ini >= 6.0.0",
641 | "Newtonsoft.Json >= 13.0.1"
642 | ]
643 | },
644 | "packageFolders": {
645 | "C:\\Users\\patma\\.nuget\\packages\\": {}
646 | },
647 | "project": {
648 | "version": "1.0.0",
649 | "restore": {
650 | "projectUniqueName": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj",
651 | "projectName": "ForzaDualSense",
652 | "projectPath": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj",
653 | "packagesPath": "C:\\Users\\patma\\.nuget\\packages\\",
654 | "outputPath": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\obj\\",
655 | "projectStyle": "PackageReference",
656 | "configFilePaths": [
657 | "C:\\Users\\patma\\AppData\\Roaming\\NuGet\\NuGet.Config"
658 | ],
659 | "originalTargetFrameworks": [
660 | "net6.0"
661 | ],
662 | "sources": {
663 | "https://api.nuget.org/v3/index.json": {}
664 | },
665 | "frameworks": {
666 | "net6.0": {
667 | "targetAlias": "net6.0",
668 | "projectReferences": {}
669 | }
670 | },
671 | "warningProperties": {
672 | "warnAsError": [
673 | "NU1605"
674 | ]
675 | }
676 | },
677 | "frameworks": {
678 | "net6.0": {
679 | "targetAlias": "net6.0",
680 | "dependencies": {
681 | "CsvHelper": {
682 | "target": "Package",
683 | "version": "[27.2.1, )"
684 | },
685 | "Microsoft.Extensions.Configuration.Binder": {
686 | "target": "Package",
687 | "version": "[6.0.0, )"
688 | },
689 | "Microsoft.Extensions.Configuration.EnvironmentVariables": {
690 | "target": "Package",
691 | "version": "[6.0.0, )"
692 | },
693 | "Microsoft.Extensions.Configuration.Ini": {
694 | "target": "Package",
695 | "version": "[6.0.0, )"
696 | },
697 | "Newtonsoft.Json": {
698 | "target": "Package",
699 | "version": "[13.0.1, )"
700 | }
701 | },
702 | "imports": [
703 | "net461",
704 | "net462",
705 | "net47",
706 | "net471",
707 | "net472",
708 | "net48"
709 | ],
710 | "assetTargetFallback": true,
711 | "warn": true,
712 | "downloadDependencies": [
713 | {
714 | "name": "Microsoft.AspNetCore.App.Runtime.win-x64",
715 | "version": "[6.0.1, 6.0.1]"
716 | },
717 | {
718 | "name": "Microsoft.NETCore.App.Crossgen2.win-x64",
719 | "version": "[6.0.1, 6.0.1]"
720 | },
721 | {
722 | "name": "Microsoft.NETCore.App.Runtime.win-x64",
723 | "version": "[6.0.1, 6.0.1]"
724 | },
725 | {
726 | "name": "Microsoft.WindowsDesktop.App.Runtime.win-x64",
727 | "version": "[6.0.1, 6.0.1]"
728 | }
729 | ],
730 | "frameworkReferences": {
731 | "Microsoft.NETCore.App": {
732 | "privateAssets": "all"
733 | }
734 | },
735 | "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.101\\RuntimeIdentifierGraph.json"
736 | }
737 | },
738 | "runtimes": {
739 | "win-x64": {
740 | "#import": []
741 | }
742 | }
743 | }
744 | }
--------------------------------------------------------------------------------
/ForzaDualSense/obj/project.nuget.cache:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "dgSpecHash": "u6+aSbCixUqt5+Y4Vyq+1t844RxyF57SrsYYlYeskYeBavNtRfwdt0hmIva7Xo2quUpIKAwA4341aBHcOgmWDQ==",
4 | "success": true,
5 | "projectFilePath": "C:\\Data\\Development\\ForzaDualSense\\ForzaDualSense\\ForzaDualSense.csproj",
6 | "expectedPackageFiles": [
7 | "C:\\Users\\patma\\.nuget\\packages\\csvhelper\\27.2.1\\csvhelper.27.2.1.nupkg.sha512",
8 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.configuration\\6.0.0\\microsoft.extensions.configuration.6.0.0.nupkg.sha512",
9 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\6.0.0\\microsoft.extensions.configuration.abstractions.6.0.0.nupkg.sha512",
10 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.configuration.binder\\6.0.0\\microsoft.extensions.configuration.binder.6.0.0.nupkg.sha512",
11 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.configuration.environmentvariables\\6.0.0\\microsoft.extensions.configuration.environmentvariables.6.0.0.nupkg.sha512",
12 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.configuration.fileextensions\\6.0.0\\microsoft.extensions.configuration.fileextensions.6.0.0.nupkg.sha512",
13 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.configuration.ini\\6.0.0\\microsoft.extensions.configuration.ini.6.0.0.nupkg.sha512",
14 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.fileproviders.abstractions\\6.0.0\\microsoft.extensions.fileproviders.abstractions.6.0.0.nupkg.sha512",
15 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.fileproviders.physical\\6.0.0\\microsoft.extensions.fileproviders.physical.6.0.0.nupkg.sha512",
16 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.filesystemglobbing\\6.0.0\\microsoft.extensions.filesystemglobbing.6.0.0.nupkg.sha512",
17 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.extensions.primitives\\6.0.0\\microsoft.extensions.primitives.6.0.0.nupkg.sha512",
18 | "C:\\Users\\patma\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512",
19 | "C:\\Users\\patma\\.nuget\\packages\\system.runtime.compilerservices.unsafe\\6.0.0\\system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
20 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.netcore.app.runtime.win-x64\\6.0.1\\microsoft.netcore.app.runtime.win-x64.6.0.1.nupkg.sha512",
21 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.windowsdesktop.app.runtime.win-x64\\6.0.1\\microsoft.windowsdesktop.app.runtime.win-x64.6.0.1.nupkg.sha512",
22 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.aspnetcore.app.runtime.win-x64\\6.0.1\\microsoft.aspnetcore.app.runtime.win-x64.6.0.1.nupkg.sha512",
23 | "C:\\Users\\patma\\.nuget\\packages\\microsoft.netcore.app.crossgen2.win-x64\\6.0.1\\microsoft.netcore.app.crossgen2.win-x64.6.0.1.nupkg.sha512"
24 | ],
25 | "logs": []
26 | }
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | ## Archival
2 |
3 | As of November 23, 2023. The Repo maintained by cosmii02: https://github.com/cosmii02/ForzaDSX provides a more stable experience, a UI to customize the settings the originally differed between the two repos, and a larger user base. For those reasons, I am choosing to archive this and direct everyone to that repo.
4 |
5 |
6 |
7 | ## Intro
8 |
9 | This is a program to control lights and adaptive triggers on a DualSense 5 controller using DualSenseX for compatible Forza Games.
10 |
11 | **NOTE:** This is currently in the alpha stage. It works well and provides decent feedback via the adaptive triggers. However it is not perfect and definetly has room for improvement. I welcome people to help make needed adjustments(See below for more information).
12 |
13 | **WARNING:** Since we do not have exact specifications of the commands sent to the controller, it is possible that certain values may be out of specification or in the not-reccomended categories. This could cause damage to controllers. Although I use this program without issue, I will not be held liable for damage to your controller due to it's use.
14 |
15 | ## Adjusting / Modifying
16 |
17 | This program is written in very simple C#. If you do not know C#, it should not be hard to learn the necessary parts. If you do not know any programming, it may be more difficult, but I have left the relevant code very well documented.
18 |
19 | The only file you should need to modify is `Program.cs` Within it the conversion from Forza Data to dual Sense output is all handled in the function `SendData` which should be the top most function. I reccomend understanding this fully and taking the rest of the code for granted as the other code shouldn't need to be modified. If you do care to modify it, please try to understand it based on context and the comments. See the credits for the sources.
20 |
21 | The `SendData` function takes the parsed data from Forza and decides how best to adapt the triggers. It does this by setting the necesary parameters on the message to send to DualSenseX and sending it. See the `Test` function for examples. The parameters are documented further in the code.
22 |
23 | There are not any commands that can be sent to DualSenseX that cannot be accessed via the DualSenseX UI. That makes that UI a great way to test the feeling of different parameters. Based on warnings I have seen, avoid setting frequency to anything above 40 Hz.
24 |
25 | If you feel your modifications are better, please sumbit them via a PR request. I will also be available on the DualSenseX discord server to discuss how to replicate real brakes/throttle.
26 |
27 | **Note:** I do not know exactly how to best replicate a good brake feel. If anyone has access to F1 2021 on PC or any of the adaptive control racing games on PS5 and would like to contribute with descriptions of how the triggers react in different situationsm, that would be great.
28 |
29 | ## Setup:
30 |
31 | In Forza, under HUD turn on the UDP data out, with an IP of 127.0.0.1 and a port of 5300.
32 | 
33 |
34 | In DualSenseX under the controller settings, set the UDP port to your preference and ensure the UDP Port Listener is enabled. The program should automatically use what is set.
35 |
36 |
37 | It should work.
38 |
39 | ## Running Release
40 |
41 | Download the Release from the releases page. Extract the Zip folder to a preferred location. Run the Executable to enjoy adaptive triggers on Forza!
42 |
43 | ## appsettings.ini
44 |
45 | There is a settings file in the release. You can change the settings to your preference.
46 |
47 | ## Running From Source
48 |
49 | 1. Install the .Net Core 6.0 SDK (x64 version) from here: [https://dotnet.microsoft.com/en-us/download/dotnet/6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
50 | 2. Clone this repository with git
51 | 3. While in the top level of this repository, run `dotnet run --project ForzaDualSense`.
52 | - Top level here refers to the main directory of the repository such that the subdirectories are `.vscode` and `ForzaDualSense`
53 |
54 | ## Thanks and Credits
55 |
56 | [DualSenseX](https://github.com/Paliverse/DualSenseX)
57 |
58 | [Forza-Telemetry](https://github.com/austinbaccus/forza-telemetry/tree/main/ForzaCore)
59 |
--------------------------------------------------------------------------------